找回密码
 会员注册
查看: 23|回复: 0

Electron应用中实现调用外接摄像头并拍照上传

[复制链接]

6

主题

0

回帖

19

积分

新手上路

积分
19
发表于 2024-9-19 16:03:46 | 显示全部楼层 |阅读模式
背景基于Electron实现的pc端智能验机应用,近期迭代了一个新的功能,需求是通过电脑外接摄像头对手机屏幕进行拍照,拍照后需将照片上传至服务端进行屏幕信息比对,确定被检测屏幕是否为原厂屏。需求分析根据上面的需求,分析大概要以下几个步骤。先实现将摄像头的画面实时展示在页面视频采集区域中;将摄像头中的视频画面采集一帧成图片并回显;将生成的图片上传至CDN拿到图片链接;将图片链接上传到后端接口做处理;确定了需要以上四个步骤后,接下来一步一步实现。实现视频采集由于Electron内置了Chromium浏览器,该浏览器对各项前端标准都支持得非常好,所以基于Electron开发应用不会遇到浏览器兼容性问题。几乎可以在Electron中使用所有HTML5、CSS3、ES6标准中定义的API。所以基于WebRTC提供的API即可获取到摄像头的视频流。MediaDevices.getUserMedia()代码如下:methods: {    getUserMedia() {        /* 可同时开启video(摄像头)和audio(麦克风) 这里只请求摄像头,所以只设置video为true */        navigator.mediaDevices.getUserMedia({ video: true })            .then(function(stream) {              /* 使用这个 stream 传递到成功回调中 */              this.success(stream)            })            .catch(function(err) {              /* 处理 error 信息 */              this.error(error)            });    }}MediaDevices.getUserMedia()会提示用户给予使用媒体输入的许可,媒体输入会产生一个MediaStream,里面包含了请求的媒体类型的轨道。此流可以包含一个视频轨道(来自硬件或者虚拟视频源,比如相机、视频采集设备和屏幕共享服务等等)、一个音频轨道(同样来自硬件或虚拟音频源,比如麦克风、A/D转换器等等),也可能是其它轨道类型。它返回一个Promise对象,成功后会resolve回调一个MediaStream对象。若找不到满足请求参数的媒体类型,promise会reject回调一个NotFoundError。现在已经成功获取到视频流,接下来就是将视频流回显到页面。这里使用video标签完成,代码如下:                                    export default {   methods: {       getUserMedia() {            /* 可同时开启video(摄像头)和audio(麦克风) 这里只请求摄像头,所以只设置video为true */            navigator.mediaDevices.getUserMedia({ video: true })                .then(function(stream) {                  /* 使用这个 stream 传递到成功回调中 */                  this.success(stream)                })                .catch(function(err) {                  /* 处理 error 信息 */                  this.error(error)                });        },       success(stream) {           console.log('成功', stream);           /* 将stream 分配给video标签 */           this.$refs.video.srcObject = stream;           this.$refs.video.play();        }    }}这时,摄像头中的画面就可以显示在页面video标签内,如下图。为了用户体验,在进入页面之前添加了判断摄像头是否已经接入并可用的逻辑,避免用户的摄像头未接入或者启动,造成应用不可用的错觉。使用MediaDevices.enumerateDevices()来获取可用媒体输入和输出设备的列表,例如摄像头、麦克风、耳机等。navigator.mediaDevices.enumerateDevices().then(devicesList => {    console.log('------devicesList', deviceList)})得到的设备列表数据格式如下:kind类型有三种,分别是audioinput、audiooutput和videoinput。分别代表音视频的输入和输出。可在列表中查找目标媒体是否已经接入且可用。若有选择切换设备需求,可根据kind类型进行媒体设备分类,选择目标deviceId,传入navigator.mediaDevices.getUserMedia,完成来源切换。 navigator.mediaDevices.getUserMedia({ video: { deviceId: xxxx } })拍照生成图片拍照其实就是截取视频中的某一帧,这里使用canvas来实现截取。getContext()方法可返回一个对象,该对象提供了用于在画布上绘图的方法和属性。其中drawImage()方法用来向画布上绘制图像、画布或视频。                                                        拍照            提交            export default {   data: {       showVideo: true, // 是否展示摄像头画面   },   methods: {        /* 拍照按钮点击 */        capture() {          this.showVideo = false          var context = this.$refs.canvas.getContext('2d');          /* 要跟video的宽高一致 */          context.drawImage(this.$refs.video, 0, 0, 1000, 692, 0, 0, 500, 346);        }    }}拍照的图片回显至canvas标签。上传图片至CDN上个步骤已经完成了拍照,接下来就需要将图片上传至CDN,拿到图片链接。这里有两种方式可以实现获取图片数据。1.使用HTMLCanvasElement.toBlob()HTMLCanvasElement.toBlob()方法生成Blob对象,用以展示canvas上的图片。因为直接可以拿到图片文件,所以无需再使用方法2中的函数来转化base64,直接可以获取到图片文件用来上传。语法toBlob(callback, type, quality)参数callback:回调函数,参数为Blob对象(目标图片文件)。type:图片格式,默认为image/png可选。quality:0-1的数字,表示图片质量,可选。点击提交按钮按钮时,先获取图片文件,为上传做准备。methods: {    /* 提交按钮点击 */    submit() {        const base64Url = this.$refs.canvas.toBlob(blob => {            console.log('===blob', blob)            const data = new FormData()            data.append('file', blob)            request.post('https://XXXXX/upload', data)        }, "image/jpeg", 0.95)    }}console的结果如下图:2.使用HTMLCanvasElement.toDataURL()HTMLCanvasElement.toDataURL()方法返回一个包含图片展示的DataURL。DataURL,即前缀为data:协议的URL,其允许内容创建者向文档中嵌入小文件。语法canvas.toDataURL(type, encoderOptions);参数type图片格式,默认为image/png。encoderOptions0到1之间的值,用来选定图片质量,默认值是0.92,超出范围会使用默认值。返回值base64组成的图片源数据,上传前需转为图片文件。这里封装了一个convertBase64UrlToImgFile函数用来转换。代码如下:                                                        拍照            提交            export default {   data: {       /* 是否展示摄像头画面 */       showVideo: true,   },   methods: {        /* 将base64转为图片文件 */        convertBase64UrlToImgFile(urlData, fileType) {            const imgData = urlData.split('base64,').splice(-1)[0]            /* 解码使用 base-64 编码的字符串 转换为byte */            const bytes = window.atob(imgData)                        /* 处理异常,将ASCII码小于0的转换为大于0 */            const ab = new ArrayBuffer(bytes.length)            const ia = new Int8Array(ab)                        for (let i = 0; i 
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-26 13:16 , Processed in 0.345609 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表