浏览器中拍照、上传
前不久碰到了这样的需求:在网页中调用摄像头,拍照后上传图片。
实现并不难,可直接参考webRTC给出的demo。但一来网上搜出来的文章都比较早,很多API已不再适用;二来涉及的东西较多,还是值得总结一下。
方法概述
- 通过navigator.mediaDevices.getUserMedia()方法获取摄像头媒体流,用<video>标签展示
- 拍照时借助canvas API中的drawImage()方法截取图片
- 保存或上传,可使用toDataURL()方法或toBlob()方法获取图片
获取媒体流
MediaDevices.getUserMedia()方法可获取媒体流,该方法返回的是一个Promise。
1. 基本用法
navigator.mediaDevices.getUserMedia(constraints)
.then(function(stream) {
  /* use the stream */
}).catch(function(err) {
  /* handle the error */
});
其中参数constraints是一个MediaStreamConstraints对象,其属性audio,video的值可配置为boolean或MediaTrackConstraints对象,默认为false。
具体配置可参考MediaTrackConstraints或直接看标准。可使用navigator.mediaDevices.getSupportedConstraints()方法检测浏览器对这些限制的支持情况。
2. 权限
调用getUserMedia方法时会根据constraints中的配置提示用户授权以使用摄像头、麦克风。
3. 兼容性
之前的API是navigator.getUserMedia(constraints, successCallback, errorCallback)。Chrome和FireFox在早期分别实现了带前缀的方法navigator.webkitGetUserMedia和navigator.mozGetUserMedia。
对于旧版本的浏览器,可使用adapter.js来polyfill。
在Chrome中,调用getUserMedia要求必须使用安全的协议(如https),否则会报错。详情请点这里。
4. 视频显示
需要注意的是,在旧版本的浏览器中<video>并不支持srcObject。1
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上传,通常也会提供File或Blob为参数的接口。
dataURL与Blob的转换
data URI是base64或ASCII编码过的数据3。在本文所述需求的情景下,dataURL与Blob的转换本质上是base64的编码与解码,用到的函数有atob()和btoa()。
下面以dataURL转Blob为例:
由于File或Blob都可由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类的支持。