使用 MediaRecorder API 轻松捕获音频

MediaRecorder API 是一种简单的构造,用于 Navigator.getUserMedia() 内部,它提供了一种简单的方法来记录用户输入设备的媒体流,并立即在 Web 应用中使用它们。本文提供了有关如何使用 MediaRecorder 的基本指南,MediaRecorder 在 Firefox 桌面/移动版 25 和 Firefox OS 2.0 中受支持。

还有哪些其他选项可用?

在 Firefox OS 上捕获媒体并不像你想象的那么简单。仅使用 getUserMedia() 会产生原始 PCM 数据,这对于流来说是可以的,但如果你想捕获一些音频或视频,你必须开始对 PCM 数据执行手动编码操作,这很快就会变得很复杂。

然后你还有 Firefox OS 上的 Camera API,直到最近它还是一个经过认证的 API,但最近已降级为特权 API。

Web 活动 也可用于通过其他应用程序(如相机)获取媒体。

最后这两个选项唯一的麻烦是它们只会捕获带有音频轨道的视频,如果你只需要音频轨道,你仍然需要将音频分离出来。MediaRecorder 提供了一种简单的方法来仅捕获音频(视频将在稍后提供 - 目前它_仅_是音频)。

示例应用程序:Web 录音机

An image of the Web dictaphone sample app - a sine wave sound visualization, then record and stop buttons, then an audio jukebox of recorded tracks that can be played back.

为了演示 MediaRecorder API 的基本用法,我们构建了一个基于 Web 的录音机。它允许你录制音频片段,然后播放它们。它甚至使用 Web Audio API 提供了设备声音输入的可视化。在本文中,我们将重点关注录制和播放功能。

你可以看到这个 实时运行的演示,或者在 Github 上 获取源代码直接 zip 文件下载)。

CSS 技巧

此应用程序中的 HTML 非常简单,因此我们不会在这里详细介绍;但是,有一些稍微更有趣的 CSS 部分值得一提,因此我们将在下面讨论它们。如果你对 CSS 不感兴趣,并且想直接进入 JavaScript,请跳到“基本应用程序设置”部分。

使用 calc() 将界面限制在视口内,无论设备高度如何

calc 函数 是 CSS 中那些有用的实用程序功能之一,最初看起来并不起眼,但很快就会让你想到“哇,我们之前为什么没有这个?为什么 CSS2 布局如此笨拙?”它允许你执行计算以确定 CSS 单位的计算值,并在过程中混合不同的单位。

例如,在 Web 录音机中,我们有三个主要的 UI 区域,垂直堆叠。我们希望为前两个(标题和控件)设置固定高度。

header {
  height: 70px;
}

.main-controls {
  padding-bottom: 0.7rem;
  height: 170px;
}

但是,我们希望使第三个区域(包含你可以回放的已录制样本)占用剩余的空间,无论设备高度如何。Flexbox 可以在这里解决问题,但对于如此简单的布局来说有点矫枉过正。相反,通过使第三个容器的高度等于父容器的高度减去其他两个容器的高度和填充来解决问题。

.sound-clips {
  box-shadow: inset 0 3px 4px rgba(0,0,0,0.7);
  background-color: rgba(0,0,0,0.1);
  height: calc(100% - 240px - 0.7rem);
  overflow: scroll;
}

注意calc() 在现代浏览器中也得到了很好的支持,甚至可以追溯到 Internet Explorer 9。

用于显示/隐藏的复选框技巧

这已经得到了很好的记录,但我们认为我们应该提一下复选框技巧,它滥用了这样一个事实:你可以点击复选框的 <label> 来切换其选中/未选中状态。在 Web 录音机中,这为“信息”屏幕提供了动力,可以通过点击右上角的问号图标来显示/隐藏该屏幕。首先,我们根据需要设置 <label> 的样式,确保它具有足够的 z-index 以始终位于其他元素之上,因此可以聚焦/点击。

label {
    font-family: 'NotoColorEmoji';
    font-size: 3rem;
    position: absolute;
    top: 2px;
    right: 3px;
    z-index: 5;
    cursor: pointer;
}

然后我们隐藏实际的复选框,因为我们不希望它弄乱我们的 UI。

input[type=checkbox] {
   position: absolute;
   top: -100px;
}

接下来,我们根据需要设置“信息”屏幕(包装在 <aside> 元素中)的样式,为其提供固定位置,以便它不会出现在布局流中并影响主 UI,将其转换为我们希望它默认位于的位置,并为其提供平滑显示/隐藏的过渡。

aside {
   position: fixed;
   top: 0;
   left: 0;
   text-shadow: 1px 1px 1px black;
   width: 100%;
   height: 100%;
   transform: translateX(100%);
   transition: 0.6s all;
   background-color: #999;
    background-image: linear-gradient(to top right, rgba(0,0,0,0), rgba(0,0,0,0.5));
}

最后,我们编写一条规则,说明当复选框被选中时(当我们点击/聚焦标签时),相邻的 <aside> 元素将更改其水平平移值并平滑地过渡到视图中。

input[type=checkbox]:checked ~ aside {
  transform: translateX(0);
}

基本应用程序设置

为了获取我们想要捕获的媒体流,我们使用 getUserMedia()(简称 gUM)。然后,我们使用 MediaRecorder API 记录流,并将每个录制的片段输出到生成的 <audio> 元素的源中,以便可以将其回放。

首先,我们将添加一个分叉机制,使 gUM 无论浏览器前缀如何都能工作,以便将来在其他浏览器开始支持 MediaRecorder 后,使应用程序在其他浏览器上运行变得更容易。

navigator.getUserMedia = ( navigator.getUserMedia ||
                       navigator.webkitGetUserMedia ||
                       navigator.mozGetUserMedia ||
                       navigator.msGetUserMedia);

然后,我们将声明一些用于录制和停止按钮的变量,以及将包含生成的音频播放器的 <article>

var record = document.querySelector('.record');
var stop = document.querySelector('.stop');
var soundClips = document.querySelector('.sound-clips');

最后,在本节中,我们将设置基本的 gUM 结构。

if (navigator.getUserMedia) {
   console.log('getUserMedia supported.');
   navigator.getUserMedia (
      // constraints - only audio needed for this app
      {
         audio: true
      },

      // Success callback
      function(stream) {


      },

      // Error callback
      function(err) {
         console.log('The following gUM error occured: ' + err);
      }
   );
} else {
   console.log('getUserMedia not supported on your browser!');
}

整个过程都包装在一个测试中,该测试会在运行任何其他操作之前检查 gUM 是否受支持。接下来,我们调用 getUserMedia(),并在其中定义

  • 约束:仅捕获音频;无论如何,MediaRecorder 目前仅支持音频。
  • 成功回调gUM 调用成功完成时会运行此代码。
  • 错误/失败回调:如果 gUM 调用因任何原因失败,则会运行此代码。

注意:以下所有代码都放置在 gUM 成功回调中。

捕获媒体流

一旦 gUM 成功获取了媒体流,你就可以使用 MediaRecorder() 构造函数创建一个新的 Media Recorder 实例,并将流直接传递给它。这是你使用 MediaRecorder API 的入口点 - 流现在已准备好直接捕获到 Blob 中,以浏览器默认的编码格式。

var mediaRecorder = new MediaRecorder(stream);

MediaRecorder 接口 中提供了一系列方法,允许你控制媒体流的录制;在 Web 录音机中,我们只使用了两个。首先,MediaRecorder.start() 用于在按下录制按钮后开始将流录制到 Blob 中。

record.onclick = function() {
  mediaRecorder.start();
  console.log(mediaRecorder.state);
  console.log("recorder started");
  record.style.background = "red";
  record.style.color = "black";
}

当 MediaRecorder 正在录制时,MediaRecorder.state 属性将返回“recording”的值。

其次,我们使用 MediaRecorder.stop() 方法在按下停止按钮时停止录制,并最终确定 Blob 以便在应用程序的其他地方使用。

stop.onclick = function() {
  mediaRecorder.stop();
  console.log(mediaRecorder.state);
  console.log("recorder stopped");
  record.style.background = "";
  record.style.color = "";
}

录制停止后,state 属性将返回“inactive”的值。

请注意,还有其他方法可以最终确定 Blob 并使其可用。

  • 如果媒体流用尽(例如,如果你正在获取歌曲曲目并且曲目已结束),则 Blob 将被最终确定。
  • 如果调用 MediaRecorder.requestData() 方法,则 Blob 将被最终确定,但录制将在新的 Blob 中继续。
  • 如果你在调用 start() 方法时包含一个时间切片属性 - 例如 start(10000) - 那么每当经过该毫秒数时,一个新的 Blob 将被最终确定(并开始新的录制)。

获取和使用 blob

当 Blob 最终确定并准备按上述方式使用时,将触发 dataavailable 事件,可以使用 mediaRecorder.ondataavailable 处理程序进行处理。

mediaRecorder.ondataavailable = function(e) {
  console.log("data available");

  var clipName = prompt('Enter a name for your sound clip');

  var clipContainer = document.createElement('article');
  var clipLabel = document.createElement('p');
  var audio = document.createElement('audio');
  var deleteButton = document.createElement('button');

  clipContainer.classList.add('clip');
  audio.setAttribute('controls', '');
  deleteButton.innerHTML = "Delete";
  clipLabel.innerHTML = clipName;

  clipContainer.appendChild(audio);
  clipContainer.appendChild(clipLabel);
  clipContainer.appendChild(deleteButton);
  soundClips.appendChild(clipContainer);

  var audioURL = window.URL.createObjectURL(e.data);
  audio.src = audioURL;

  deleteButton.onclick = function(e) {
    evtTgt = e.target;
    evtTgt.parentNode.parentNode.removeChild(evtTgt.parentNode);
  }
}

让我们浏览一下上面的代码,看看发生了什么。

首先,我们显示一个提示,要求用户为其剪辑命名。

接下来,我们创建如下所示的 HTML 结构,将其插入到我们的剪辑容器中,该容器是一个 <section> 元素。

your clip name

之后,我们使用 window.URL.createObjectURL(e.data) 创建一个指向事件的 data 属性的对象 URL:此属性包含已录制音频的 Blob。然后,我们将 <audio> 元素的 src 属性的值设置为对象 URL,以便当按下音频播放器上的播放按钮时,它将播放 Blob。

最后,我们为删除按钮上的 onclick 处理程序设置一个函数,该函数将删除整个剪辑 HTML 结构。

结论

就是这样;MediaRecorder 应该有助于简化你的应用程序媒体录制需求。试用一下,让我们知道你的想法:我们期待着看到你将构建什么!

关于 Chris Mills

Chris Mills 是 Mozilla 的高级技术作家,在那里他编写有关开放式 Web 应用、HTML/CSS/JavaScript、A11y、WebAssembly 等内容的文档和演示。他喜欢捣鼓 Web 技术,并在会议和大学偶尔进行技术讲座。他曾为 Opera 和 W3C 工作,喜欢演奏重金属鼓和饮用好啤酒。他和他的妻子以及三个可爱的孩子住在英国曼彻斯特附近。

更多 Chris Mills 的文章……

关于 Robert Nyman [荣誉编辑]

Mozilla Hacks 的技术布道师和编辑。发表关于 HTML5、JavaScript 和开放 Web 的演讲和博客。Robert 坚信 HTML5 和开放 Web,并且自 1999 年以来一直从事 Web 的前端开发工作 - 在瑞典和纽约市。他还定期在 http://robertnyman.com 上发布博客,并且喜欢旅行和结识新朋友。

更多 Robert Nyman [荣誉编辑] 的文章……


9 条评论

  1. Brett Zamir

    太棒了!伟大的工作……我可以问一下拼接两个文件有多困难,无论是在末尾还是在其中一部分?

    2014 年 6 月 11 日 02:35

    1. Chris Mills

      这应该可以使用 Web Audio API 实现。你可以创建一个音频上下文,然后从媒体流、音频文件或任何其他内容创建两个源(https://mdn.org.cn/en-US/docs/Web/API/Web_Audio_API#Defining_audio_sources),然后将这些源馈送到同一个音频图中。我还没有太多地使用过它,所以还没有演示,但我目前正在进行一些实验!

      2014 年 6 月 11 日 02:44

  2. Alejandro

    早上好,好的 API,一个问题是仍然无法在 Firefox OS 软件中捕获屏幕

    2014 年 6 月 11 日 05:30

    1. Chris Mills

      这目前只能捕获音频;视频很快就会到来。

      2014 年 6 月 11 日 07:31

  3. Sam Dutton

    很棒的文章 - 期待视频捕获 :)!

    一个小错误:我认为“当博客最终确定时”应该是“……Blob……”

    2014 年 6 月 11 日 06:33

  4. lcamacho

    抓取和使用 Blob 的部分有小错误,“When the blog” 应改为“blob”。

    2014年6月11日 07:17

    1. Chris Mills

      错别字已更正 - 谢谢大家!

      2014 年 6 月 11 日 07:31

  5. Mindaugas J.

    > clipLabel.innerHTML = clipName;
    从用户输入获取 innerHTML 不是一个好主意 ;)

    2014年6月11日 11:39

  6. omitsolutions

    分享给我们的信息很棒

    2014年6月24日 01:54

本文评论已关闭。