浏览器中拍照、上传

  • camera
  • getUserMedia

前不久碰到了这样的需求:在网页中调用摄像头,拍照后上传图片。

实现并不难,可直接参考webRTC给出的demo。但一来网上搜出来的文章都比较早,很多API已不再适用;二来涉及的东西较多,还是值得总结一下。

方法概述

获取媒体流

MediaDevices.getUserMedia()方法可获取媒体流,该方法返回的是一个Promise

1. 基本用法

navigator.mediaDevices.getUserMedia(constraints)
.then(function(stream) {
  /* use the stream */
}).catch(function(err) {
  /* handle the error */
});

其中参数constraints是一个MediaStreamConstraints对象,其属性audio,video的值可配置为booleanMediaTrackConstraints对象,默认为false

具体配置可参考MediaTrackConstraints或直接看标准。可使用navigator.mediaDevices.getSupportedConstraints()方法检测浏览器对这些限制的支持情况。

2. 权限

调用getUserMedia方法时会根据constraints中的配置提示用户授权以使用摄像头、麦克风。

3. 兼容性

之前的API是navigator.getUserMedia(constraints, successCallback, errorCallback)。Chrome和FireFox在早期分别实现了带前缀的方法navigator.webkitGetUserMedianavigator.mozGetUserMedia

对于旧版本的浏览器,可使用adapter.js来polyfill。

在Chrome中,调用getUserMedia要求必须使用安全的协议(如https),否则会报错。详情请点这里

4. 视频显示

需要注意的是,在旧版本的浏览器中<video>并不支持srcObject1

var video = document.querySelector('video');
// Older browsers may not have srcObject
if ("srcObject" in video) {
    video.srcObject = stream;
} else {
    // Avoid using this in new browsers, as it is going away.
    video.src = window.URL.createObjectURL(stream);
}

拍照

截取视频的当前帧,显示在canvas上。

var video = document.querySelector('video');
var button = document.querySelector('button');
button.onclick = function() {
    canvas.width = video.videoWidth;
    canvas.height = video.videoHeight;
    canvas.getContext('2d').
        drawImage(video, 0, 0, canvas.width, canvas.height);
};

图片上传

canvas提供了toDataURL()toBlob()两个方法来获取其内容。2

toDataURL

该方法返回的是一个data URI。默认格式为image/png,质量为0.92。

var dataURL = canvas.toDataURL();

toBlob

该方法需要在回调中获取Blob对象。

canvas.toBlob(function(blob){...}, 'image/jpeg', 0.95); // JPEG at 95% quality

可以通过toDataURL()方法获取Blob对象,或构造File对象,通过FormData上传。对于第三方SDK上传,通常也会提供FileBlob为参数的接口。

dataURL与Blob的转换

data URI是base64ASCII编码过的数据3。在本文所述需求的情景下,dataURL与Blob的转换本质上是base64的编码与解码,用到的函数有atob()btoa()

下面以dataURL转Blob为例:

由于FileBlob都可由ArrayBuffer构造,因此只需将data URI字符串转化为ArrayBuffer对象。ArrayBuffer有不同的数据格式视图,统称为TypedArray,下面的方法选择的是Uint8Array(无符号8位整数数组)。4

function dataURItoArrayBuffer(dataURI) {
    var byteString = atob(dataURI.split(',')[1]);
    var bufferArray = new Uint8Array(byteString.length);
    for (var i = 0; i < byteString.length; i++) {
        bufferArray[i] = byteString.charCodeAt(i);
    }
    return bufferArray;
}

事实上,Node.js中的Buffer类即实现了Uint8Array的API,可以通过Buffer.from()方法来构造。浏览器中也可通过引用buffer实现对Buffer类的支持。

参考链接

  1. caniuse.com中getUserMedia的注解 

  2. canvas element 

  3. The “data” URL scheme 

  4. 关于JavaScript操作二进制数据的接口,建议阅读这篇文章