js调用摄像头,实现简单的视频展台软件

发布时间 2023-10-09 18:01:30作者: 十一的杂文录

功能

  显示设备名称、放大、缩小、旋转(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"></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 = "";
            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>