简介
音频/视频捕获功能长期以来一直是 Web 开发的“圣杯”。多年来,我们一直依赖浏览器插件(Flash 或 Silverlight)来完成此任务。快来!
此时,HTML5 可以派上用场。这可能并不明显,但 HTML5 的兴起确实带来了对设备硬件的大量访问。Geolocation API(GPS)、Orientation API(加速度计)、WebGL(GPU)和 Web Audio API(音频硬件)就是很好的例子。这些功能非常强大,可公开基于系统底层硬件功能的高级 JavaScript API。
本教程介绍了新 API GetUserMedia,该 API 可让 Web 应用访问用户的相机和麦克风。
使用 getUserMedia() 的途径
如果您不了解 getUserMedia()
API 的历史,不妨了解一下我们开发 getUserMedia()
API 的历程,这是一个有趣的故事。
过去几年,“Media Capture API”的多个变体不断演变。许多人认识到需要能够在 Web 上访问原生设备,但这导致所有人都在制定新的规范。情况变得如此混乱,以至于 W3C 最终决定成立一个工作组。其唯一用途? 了解这场疯狂的竞赛!设备 API 政策 (DAP) 工作组负责整合和标准化这些众多提案。
我会尝试总结一下 2011 年发生的情况…
第 1 轮:HTML 媒体捕获
HTML Media Capture 是 DAP 首次尝试在 Web 上标准化媒体捕获功能。其工作原理是重载 <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 Media Capture 仅允许您及时录制媒体文件或拍摄快照。
支持:
- Android 3.0 浏览器 - 最早的实现之一。观看此视频,了解其运作方式。
- Android 版 Chrome (0.16)
- Firefox Mobile 10.0
- iOS6 Safari 和 Chrome(部分支持)
第 2 轮:设备元素
许多人认为 HTML Media Capture 限制太多,因此出现了一种支持任何类型(未来)设备的新规范。毫不奇怪,该设计需要一个新元素,即 <device>
元素,该元素后来成为 getUserMedia()
的前身。
Opera 是首批基于 <device>
元素创建视频截取初始实现的浏览器之一。不久之后(确切地说是同一天),WhatWG 决定弃用 <device>
标记,改用另一个新兴的 JavaScript API,这次是名为 navigator.getUserMedia()
的 API。一周后,Opera 发布了支持更新后的 getUserMedia()
规范的新 build。同年晚些时候,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(Web Real Time Communications)的广泛使用,我们在寻找合适的捕获 API 方面取得了更大的进展。该规范由 W3C WebRTC 工作组监督。Google、Opera、Mozilla 和其他一些浏览器都已实现该功能。
getUserMedia()
与 WebRTC 相关,因为它是进入这组 API 的网关。它提供了访问用户本地摄像头/麦克风数据流的方法。
支持:
从 Chrome 21、Opera 18 和 Firefox 17 开始,系统支持 getUserMedia()
。
使用入门
借助 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);
如需了解更多配置,请参阅 constraints 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;
}