以 HTML5 格式录制音频和视频
长久以来,音频/视频捕获一直是 Web 开发的“圣杯”。多年来,我们一直依赖于浏览器插件(Flash 或 Silverlight)来完成工作。来吧!
这种情况下就轮到 HTML5 大显身手了。这可能不是显而易见的,但 HTML5 的兴起引发了对设备硬件访问的激增。地理定位 (GPS)、Orientation API(加速度计)、WebGL (GPU) 和 Web Audio API(音频硬件)都是理想的示例。这些功能非常强大,公开了基于系统底层硬件功能之上的高级 JavaScript API。
本教程介绍了一个新 API GetUserMedia,可让 Web 应用访问用户的摄像头和麦克风。
getUserMedia() 之路
如果您还不了解 getUserMedia()
API 的历史,那么我们就是一个有趣的故事。
在过去几年中,“Media Capture API”的几个变体已经发生了变化。许多人意识到需要能够在网络上访问原生设备,但这促使所有人与他们的妈妈共同制定了一个新的规范。局面一片混乱,以至于 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 浏览器 – 首批实现之一。请观看此视频,了解实际运作方式。
- Android 版 Chrome (0.16)
- Firefox 移动 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 的网关。它提供了访问用户本地摄像头/麦克风流的方法。
支持:
从 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;
}