整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

HTML5 的调用摄像头拍照和获取视频流的方法

着web功能越来越强大,我们很多时候需要在web页面来获取摄像头进行操作,原生html5提供了对摄像头的支持,需要用户的同意授权,下面是一个基于 HTML5 的调用摄像头拍照并上传后台的示例代码:

html复制代码<!DOCTYPE html>
<html>
<head>
    <title>拍照上传</title>
</head>
<body>
    <video id="video" style="width:300px;height:200px;"></video>
    <br>
    <button id="btn-start">启动摄像头</button>
    <button id="btn-stop">停止摄像头</button>
    <button id="btn-capture">拍照上传</button>
    <br>
    <canvas id="canvas"></canvas>
    <form id="form-upload" method="post" enctype="multipart/form-data">
        <input type="file" id="input-file" name="file"/>
    </form>

    <script type="text/javascript">
        var video = document.getElementById('video');
        var canvas = document.getElementById('canvas');
        var ctx = canvas.getContext('2d');
        
        // 启动摄像头
        document.getElementById('btn-start').addEventListener('click', function() {
            navigator.mediaDevices.getUserMedia({
                video: true,
                audio: false
            }).then(function(stream) {
                video.srcObject = stream;
            }).catch(function(err) {
                console.log("启动摄像头失败:" + err);
            });
        });

        // 停止摄像头
        document.getElementById('btn-stop').addEventListener('click', function() {
            video.pause();
            video.srcObject.getTracks()[0].stop();
            video.srcObject = null;
        });

        // 拍照,并上传到后台
        document.getElementById('btn-capture').addEventListener('click', function() {
            canvas.width = video.videoWidth;
            canvas.height = video.videoHeight;
            ctx.drawImage(video, 0, 0);
            canvas.toBlob(function(blob) {
                var formData = new FormData();
                formData.append('file', blob, 'photo.jpg');
                postRequest('/upload', formData, function(res) {
                    alert(res.message);
                });
            }, 'image/jpeg');
        });

        // 发送 POST 请求
        function postRequest(url, data, callback) {
            var xhr = new XMLHttpRequest();
            xhr.open('POST', url, true);
            xhr.onreadystatechange = function() {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    var res = JSON.parse(xhr.responseText);
                    callback(res);
                }
            };
            xhr.send(data);
        }
    </script>
</body>
</html>

上述代码主要分为以下几个部分:

  1. html5 的 <video> 元素用于显示摄像头数据流,我们可以通过 JavaScript API 控制调用摄像头、暂停和关闭。同时使用了 <canvas> 元素来缓存视频帧的图像数据。
  2. 页面中 btn-start 按钮通过 getUserMedia() 方法请求浏览器授权,并将摄像头数据流传给 <video> 元素进行播放。
  3. 点击 btn-capture 按钮时,通过 toBlob() 将缓存的图像数据转化为 Blob 对象,并封装到 FormData 中,以便发送给后台服务。
  4. postRequest() 函数用于发送包含文件数据的 POST 请求,其中 FormData 对象中的 key 是上传后台服务器处理时需要读取的参数名称,通常对应具体的后台业务逻辑。

最后,需要注意的是,在本地调试和开发时,特别是在 Windows 操作系统下使用 Chrome 浏览器访问时,可能会遇到摄像头不能正常运行的情况。这时可以打开地址栏,在目标请求前加上 --unsafely-treat-insecure-origin-as-secure="http://localhost:8080" 参数(其中端口号需替换成实际的本地服务端口),即可获得权限,进行摄像头使用。

基于Electron实现的pc端智能验机应用,近期迭代了一个新的功能,需求是通过电脑外接摄像头对手机屏幕进行拍照,拍照后需将照片上传至服务端进行屏幕信息比对,确定被检测屏幕是否为原厂屏。

需求分析

根据上面的需求,分析大概要以下几个步骤。

  1. 先实现将摄像头的画面实时展示在页面视频采集区域中;
  2. 将摄像头中的视频画面采集一帧成图片并回显;
  3. 将生成的图片上传至CDN拿到图片链接;
  4. 将图片链接上传到后端接口做处理;

确定了需要以上四个步骤后,接下来一步一步实现。

实现

视频采集

由于 Electron 内置了 Chromium 浏览器,该浏览器对各项前端标准都支持得非常好,所以基于 Electron 开发应用不会遇到浏览器兼容性问题。几乎可以在 Electron 中使用所有 HTML5CSS3ES6 标准中定义的 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 对象。若找不到满足请求参数的媒体类型,promisereject回调一个NotFoundError

现在已经成功获取到视频流,接下来就是将视频流回显到页面。这里使用video标签完成,代码如下:

<template>
    <div class="video-page">
        <div class="video-content">
            <video ref="video" class="video-item"></video>
        </div>
    </div>
</template>

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类型有三种,分别是audioinputaudiooutputvideoinput。分别代表音视频的输入和输出。可在列表中查找目标媒体是否已经接入且可用。

若有选择切换设备需求,可根据kind类型进行媒体设备分类,选择目标deviceId,传入navigator.mediaDevices.getUserMedia,完成来源切换。

 navigator.mediaDevices.getUserMedia({ video: { deviceId: xxxx } })

拍照生成图片

拍照其实就是截取视频中的某一帧,这里使用canvas来实现截取。getContext() 方法可返回一个对象,该对象提供了用于在画布上绘图的方法和属性。其中drawImage()方法用来向画布上绘制图像、画布或视频。

<template>
    <div class="video-page">
        <div class="video-content">
            <video ref="video" class="video-item" v-if="showVideo"></video>
            <canvas ref="canvas" v-else width="500" height="346"></canvas>
        <div class="video-buttons">
            <div @click="capture" class="button-item capture">拍照</div>
            <div @click="submit" class="button-item submit"}">提交</div>
        </div>
    </div>
</template>

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()方法返回一个包含图片展示的Data URL。

Data URL,即前缀为 data: 协议的 URL,其允许内容创建者向文档中嵌入小文件。

语法

canvas.toDataURL(type, encoderOptions);

参数

type 图片格式,默认为image/png

encoderOptions 0到1之间的值,用来选定图片质量,默认值是0.92,超出范围会使用默认值。

返回值

base64组成的图片源数据,上传前需转为图片文件。这里封装了一个convertBase64UrlToImgFile函数用来转换。代码如下:

<template>
    <div class="video-page">
        <div class="video-content">
            <video ref="video" class="video-item" v-if="showVideo"></video>
            <canvas ref="canvas" v-else width="500" height="346"></canvas>
        <div class="video-buttons">
            <div @click="capture" class="button-item capture">拍照</div>
            <div @click="submit" class="button-item submit">提交</div>
        </div>
    </div>
</template>

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 < bytes.length; i++) {
                ia[i] = bytes.charCodeAt(i)
            }
            
            /* 转换成文件,可以添加文件的type,lastModifiedDate属性 */
            const blob = new Blob([ab], { type: fileType })
            blob.lastModifiedDate = new Date()
            return blob
        },
        /* 提交按钮点击 */
        async submit() {
            const base64Url = this.$refs.canvas.toDataURL()
            const imgFile = this.convertBase64UrlToImgFile(base64Url, 'image/jpg')
            console.log('====imgFile', imgFile)
            const data = new FormData()
            data.append('file', imgFile)
            /* 上传 */
            request.post('https://XXXXX/upload', data)
        },
    }
}

convertBase64UrlToImgFile可用于在使用canvas外的场景进行base64转换图片文件。和HTMLCanvasElement.toBlob()方法得到的结果一致。

以上两种方法都可以完成图片上传,最终拿到CDN图片链接后可传给后端进行处理。获取屏幕信息。

总结

通过以上四个步骤就完成了Electron应用中通过外接摄像头拍照并上传的功能。这里基本用不到Electron的能力,和在web端的实现方式并无区别,Electron在这里起到的作用就是获取摄像头媒体流不需要获取用户权限。

Electron是基于ChromiumNode.js实现的,这就使前端开发者可以使用JavaScriptHTMLCSS轻松构建跨平台的桌面应用。Electron可以使用几乎所有的Web前端生态领域及Node.js生态领域的组件和技术方案。

后续会介绍Electron在智能验机应用中的实践方案,敬请期待~

作者:谢星

来源:微信公众号:大转转FE
出处:https://mp.weixin.qq.com/s/Yb590oc6u94feS9s7FZWrQ

天在博客中加了个新的功能,直接复制图片然后黏贴上传到服务器。

之前是自己弄的markdown编辑器,很多功能不完善,刚开始加了个插入图片的按钮,现在能直接复制图片方便多了。

目前只支持chrome浏览器,哈哈,感觉chrome是最好用的浏览器。

主要原理是利用paste事件,然后拿到里面的图片数据上传到服务器。

下面贴代码

<textarea id="content" />

有个编辑的文本框,然后监听parse事件

document.querySelector('#content').addEventListener('paste', function(event) {

var isChrome = false;

if (event.clipboardData || event.originalEvent) {

//not for ie11 某些chrome版本使用的是event.originalEvent

var clipboardData = (event.clipboardData || event.originalEvent.clipboardData);

if (clipboardData.items) {

// for chrome

var items = clipboardData.items, len = items.length, blob = null;

isChrome = true;

for (var i = 0; i < len; i++) {

if (items[i].type.indexOf("image") !== -1) {

//getAsFile() 此方法只是living standard firefox ie11 并不支持

blob = items[i].getAsFile();

}