教你web如何调用摄像头拍照并上传到服务器(谷歌浏览器亲测可用!)

这几天公司有一个业务需求,就是需要在web前端(一般环境都是谷歌浏览器)调用本地摄像头进行拍照,然后上传到服务器上。有两种解决方案,一种是通过flash调用本地摄像头进行拍照,还有一种方式就是使用video标签获取本地摄像头摄像,然后结合canvas标签进行展示。
项目中前端使用的是layui,但并不影响迁移到其他框架,主语要把layui框架的一些语法换成迁移框架的即可。同时,项目中对启用摄像头,拍照生成照片(base64)等功能进行了封装,所以在使用此案例时可以直接导入封装好的js文件,然后再需要的地方引入即可。由于公司后端接口存在保密性,就不贴出来了,时间原因回头我自己写一个小的demo贴出来。

上传至服务器思路:用户点击拍照功能,会生成base64编码格式的文件,然后再转码成file文件格式(相当于input type=‘file’),实现上传。这样后端MultipartFile可以直接接收。(其实也可以转码成Blob(二进制)大对象,但是上传至服务器就是一个blob文件,图片回显的时候是直接通过url链接服务器地址进行回显,所以不能实现图片回显的功能。如果是图片下载,或者你不嫌麻烦在回显的时候先从图片服务器下载下来缓存到本地,也可以直接上传blob对象,后端MultipartFile也是可以接收的)
废话不多少,直接上代码。
前端Html:

<div class="layui-fluid layui-anim" id="camera" lay-title="摄像头测试">
    <div class="layui-row">
        <div class="layui-card" style="background-color:#f0f0f0;margin: auto auto;">
            <div class="layui-card-body" style="padding: 0px;">
                <div class="layui-row camera-show-pc">
                    <div class="bag_display_flex" style="justify-content: center;display: flex">
                        <div style="position: relative;">
                            <video id="v" style="background-color: #000000"></video>
                            <div class="camera-control">
                                <button type="button" id="stop" class="layui-icon layui-icon-stop camera-btn">关闭</button>
                                <button type="button" id="start" class="layui-icon layui-icon-triangle-r camera-btn">开始</button>
                                <button type="button" id="snap" class="layui-icon layui-icon-camera-fill camera-btn">拍照</button>
                                <button type="button" id="clear" class="layui-icon layui-icon-fonts-clear camera-btn">清空</button>
                                <button type="button" id="save" class="layui-icon layui-icon-save camera-btn">上传</button>
                            </div>
                        </div>
                        <div>
                            <canvas id="canvas" style="margin-left: 2px;background-color: #000000"></canvas>
                        </div>
                    </div>
                    <div class="layui-row" style="position: relative">
                        <div class="camera-canvas-group"></div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div class="layui-form-item" style="text-align: center;">
        <button id="closeBtn" type="button" class="layui-btn layui-btn-primary" function="close">关闭</button>
    </div>
</div>
//在使用前引入
<script type="text/javascript" src="${ctxPath!}/plugins/camvas/camvas.js"></script>
<script data-th-inline="javascript" type="text/javascript">
    layui.use(['jquery','layer'], function () {
        var $ = layui.jquery,
            $view = $('#camera'),
            config={},
            myCamvas;
        var layer = layui.layer;
        init();
        onClick();


        function init() {
            let scale = 120;//宽高比例倍数
            config = {
                video:{width:Number(scale*4),height:Number(scale*3)},//4:3
                canvasId:'canvas',
                videoId:'v',
                imgType:'png',
                quality:'1' //图片质量0-1之间
            };
            $('.camera-show-pc').css('width',config.video.width*2+20);
            $view.find('#canvas').attr('width',config.video.width);
            $view.find('#canvas').attr('height',config.video.height);
            //拍照实例化
            myCamvas = new camvas(config);
            myCamvas.startCamera();
            $view.find('#start').hide();
        }
        function onClick() {
            //停止拍照
            $view.find('#stop').click(function () {
                myCamvas.stop()
                $view.find('#stop').hide()
                $view.find('#start').show()
                $view.find('#snap').show()
            })
            //启动摄像头
            $view.find('#start').click(function () {
                myCamvas.startCamera();
                $view.find('#stop').show()
                $view.find('#start').hide()
            })
            //拍照
            $view.find('#snap').click(function () {
                myCamvas.drawImage(function drawImage(base64URL) {
                    let xsCamera = $('<div></div>').addClass('camera-canvas-item');
                    //这个可以实现连续拍照,公司业务需求只需要单张照片,如果想要连续拍照,把下面那行代码注释掉即可。
                    $('.camera-canvas-group').empty()
                    xsCamera.append($('<img>').attr('src',base64URL)).append($('<span></span>').addClass('layui-icon layui-icon-close-circle-fill canvas-item-del'));
                    $('.camera-canvas-group').prepend(xsCamera);
                });
                /*$view.find('#snap').hide();*/
            })
            //清空
            $view.find('#clear').click(function () {
                clearCanvas();
                $view.find('.camera-canvas-group').empty()
            })

            //append方式添加节点直接click无效,点击显示大图
            $view.find('.camera-canvas-group').on('click','.camera-canvas-item img',function () {
                myCamvas.ctx.drawImage(this,0,0,config.video.width,config.video.height)
            })
            //删除图片
            $view.find('.camera-canvas-group').on('click','.camera-canvas-item .canvas-item-del',function () {
                //删除父节点元素
                $(this).closest('.camera-canvas-item').remove()
            })
            //保存所有图片到本地
            $view.find('#save').click(function () {
                let fileName = getFileName();
                let el_a = $('<a>');
                layui.each($view.find('.camera-canvas-group .camera-canvas-item'),function (k,item) {
                    let t_filename = fileName+'_'+k+'.'+config.imgType;
                    // 解析,把照片上传到服务器。并回显到父级窗口
                    downLoad($(this).find('img').attr('src'),t_filename,config.imgType);

                })
            })
            //空格按下拍照
            $(document).keypress(function (e) {
                e.keyCode===32&&$('#snap').trigger('click');
            })
        };
        //tode
        function getFileName() {
            let date = new Date();
            let fileName = ''+date.getFullYear()+(date.getMonth()<9?+'0':'')
                +(date.getMonth()+1) +(date.getDate()<10?+'0':'')+date.getDate()
                +date.getHours() +date.getMinutes()+(date.getSeconds()<10?'0':'')+date.getSeconds();
            return fileName;
        }
        // 文件上传到ftp服务器
        function downLoad(dataURL,fileName,fileType) {
            var reader = new FileReader();
            reader.readAsDataURL(createFile(dataURL));
            reader.onload = function (e) {
                // 转换成base64 进行父窗口的回显
                /*console.log(window.parent.frames.length);*/
                window.parent.frames[window.parent.frames.length-2].document.getElementById("shotPhoto").innerHTML=fileName;
                window.parent.frames[window.parent.frames.length-2].document.getElementById("preShow").setAttribute("src",reader.result);
                //进行文件上传到ftp服务器 ,将blob对象转换成file
                let file = dataURLtoFile(dataURL,fileName);
                var formData = new FormData();
                formData.append("file",file);
                $.ajax({
                    type:'post',
                    url:'你的上传接口地址',
                    data:formData,
                    dataType: "json",
                    processData: false,
                    contentType: false,
                    success:function (res) {
                        //上传失败
                        if(res.code > 0){
                            layer.msg('上传失败',{icon:2,time:1000});
                        }
                        // 上传成功
                        if(res.code == 0){
                            var objFile = JSON.parse(res.data);
                            window.parent.frames[window.parent.frames.length-2].document.getElementById("img_url").setAttribute("value",objFile['filePath']);
                            layer.msg('上传成功',{icon:1,time:1000});
                            setTimeout(function () {
                                $("#closeBtn").click();
                            },1000)

                        }
                    }
                });

        // 解析 BASE64文件内容 for IE,Edge
        function createFile(urlData) {
            var arr = urlData.split(','),
                mime = arr[0].match(/:(.*?);/)[1],
                bstr = window.atob(arr[1]),
                n = bstr.length,
                u8arr = new Uint8Array(n);
            while (n--) {
                u8arr[n] = bstr.charCodeAt(n);
            }
            return new Blob([u8arr], { type: mime });
        }
        // base64 to file
        function dataURLtoFile(dataurl, filename) {//将base64转换为文件
            var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
                bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
            while(n--){
                u8arr[n] = bstr.charCodeAt(n);
            }
            return new File([u8arr], filename, {type:mime});
        }
        function clearCanvas()
        {
            var c=document.getElementById("canvas");
            var cxt=c.getContext("2d");
            cxt.clearRect(0,0,c.width,c.height);
        }
    });
</script>

js文件:camvas.js

/*
实例化camvas配置参数
config = {
                video:{width:Number(scale*4),height:Number(scale*3)},//视频比例4:3
                canvasId:'canvas',//画布canvas节点ID
                videoId:'v',//video节点ID
                imgType:'png',//图片类型,/png|jpeg|bmp|gif/
                quality:'1' //图片质量0-1之间
            }
*/

window.URL = window.URL || window.webkitURL||window.mozURL || window.msURL;

navigator.getUserMedia  = navigator.getUserMedia ||
    navigator.webkitGetUserMedia ||
    navigator.mozGetUserMedia ||
    navigator.msGetUserMedia

window.requestAnimationFrame = window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.msRequestAnimationFrame ||
    window.oRequestAnimationFrame

// Integrate navigator.getUserMedia & navigator.mediaDevices.getUserMedia
function getUserMedia (constraints, successCallback, errorCallback) {
    if (!constraints || !successCallback || !errorCallback) {return}

    if (navigator.mediaDevices) {
        navigator.mediaDevices.getUserMedia(constraints).then(successCallback, errorCallback)
    } else {
        navigator.getUserMedia(constraints, successCallback, errorCallback)
    }
}

//获取摄像头设备源
function getMediaStream() {
    var exArray = []; //存储设备源ID
    MediaStreamTrack.getSources(function (sourceInfos) {
        for (var i = 0; i != sourceInfos.length; ++i) {
            var sourceInfo = sourceInfos[i];
            //这里会遍历audio,video,所以要加以区分
            if (sourceInfo.kind === 'video') {
                exArray.push(sourceInfo.id);
            }
        }
    });
    return exArray;
}

//用户手机端使用后置摄像头
function getMediaConfig() {
    if (navigator.getUserMedia) {
        if(/Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent)){
            //手机端
            return {
                'video':
                    {'optional': [
                            {'sourceId': getMediaStream()[1] //0为前置摄像头,1为后置
                            }]
                    },
                'audio':false
            }
        }else{
            // 下面注释为强制使用后置摄像头
            /*return { 'audio': false, 'video': { 'facingMode': { 'exact': "environment" } } }*/
            return {'video':true,'audio':false}
        }
    }
    else {
        alert('Native device media streaming (getUserMedia) not supported in this browser.');
    }
}

// The function takes a canvas context and a `drawFunc` function.
// `drawFunc` receives two parameters, the video and the time since
// the last time it was called.
function camvas(config) {
    var self = this
    self.convas = document.getElementById(config.canvasId)
    self.ctx = self.convas.getContext('2d');
    self.config = config
    self.isStop = false;

    //video节点ID
    self.video = document.getElementById(self.config.videoId)

    //video 显示尺寸
    self.video.setAttribute('width', this.config.video.width)
    self.video.setAttribute('height', this.config.video.height)

    //视频流控制句柄
    var mediaStreamTrack;
    //对外开启视频方法
    this.startCamera = function () {
        // The callback happens when we are starting to stream the video.
        getUserMedia(getMediaConfig(), function(stream) {
            // Yay, now our webcam input is treated as a normal video and
            // we can start having fun
            try {
                mediaStreamTrack = typeof stream.stop === 'function' ? stream : stream.getTracks().length==1 ?
                    stream.getTracks()[0]:stream.getTracks()[1];
                if(self.video.mozSrcObject !== undefined){
                    //Firefox中,video.mozSrcObject最初为null,而不是未定义的,我们可以靠这个来检测Firefox的支持
                    self.video.mozSrcObject = stream;
                }else{
                    self.video.srcObject = stream;
                }
            } catch (error) {
                self.video.src = window.URL && window.URL.createObjectURL(stream) || stream;
            }
            self.isStop = false;
            self.video.play();
            // Let's start drawing the canvas!
            // self.recordVideo()
        }, function(err){
            alert(err);
        })
    }

    //录像方法
    this.recordVideo = function() {
        var self = this
        var last = Date.now()
        var loop = function() {
            // For some effects, you might want to know how much time is passed
            // since the last frame; that's why we pass along a Delta time `dt`
            // variable (expressed in milliseconds)
            var dt = Date.now() - last
            self.draw(self.video, dt)
            last = Date.now()
            requestAnimationFrame(loop)
        }
        requestAnimationFrame(loop)
    }
    //停止视频
    this.stop = function () {
        self.ctx.clearRect(0, 0, self.config.video.width,self.config.video.height);
        mediaStreamTrack && mediaStreamTrack.stop();
        self.isStop = true;
    }
    //拍照,base64/image/png
    this.drawImage=function (callback) {
        if(!self.isStop){
            self.ctx.drawImage(self.video,0,0,self.config.video.width,self.config.video.height);
            var base64URL = self.convas.toDataURL('image/'+self.config.imgType,self.config.quality);
            callback&&callback(base64URL);
        }
    }

    //录像数据帧
    this.draw = function(video, dt) {
        self.ctx.drawImage(video, 0, 0)
    }
}
  • 2
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值