js调用摄像头,实现简单的视频展台软件
参考文档:
web api:https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices
codeantenna:https://codeantenna.com/a/Py4bUtGBSS
极客教程:https://geek-docs.com/javascript/javascript-ask-answer/g_how-to-get-camera-resolution-using-javascript.html
功能:
显示设备名称、放大、缩小、旋转(0°、90°、180°、270°)、上下镜像、左右镜像、拍照、录像、识别二维码、批注、橡皮檫、清空批注内容、冻结视频
网页效果:↓
代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>HP CamScan(web)</title> <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script> <script src="https://cozmo.github.io/jsQR/jsQR.js"></script> <style> body{ margin: 0; padding: 0; overflow-y: hidden; } #side_bar{ background-color: rgb(45, 45, 45); float: left; width: 50px; height: 500px; padding-top: 40px; padding-bottom: 0px; padding-left: 2px; padding-right: 2px; } #video_bar{ background-color: black; float: left; text-align: center; } #tool_bar{ position: absolute; bottom: 50px; left:50%; width: 310px; height: 80px; margin-left: -150px; z-index: 9999; } #corner_bar{ position: absolute; bottom: 50px; right: 50px; width: 120px; height: 60px; } #side_bar button{ width: 50px;height: 50px; border: none; padding: 5px; background-color: rgb(45, 45, 45); } #side_bar button:hover{ border: 1px solid gray; border-radius: 25px; background-color: gray; } #side_bar button img{ width: 40px;height: 40px; } #device_select_dialog{ border: 1px solid #696969; border-radius: 10px; background-color: rgba(105, 105, 105, 0.5); width: 400px; height: 260px; position: absolute; top: 40px; left: 65px; padding: 10px; } #device_select_dialog p{ color: #66CD00; font-size: 8px; } #suofang_dialog{ width: 400px; height: 150px; position: absolute; top: 50%; left: 50%; margin-top: -75px; margin-left: -200px; text-align: center; } #suofang_dialog #suofang_dialog_beishu{ width: 58px; height: 32px; border: 1px solid white; border-radius: 16px; background-color: white; font-size: 14px; color: black; } #suofang_dialog #suofang_dialog_suoxiao{ width: 100px; height: 100px; background-color: transparent; padding: 30px; border: none; } #suofang_dialog #suofang_dialog_suoxiao:hover{ border: 1px solid gray; border-radius: 50px; background-color: rgba(128, 128, 128, 0.5); } #suofang_dialog #suofang_dialog_huakuai{ width: 180px; height: 50px; line-height: 50px; } #suofang_dialog #suofang_dialog_fangda{ width: 100px; height: 100px; background-color: transparent; padding: 30px; border: none; } #suofang_dialog #suofang_dialog_fangda:hover{ border: 1px solid gray; border-radius: 50px; background-color: rgba(128, 128, 128, 0.5); } #xuanzhuan_dialog{ width: 400px; height: 400px; position: absolute; top: 50%; left: 50%; margin-top: -200px; margin-left: -200px; text-align: center; } #xuanzhuan_dialog button{ margin: 10px; border: 2px solid white; border-radius: 10px; background-color: transparent; width: 20px;height: 20px; } #xuanzhuan_dialog #xuanzhuan_dialog_dushu{ width: 280px;height: 280px; border: 2px solid white; border-radius: 140px; color: black; font-size: 32px; } #tool_bar button{ width: 80px;height: 80px; border: 1px solid white; border-radius: 40px; background-color: rgba(52,43,34,0.8); } #tool_bar #tool_pizhu{ width: 52px;height: 52px; border: 1px solid rgba(190, 190, 190, 0.8);/*rgb(28, 28, 22)*/ border-radius: 26px; background-color: rgba(190, 190, 190, 0.8); } #corner_bar button{ width: 46px; height: 46px; margin: 5px; padding: 0px; background-color: rgb(17,14,14); border: 1px solid rgb(17,14,14); border-radius: 24px; } #pizhu_dialog{ width: 160px; height: 50px; border: 1px solid rgba(190, 190, 190, 0.8); border-radius: 26px; background-color: rgba(190, 190, 190, 0.8); position: absolute; bottom: 59px; left: 50%; margin-left: 105px; z-index: 999; } #pizhu_dialog button{ float: right; width: 52px; height: 52px; border: 1px solid rgba(190, 190, 190, 0.8); border-radius: 26px; background-color: rgba(190, 190, 190, 0.8); } #pizhu_clear_all{ width: 100px; height: 40px; position: absolute; bottom: 120px; left: 50%; margin-left: 190px; z-index: 999; background-color: rgba(10, 15, 20, 0.7); color: white; font-weight: 700; border: none; } </style> </head> <body onload="load()"> <!-- 侧边栏按钮组 --> <div id="side_bar"> <button onclick="select_video_dialog()"><img src="HPImage/shexiangtou.png" alt=""></button> <button onclick="suofang_dialog()"><img src="HPImage/suofang.png" alt=""></button> <button onclick="xuanzhuan_dialog()"><img src="HPImage/xuanzhuan.png" alt=""></button> <button><img src="HPImage/jingxiang.png" alt=""></button> <button onclick="shangxiajingxiang()"><img src="HPImage/shangxiajingxiang.png" alt=""></button> <button onclick="zuoyoujingxiang()"><img src="HPImage/zuoyoujingxiang.png" alt=""></button> </div> <!-- 视频区域 --> <div id="video_bar"><video src="" id="video"></video></div> <!-- 工具栏:拍照、批注按钮组 --> <div id="tool_bar"> <!-- <button>更多</button> --> <button id="tool_paizhao" onclick="tool_paizhao()"><img width="50px" height="50px" src="HPImage/paizhao.png" alt=""></button> <button id="tool_luxiang" onclick="tool_luxiang()"><img id="tool_luxiang_img" width="50px" height="50px" src="HPImage/luxiang_start.png" alt=""></button> <button id="tool_tiaoma" onclick="tool_tiaoma()"><img width="50px" height="50px" src="HPImage/saoma.png" alt=""></button> <button id="tool_pizhu" onclick="tool_pizhu()"><img id="tool_pizhu_img" width="40px" height="40px" src="HPImage/pizhu.png" alt=""></button> </div> <!-- 工具栏:冻结、九宫格按钮组 --> <div id="corner_bar"> <button id="tool_dongjie" onclick="tool_dongjie()"><img id="tool_dongjie_img" width="38px" height="38px" src="HPImage/dongjie.png" alt=""></button> <!-- <button id="tool_sigongge" onclick="tool_sigongge()"><img id="tool_sigongge_img" width="38px" height="38px" src="HPImage/sigongge.png" alt=""></button> --> </div> <!-- 哈哈哈哈哈哈哈哈哈哈哈 begin 哈哈哈哈哈哈哈哈哈哈哈啊哈 --> <!-- 画布 --> <canvas id="canvas" style="display:none;"></canvas> <!-- 请选择摄像头 --> <div id="device_select_dialog"> <p>Select Camera</p> <p id="device_select_dialog_name" style="padding-left: 30px;font-size: 14px;color: white;"></p> </div> <!-- 缩放 --> <div id="suofang_dialog"> <button id="suofang_dialog_beishu">1x</button> <br> <button id="suofang_dialog_suoxiao" onclick="suofang_dialog_suoxiao()"><img width="40px" height="40px" src="HPImage/suoxiao.png" alt=""></button> <input type="range" id="suofang_dialog_huakuai" min="1" max="10" step="1" value="1"> <button id="suofang_dialog_fangda" onclick="suofang_dialog_fangda()"><img width="40px" height="40px" src="HPImage/fangda.png" alt=""></button> </div> <!-- 旋转 --> <div id="xuanzhuan_dialog"> <button onclick="xuanzhuan_dialog_90()" id="xuanzhuan_dialog_90"></button><br> <button onclick="xuanzhuan_dialog_180()" id="xuanzhuan_dialog_180"></button> <button id="xuanzhuan_dialog_dushu">0°</button> <button onclick="xuanzhuan_dialog_0()" id="xuanzhuan_dialog_0"></button><br> <button onclick="xuanzhuan_dialog_270()" id="xuanzhuan_dialog_270"></button> </div> <!-- 批注 总窗口 --> <div id="pizhu_dialog"> <button onclick="pizhu_dialog_xiangpicha()"><img id="pizhu_dialog_xiangpicha_img" width="30px" height="30px" src="HPImage/xiangpicha.png" alt=""></button> <button onclick="pizhu_dialog_bi()"><img id="pizhu_dialog_bi_img" width="30px" height="30px" src="HPImage/bi.png" alt=""></button> </div> <!-- 批注画布 --> <canvas id="pizhu_canvas" style="background-color: transparent;position: absolute;top: 0px;left: 54px;"></canvas> <!-- 批注,清空按钮 --> <button id="pizhu_clear_all" onclick="pizhu_qingkong()">ClearAll</button> <!-- 哈哈哈哈哈哈哈哈哈哈哈 end 哈哈哈哈哈哈哈哈哈哈哈哈啊哈 --> <script> // 全局变量 var video = document.getElementById("video"); // 视频对象 var record = null; // 录像对象 var recordData = []; // 存储视频流 var context; // 绘制对象 var canvas = document.getElementById("canvas"); // 画布对象 var zoom = document.getElementById("suofang_dialog_huakuai"); // 缩放滑块 var pizhu_canvas = document.getElementById("pizhu_canvas"); // 批注画布 var pizhu_ctx = pizhu_canvas.getContext("2d"); var state = { "side_shexiangtou": false, "side_suofang": false, "side_xuanzhuan": false, "side_shangxiajingxiang": false, "side_zuoyoujingxiang": false, "tool_luxiang": false, "tool_pizhu": false, "tool_dongjie": false, "pizhu": { "pizhu_bi": false, "pizhu_xiangpicha": false, "pizhu_qingkong": false, "pizhu_press": false, "lastX": 0, "lastY": 0 } }; // 加载网页时运行 function load(){ // 设置侧边栏css let side_bar = document.getElementById("side_bar"); side_bar.style.width = 50 + "px"; side_bar.style.height = window.innerHeight + "px"; // 设置视频区域css let video_bar = document.getElementById("video_bar"); video_bar.style.width = window.innerWidth - 54 + "px"; video_bar.style.height = window.innerHeight + "px"; // 设置拍照 let tool_bar = document.getElementById("tool_bar"); // 设置批注画布 pizhu_canvas.width = window.innerWidth - 54; pizhu_canvas.height = window.innerHeight; // 打开视频 OpenVideo() video.style.height = window.innerHeight + "px"; // 设置先关的dialog隐藏 document.getElementById("device_select_dialog").style.display = "none"; document.getElementById("suofang_dialog").style.display = "none"; document.getElementById("xuanzhuan_dialog").style.display = "none"; document.getElementById("pizhu_dialog").style.display = "none"; pizhu_canvas.style.display = "none"; document.getElementById("pizhu_clear_all").style.display = "none"; document.getElementById("xuanzhuan_dialog_0").style.backgroundColor = "green"; } // 请选择摄像头 function select_video_dialog(){ state.side_shexiangtou = state.side_shexiangtou == true ? false :true; if(state.side_shexiangtou){ document.getElementById("device_select_dialog").style.display = "block"; }else{ document.getElementById("device_select_dialog").style.display = "none"; } } // 缩放按钮 function suofang_dialog(){ state.side_suofang = state.side_suofang == true ? false :true; if(state.side_suofang){ document.getElementById("suofang_dialog").style.display = "block"; }else{ document.getElementById("suofang_dialog").style.display = "none"; } } // 缩小 function suofang_dialog_suoxiao(){ let zoom_val = zoom.value; if(zoom_val == "1"){return;} let new_val = parseInt(zoom_val) + 10 - 1; let height_val = window.innerHeight; video.style.height = height_val / 10 * new_val + "px"; zoom.value = parseInt(zoom_val) - 1; document.getElementById("suofang_dialog_beishu").innerHTML = zoom.value + "x"; } // 放大 function suofang_dialog_fangda(){ let zoom_val = zoom.value; if(zoom_val == "10"){return;} let new_val = parseInt(zoom_val) + 10; let height_val = window.innerHeight; video.style.height = height_val / 10 * new_val + "px"; zoom.value = parseInt(zoom_val) + 1; document.getElementById("suofang_dialog_beishu").innerHTML = zoom.value + "x"; } // 旋转按钮 function xuanzhuan_dialog(){ state.side_xuanzhuan = state.side_xuanzhuan == true ? false :true; if(state.side_xuanzhuan){ document.getElementById("xuanzhuan_dialog").style.display = "block"; }else{ document.getElementById("xuanzhuan_dialog").style.display = "none"; } } // 旋转0 function xuanzhuan_dialog_0(){ document.getElementById("xuanzhuan_dialog_0").style.backgroundColor = "green"; document.getElementById("xuanzhuan_dialog_90").style.backgroundColor = "transparent"; document.getElementById("xuanzhuan_dialog_180").style.backgroundColor = "transparent"; document.getElementById("xuanzhuan_dialog_270").style.backgroundColor = "transparent"; document.getElementById("xuanzhuan_dialog_dushu").innerHTML = "0°"; video.style.transform = "rotate(0deg)"; } function xuanzhuan_dialog_90(){ document.getElementById("xuanzhuan_dialog_0").style.backgroundColor = "transparent"; document.getElementById("xuanzhuan_dialog_90").style.backgroundColor = "green"; document.getElementById("xuanzhuan_dialog_180").style.backgroundColor = "transparent"; document.getElementById("xuanzhuan_dialog_270").style.backgroundColor = "transparent"; document.getElementById("xuanzhuan_dialog_dushu").innerHTML = "90°"; video.style.transform = "rotate(90deg)"; } function xuanzhuan_dialog_180(){ document.getElementById("xuanzhuan_dialog_0").style.backgroundColor = "transparent"; document.getElementById("xuanzhuan_dialog_90").style.backgroundColor = "transparent"; document.getElementById("xuanzhuan_dialog_180").style.backgroundColor = "green"; document.getElementById("xuanzhuan_dialog_270").style.backgroundColor = "transparent"; document.getElementById("xuanzhuan_dialog_dushu").innerHTML = "180°"; video.style.transform = "rotate(180deg)"; } function xuanzhuan_dialog_270(){ document.getElementById("xuanzhuan_dialog_0").style.backgroundColor = "transparent"; document.getElementById("xuanzhuan_dialog_90").style.backgroundColor = "transparent"; document.getElementById("xuanzhuan_dialog_180").style.backgroundColor = "transparent"; document.getElementById("xuanzhuan_dialog_270").style.backgroundColor = "green"; document.getElementById("xuanzhuan_dialog_dushu").innerHTML = "270°"; video.style.transform = "rotate(270deg)"; } // 上下镜像 function shangxiajingxiang(){ state.side_shangxiajingxiang = state.side_shangxiajingxiang == true ? false :true; if(state.side_shangxiajingxiang){ video.style.transform = "rotateX(180deg)"; }else{ video.style.transform = "rotateX(0deg)"; } } // 左右镜像 function zuoyoujingxiang(){ state.side_zuoyoujingxiang = state.side_zuoyoujingxiang == true ? false :true; if(state.side_zuoyoujingxiang){ video.style.transform = "rotateY(180deg)"; }else{ video.style.transform = "rotateY(0deg)"; } } // 拍照 function tool_paizhao(){ let ctx = canvas.getContext("2d"); canvas.width = 1920; canvas.height = 1080; ctx.drawImage(video, 0,0,1920,1080); let data = new Date(); let fileName = data.toLocaleString() + data.getMilliseconds() + ".jpg"; fileName = fileName.replaceAll("/", "-"); fileName = fileName.replaceAll(":", "_"); let a = document.createElement("a"); a.setAttribute("download", fileName); a.href = canvas.toDataURL("image/jpeg"); a.click(); } // 录制视频 function tool_luxiang(){ if(record == null){ record = new MediaRecorder(video.captureStream()); record.ondataavailable = (e) => { console.log(e); recordData.push(e.data); }; record.onstop = (e) =>{ const blob = new Blob(recordData, {"type": record.mimeType}); const videoUrl = window.URL.createObjectURL(blob); let data = new Date(); let fileName = data.toLocaleString() + data.getMilliseconds() + ".mp4"; fileName = fileName.replaceAll("/", "-"); fileName = fileName.replaceAll(":", "_"); let a = document.createElement("a"); a.setAttribute("download", fileName); a.href = videoUrl; a.click(); window.URL.revokeObjectURL(videoUrl); }; } state.tool_luxiang = state.tool_luxiang == false ? true : false; if(state.tool_luxiang){ record.start(); document.getElementById("tool_luxiang_img").src = "HPImage/luxiang_stop.png"; }else{ record.stop(); recordData.splice(0, recordData.length); document.getElementById("tool_luxiang_img").src = "HPImage/luxiang_start.png"; } } // 识别条码 function tool_tiaoma(){ let ctx = canvas.getContext("2d"); canvas.width = 1920; canvas.height = 1080; ctx.drawImage(video, 0,0,1920,1080); let imageData = ctx.getImageData(0,0,1920, 1080); let code = jsQR(imageData.data, 1920, 1080, {inversionAttmpts: "dontInvert"}) console.log(code); if(code){ const textArea = document.createElement("textarea"); textArea.value = code.data; document.body.appendChild(textArea); textArea.select(); document.execCommand("copy"); document.body.removeChild(textArea); } } // 批注 function tool_pizhu(){ state.tool_pizhu = state.tool_pizhu == true ? false : true; if(state.tool_pizhu){ document.getElementById("pizhu_dialog").style.display = "block"; document.getElementById("tool_pizhu").style.backgroundColor = "rgb(44, 40, 41)"; document.getElementById("tool_pizhu").style.border = "1px solid rgb(44, 40, 41)"; document.getElementById("tool_pizhu_img").src = "HPImage/pizhu2.png"; document.getElementById("pizhu_dialog_bi_img").src = "HPImage/bi2.png"; pizhu_canvas.style.display = "block"; state.pizhu.pizhu_bi = true; }else{ document.getElementById("pizhu_dialog").style.display = "none"; document.getElementById("tool_pizhu").style.backgroundColor = "rgba(190, 190, 190, 0.8)"; document.getElementById("tool_pizhu").style.border = "1px solid rgba(190, 190, 190, 0.8)"; document.getElementById("tool_pizhu_img").src = "HPImage/pizhu.png"; document.getElementById("pizhu_dialog_xiangpicha_img").src = "HPImage/xiangpicha.png"; pizhu_canvas.style.display = "none"; document.getElementById("pizhu_clear_all").style.display = "none"; state.pizhu.pizhu_bi = false; state.pizhu.pizhu_xiangpicha = false; state.pizhu.pizhu_qingkong = false; } } // 橡皮檫 function pizhu_dialog_xiangpicha(){ // 已经选中橡皮檫了,再次点击,就显示【清空全部】 if(state.pizhu.pizhu_xiangpicha){ state.pizhu.pizhu_qingkong = state.pizhu.pizhu_qingkong == true ? false : true; if(state.pizhu.pizhu_qingkong){ document.getElementById("pizhu_clear_all").style.display = "block"; }else{ document.getElementById("pizhu_clear_all").style.display = "none"; } } state.pizhu.pizhu_xiangpicha = true; state.pizhu.pizhu_bi = false; document.getElementById("pizhu_dialog_xiangpicha_img").src = "HPImage/xiangpicha2.png"; document.getElementById("pizhu_dialog_bi_img").src = "HPImage/bi.png"; } // 笔 function pizhu_dialog_bi(){ state.pizhu.pizhu_bi = true; if(state.pizhu.pizhu_bi){ document.getElementById("pizhu_dialog_xiangpicha_img").src = "HPImage/xiangpicha.png"; document.getElementById("pizhu_dialog_bi_img").src = "HPImage/bi2.png"; state.pizhu.pizhu_bi = true; state.pizhu.pizhu_xiangpicha = false; state.pizhu.pizhu_qingkong = false; document.getElementById("pizhu_clear_all").style.display = "none"; } } // 冻结 function tool_dongjie(){ state.tool_dongjie = state.tool_dongjie == false ? true : false; if(state.tool_dongjie){ document.getElementById("tool_dongjie_img").src = "HPImage/dongjie2.png"; video.pause(); } else{ document.getElementById("tool_dongjie_img").src = "HPImage/dongjie.png" video.play(); } } // 打开视频 function OpenVideo(){ navigator.mediaDevices.getUserMedia({video: true}) .then(function(mediaStream){ video.srcObject = mediaStream; video.play(); // 记录设备名称 let device_name = mediaStream.getVideoTracks()[0].label; document.getElementById("device_select_dialog_name").innerHTML = device_name; }) .catch(function(err){ alert(err.message); }) } /* ================== 批注相关 begin ================================== */ pizhu_canvas.addEventListener("mousedown", (e)=>{ state.pizhu.pizhu_press = true; if(state.pizhu.pizhu_bi){[state.pizhu.lastX, state.pizhu.lastY] = [e.offsetX, e.offsetY];} if(state.pizhu.pizhu_xiangpicha){pizhu_ctx.clearRect(e.offsetX - 4, e.offsetY - 4, 8, 8);} }) pizhu_canvas.addEventListener("mousemove", (e)=>{ if(state.pizhu.pizhu_bi && state.pizhu.pizhu_press){ drawLine(state.pizhu.lastX, state.pizhu.lastY, e.offsetX, e.offsetY); [state.pizhu.lastX, state.pizhu.lastY] = [e.offsetX, e.offsetY]; } if(state.pizhu.pizhu_xiangpicha && state.pizhu.pizhu_press){ pizhu_ctx.clearRect(e.offsetX - 4, e.offsetY - 4, 8, 8); } }) pizhu_canvas.addEventListener("mouseup", (e)=>{state.pizhu.pizhu_press = false;}) function drawLine(x1, y1, x2, y2){ pizhu_ctx.beginPath(); pizhu_ctx.moveTo(x1, y1); pizhu_ctx.lineTo(x2, y2); pizhu_ctx.lineWidth = 5; pizhu_ctx.strokeStyle = "#FF0000"; pizhu_ctx.stroke(); } function pizhu_qingkong(){ pizhu_ctx.clearRect(0,0,pizhu_canvas.width, pizhu_canvas.height); } /* ================== 批注相关 end ==================================== */ </script> </body> </html>