简介
长期以来,音频/视频捕获一直是 Web 开发的“圣杯”。 多年来,我们不得不依赖浏览器插件(Flash 或 Silverlight)来完成这项工作。Come on!
HTML5 闪亮登场。虽然可能不太明显,但 HTML5 的兴起带来了对设备硬件的访问激增。地理定位 (GPS)、Orientation API(加速度计)、WebGL (GPU) 和 Web Audio API(音频硬件)就是很好的例子。这些功能非常强大,它们公开了位于系统底层硬件功能之上的高级 JavaScript API。
本教程介绍了一个新的 API:GetUserMedia,它允许 Web 应用访问用户的摄像头和麦克风。
getUserMedia() 的发展历程
如果您不了解其历史,那么我们推出 getUserMedia() API 的过程是一个有趣的故事。
过去几年中,“媒体捕获 API”的多个变体不断演变。 许多人都认识到需要能够在 Web 上访问原生设备,但这导致每个人和他们的母亲都制定了新的规范。事情变得如此混乱,以至于 W3C 最终决定成立一个工作组。他们的唯一目的? 理清混乱局面!设备 API 政策 (DAP) 工作组的任务是整合和标准化大量提案。
我来试着总结一下 2011 年发生的事情…
第 1 轮:HTML 媒体捕获
HTML 媒体捕获是 DAP 首次尝试在网络上标准化媒体捕获。其工作原理是重载 <input type="file"> 并为 accept 参数添加新值。
如果您想让用户使用摄像头拍摄自己的快照,可以使用 capture=camera 实现:
<input type="file" accept="image/*;capture=camera">
录制视频或音频的操作类似:
<input type="file" accept="video/*;capture=camcorder">
<input type="file" accept="audio/*;capture=microphone">
是不是很棒?我特别喜欢它能重复使用文件输入。从语义上讲,这很有道理。这个特定“API”的不足之处在于无法实现实时效果(例如,将实时摄像头数据渲染到 <canvas> 并应用 WebGL 滤镜)。HTML 媒体捕获功能仅允许您录制媒体文件或及时拍摄快照。
支持:
- Android 3.0 浏览器 - 首批实现之一。观看此视频,了解其运作方式。
- Chrome(Android 版)(0.16)
- Firefox Mobile 10.0
- iOS6 Safari 和 Chrome(部分支持)
第 2 轮:设备元素
许多人认为 HTML 媒体捕获功能限制太多,因此出现了一种支持任何类型(未来)设备的新规范。不出所料,该设计需要一个新元素,即 <device> 元素,它成为了 getUserMedia() 的前身。
Opera 是最早基于 <device> 元素创建视频捕获初始实现的浏览器之一。不久之后(确切地说是同一天),WhatWG 决定废弃 <device> 标记,转而采用另一个后起之秀,这次是一个名为 navigator.getUserMedia() 的 JavaScript API。一周后,Opera 发布了新 build,其中包含对更新后的 getUserMedia() 规范的支持。同年晚些时候,Microsoft 也加入了这一行列,发布了支持新规范的 IE9 实验室。
<device> 如下所示:
<device type="media" onchange="update(this.data)"></device>
<video autoplay></video>
<script>
function update(stream) {
document.querySelector('video').src = stream.url;
}
</script>
支持:
很遗憾,任何已发布的浏览器都未包含 <device>。
我想,这下少了一个需要担心的 API :) 不过,<device> 确实有两个优点:1.) 它是语义化的;2.) 它很容易扩展,不仅支持音频/视频设备,还支持更多设备。
放轻松。这些技术发展得太快了!
第 3 轮:WebRTC
<device> 元素最终被淘汰。
由于 WebRTC(网络实时通信)工作量较大,寻找合适捕获 API 的步伐加快了。该规范由 W3C WebRTC 工作组负责监督。Google、Opera、Mozilla 和其他一些公司都有实现。
getUserMedia() 与 WebRTC 相关,因为它是该组 API 的网关。它提供了访问用户本地摄像头/麦克风数据流的方法。
支持:
getUserMedia() 从 Chrome 21、Opera 18 和 Firefox 17 开始受支持。
使用入门
借助 navigator.mediaDevices.getUserMedia(),我们终于可以在不使用插件的情况下利用网络摄像头和麦克风输入。
现在,您只需拨打电话即可获得摄像头访问权限,而无需安装任何应用。它直接内置于浏览器中。是不是很期待?
功能检测
功能检测是对 navigator.mediaDevices.getUserMedia 是否存在的简单检查:
if (navigator.mediaDevices?.getUserMedia) {
// Good to go!
} else {
alert("navigator.mediaDevices.getUserMedia() is not supported");
}
获取对输入设备的访问权限
如需使用摄像头或麦克风,我们需要请求权限。
navigator.mediaDevices.getUserMedia() 的第一个参数是一个对象,用于指定您要访问的每种媒体类型的详细信息和要求。例如,如果您想访问摄像头,则第一个参数应为 {video: true}。如需同时使用麦克风和摄像头,请传递 {video: true, audio: true}:
<video autoplay></video>
<script>
navigator.mediaDevices
.getUserMedia({ video: true, audio: true })
.then((localMediaStream) => {
const video = document.querySelector("video");
video.srcObject = localMediaStream;
})
.catch((error) => {
console.log("Rejected!", error);
});
</script>
好的。那么,这里发生了什么情况?媒体捕获是新 HTML5 API 协同工作的完美示例。它可与我们的其他 HTML5 伙伴(<audio> 和 <video>)搭配使用。
请注意,我们没有在 <video> 元素上设置 src 属性或添加 <source> 元素。我们不向视频提供媒体文件的网址,而是将 srcObject 设置为表示摄像头的 LocalMediaStream 对象。
我还指示 <video> autoplay,否则它会冻结在第一帧。添加 controls 也能按预期运行。
设置媒体限制(分辨率、高度、宽度)
getUserMedia() 的第一个参数还可用于对返回的媒体流指定更多要求(或限制)。例如,除了指明您希望获得对视频的基本访问权限(例如 {video: true})之外,您还可以额外要求视频流为高清:
const hdConstraints = {
video: { width: { exact: 1280} , height: { exact: 720 } },
};
const stream = await navigator.mediaDevices.getUserMedia(hdConstraints);
const vgaConstraints = {
video: { width: { exact: 640} , height: { exact: 360 } },
};
const stream = await navigator.mediaDevices.getUserMedia(hdConstraints);
如需了解更多配置,请参阅约束条件 API。
选择媒体来源
MediaDevices 接口的 enumerateDevices() 方法会请求可用媒体输入和输出设备的列表,例如麦克风、摄像头、耳机等。返回的 Promise 会通过描述设备的 MediaDeviceInfo 对象数组进行解析。
在此示例中,系统会选择找到的最后一个麦克风和摄像头作为媒体流来源:
if (!navigator.mediaDevices?.enumerateDevices) {
console.log("enumerateDevices() not supported.");
} else {
// List cameras and microphones.
navigator.mediaDevices
.enumerateDevices()
.then((devices) => {
let audioSource = null;
let videoSource = null;
devices.forEach((device) => {
if (device.kind === "audioinput") {
audioSource = device.deviceId;
} else if (device.kind === "videoinput") {
videoSource = device.deviceId;
}
});
sourceSelected(audioSource, videoSource);
})
.catch((err) => {
console.error(`${err.name}: ${err.message}`);
});
}
async function sourceSelected(audioSource, videoSource) {
const constraints = {
audio: { deviceId: audioSource },
video: { deviceId: videoSource },
};
const stream = await navigator.mediaDevices.getUserMedia(constraints);
}
请查看 Sam Dutton 的精彩演示,了解如何让用户选择媒体来源。
安全
浏览器会在调用 navigator.mediaDevices.getUserMedia() 时显示权限对话框,让用户可以选择授予或拒绝访问其摄像头/麦克风。例如,以下是 Chrome 的权限对话框:
提供后备
对于不支持 navigator.mediaDevices.getUserMedia() 的用户,如果 API 不受支持和/或调用因某种原因而失败,一种选择是回退到现有视频文件:
if (!navigator.mediaDevices?.getUserMedia) {
video.src = "fallbackvideo.webm";
} else {
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
video.srcObject = stream;
}