以 HTML5 格式录制音频和视频

长期以来,音频/视频捕获一直是 Web 开发中的“圣杯”。多年来,我们一直依赖浏览器插件(FlashSilverlight)来完成此任务。来吧!

这种情况下就轮到 HTML5 大显身手了。这种现象可能并不明显,但 HTML5 的兴起引发了对设备硬件访问的激增。Geolocation API(GPS)、Orientation API(加速度计)、WebGL(GPU)和 Web Audio API(音频硬件)就是很好的例子。这些功能非常强大,可公开基于系统底层硬件功能的高级 JavaScript API。

本教程引入了一个新的 API,即 GetUserMedia,它允许 Web 应用访问用户的摄像头和麦克风。

如果您不了解 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 的权限对话框:

Chrome 中的权限对话框
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;
}