以 HTML5 格式录制音频和视频

简介

长期以来,音频/视频捕获一直是 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 的权限对话框:

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;
}