以 HTML5 格式录制音频和视频

简介

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

这种情况下就轮到 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 的权限对话框:

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