使用 MediaRecorder 录制浏览器中的几乎所有内容

MediaRecorder API 允许您录制媒体流,即移动图像和音频。这些录制的成果可以是,例如,一个 OGG 文件,就像您用来听音乐的文件一样。

在浏览器方面,我们可以通过多种方式获取流。让我们从您可能熟悉的内容开始:我们将从网络摄像头获取一个流,使用 MediaDevices 接口

navigator.mediaDevices.getUserMedia({
    audio: true
}).then(function (stream) {
    // do something with the stream
}

一旦我们有了流,我们就可以创建MediaRecorder实例

var recorder = new MediaRecorder(stream);

这个实例就像其他 JavaScript 对象一样:它**有我们可以调用的方法**,并且它**发出我们可以监听的事件**。最重要的的方法是startstop。最重要的事件是dataavailable,它表示编码已完成并且录制已准备好供我们使用。

有了这些知识,我们可以像这样录制音频

recorder.addEventListener('dataavailable', function(e) {
    // e.data contains the audio data! let's associate it to an <audio> element
    var el = document.querySelector('audio');
    el.src = URL.createObjectURL(e.data);
});

// start recording here...
recorder.start();

// and eventually call this to stop the recording, perhaps on the press of a button
recorder.stop();

试试看。

相当 简短,并且我们能够在浏览器中本地编码音频,而无需使用插件或加载外部库。

新增功能

现在我们了解了基础知识,我们就可以开始使用新的功能了:我们终于可以在浏览器中录制视频了!

视频是一个复杂的话题,但是MediaRecorder使它变得足够简单。为了演示,我们可以基于前面的示例录制视频。

我们要做的第一件事是更改初始化流的方式。我们将请求音频视频

navigator.mediaDevices.getUserMedia({
    audio: true,
    video: true // <-- new!
})

其余代码基本上相同,除了我们设置了src一个<video>元素而不是一个<audio>元素。 试试看。

是不是很漂亮?请允许我坚持认为我们没有加载任何外部库或将现有的 C 或 C++ 编码器转换为使用 Emscripten、asm.js 或不太可移植的 PNaCl 之类的工具高度优化的 JavaScript。代码只有 62 行,没有任何外部依赖项,因为它使用了内置的浏览器功能。六十二行!包括注释!

我们同时节省了带宽和处理器功耗,因为原生代码对于视频编码效率更高。我们还重用了用于其他平台功能的代码。我们都赢了

升级

MediaRecorder不关心流中的内容或流的来源。这是一个非常强大的功能:我们可以使用其他 Web API 以各种方式修改流,然后再将其传递给MediaRecorder实例。 Web AudioWebGL 特别适合以高效、高性能的方式操作数据,因此您会经常看到它们与MediaRecorder.

新的 Media Capture API 集合(也包括 Media Recorder API)描述了对<canvas>, <audio><video>元素的扩展,该扩展允许将元素的输出捕获为流。我们还可以进行流操作,例如创建新流并向其中添加轨道,或者将流分解成轨道并根据需要将其输入和输出到其他流中,无论是否进行进一步处理。

但是,让我们一步一步地了解所有这些技术……

从 DOM 元素到流

例如,让我们从录制在<canvas>元素中渲染的动画的视频开始。我们要做的就是调用captureStream()画布上的方法

var canvasStream = canvas.captureStream();

然后我们像以前一样继续,使用我们刚刚获得的流创建一个MediaRecorder的实例

var recorder = new MediaRecorder(canvasStream);

您可以看到 一个示例,它生成一个带有白噪声的剪辑,该剪辑使用requestAnimationFrame.

定期在画布上渲染“但这只显示了如何在没有外部输入的情况下录制画布”,我听到你说。你是对的!要操作传入的图像然后录制流

// set the stream as src for a video element
video.src = URL.createObjectURL(stream)

// periodically draw the video into a canvas
ctx.drawImage(video, 0, 0, width, height);

// get the canvas content as image data
var imageData = ctx.getImageData(0, 0, width, height);

// apply your pixel magic to this bitmap
var data = imageData.data; // data is an array of pixels in RGBA

for (var i = 0; i < data.length; i+=4) {
    var average = (data[i] + data[i + 1]  + data[i + 2]) / 3;
    data[i] = average >= 128 ? 255 : 0; // red
    data[i + 1] = average >= 128 ? 255 : 0; // green
    data[i + 2] = average >= 128 ? 255 : 0; // blue
    // note: i+3 is the alpha channel, we are skipping that one
}

在这个示例中,我们使用画布对输入视频应用一个简单的滤镜。录制它只需使用captureStream()在画布上,如您在 此其他示例中看到的那样。

也就是说,画布像素操作不是最有效的解决方案。WebGL 更适合这些应用程序,但设置起来也更难,对于此示例来说有点过头了。

注意:规范还描述了在captureStream方法上<audio><video>元素,但浏览器中尚未实现此功能。

AudioContextAudioContext

流可以用作 Web Audio 中的输入和输出节点,方法是使用正确的音频节点类型。这使我们能够将音频流输入AudioContext并使用音频图对其进行操作。然后,我们将输出发送到另一个流,从AudioContext中输出,以供进一步使用或处理。

假设我们有一个和一个audioContext,我们需要创建一个 MediaStreamAudioSourceNode 的实例才能在音频上下文中使用流的音频

var sourceNode = audioContext.createMediaStreamSource(stream);

如果我们想听到输入,我们可以将此节点直接连接到audioContext.destination

sourceNode.connect(audioContext.destination);

或者我们可以将其连接到一个滤波器以修改声音,并将滤波器连接到audioContext.destination。这样,我们只听到过滤后的版本,而不是原始版本

var filter = audioContext.createBiquadFilter();
filter.connect(audioContext.destination);
sourceNode.connect(filter);

如果我们想捕获此过滤后的版本,我们需要创建一个 MediaStreamAudioDestination 节点,并将滤波器连接到它,而不是连接到audioContext.destination

var streamDestination = audioContext.createMediaStreamDestination();
filter.connect(streamDestination);

连接到streamDestination的所有内容都将通过节点上的属性从音频图中流出,然后我们可以使用它来创建MediaRecorder的实例并录制过滤后的声音

var filteredRecorder = new MediaRecorder(streamDestination.stream);

这是一个 在录制之前对输入音频应用滤波器 的示例。

现在全部放在一起

在以上部分中,我们了解了如何分别处理视频和音频。复杂的应用程序将要求我们同时执行这两项操作。

您可以使用输入流“按原样”并行处理两者,但有一个问题:由于我们需要在必须播放的视频中渲染流,因此您将听到未处理的音频。前面的示例没有这个问题,因为我们请求了一个没有音频轨道的流。您可能会想:“啊,我可以静音视频元素!”,但这也会静音流,导致您没有音频可供处理。

解决方案是创建两个新流,一个用于视频,另一个用于音频,仅向其中添加相应的视频和音频轨道,并将其用作输入以并行处理它们。完成后,我们将输出合并到一个流中。

让我们首先使用MediaStream构造函数创建一个新流

var videoStream = new MediaStream();

我们将使用getVideoTracks()方法列出视频轨道,并将它们添加到videoStream:

var videoTracks = inputStream.getVideoTracks();
videoTracks.forEach(function(track) {
    videoStream.addTrack(track);
});

当然,也有一种方法可以仅列出音频轨道。我们将在audioStream:

var audioStream = new MediaStream();
var audioTracks = inputStream.getAudioTracks();
audioTracks.forEach(function(track) {
    audioStream.addTrack(track);
});

中使用它

// Manipulate videoStream into a canvas, as shown above
// [...]
// Then get result from canvas stream into videoOutputStream
var videoOutputStream = videoCanvas.captureStream();

// Manipulate audio with an audio context, as shown above
// [...]
// Then get result from audio destination node into audioOutputStream
var audioOutputStream = streamDestination.stream;

现在我们可以使用这些新流作为视频和音频并行处理的输入。最后,我们将有两个输出流videoOutputStreamaudioOutputStream,我们需要将它们组合到最终流中。这次,我们可以使用getTracks()

var outputStream = new MediaStream();
[audioOutputStream, videoOutputStream].forEach(function(s) {
    s.getTracks().forEach(function(t) {
        outputStream.addTrack(t);
    });
});

方法,这将帮助我们使代码更通用然后我们可以像往常一样使用outputStreamMediaRecorder作为构造函数的参数!

var finalRecorder = new MediaRecorder(outputStream);

您可以查看 Boo! 以了解所有这些技术协同工作​​的完整演示。它是一个视频亭,完全在客户端运行,包括音频和视频处理以及编码。

浏览器支持

目前,浏览器支持并非我们所说的出色,但它正在迅速变得非常出色。

桌面版 Firefox 支持视频和音频录制以及我们上面描述的所有其他技术,从 Firefox 开发者版 47 开始,开箱即用

MediaRecorder目前在 Android 版 Firefox 上不起作用,但我们希望在 Firefox 48 发布周期中尽快启用它。移动设备上的性能一开始可能不会很好,但我们将努力尽快在今年实现硬件编码。

Chrome 47+ 和 Opera 36+ 提供部分支持:它们仅支持从 WebRTC 流录制视频。(来自画布的流captureStream()和来自 Web Audio 的流目前不受支持。)您还需要在chrome://flagsopera://flags中分别启用支持实验性 Web 平台功能标志,然后重新启动浏览器。该功能(和音频录制)在 Chrome 49 中默认启用。Microsoft Edge 似乎也打算在某个时候实现这一点——事实上,规范编辑之一在微软工作。

这并不能阻止您将此功能用作网站增强功能的附加层。

为了避免以愚蠢的方式破坏您的代码或在用户在不可用MediaRecorder的浏览器中访问网站时显示非功能性录制 UI 按钮或类似内容,请确保首先检测对它的支持。仅在支持的情况下显示额外功能,以防止事物在屏幕上出现和消失。

例如,您可以检查window对象是否包含MediaRecorder属性

if(window.MediaRecorder !== undefined) {
    // great! show recording UI
}

更多信息和资源

希望这能激发您的兴趣,并希望您想进一步了解 MediaRecorder API 及其朋友!

我们准备了一个 简单的在线示例集,用于演示各个技术,而不是一个庞大的“代码整体”,它试图同时演示所有内容。您还可以克隆 整个存储库。建议使用现代的 mediaDevices.getUserMedia 语法,因此我们提供了 polyfill 以兼容尚未实现它的平台。请注意,出于简单起见,示例包含 polyfill。

如上所述,Boo! 是一个视频亭,能够实时应用视频和音频效果,并以短片的形式捕获输出,所有这些都在浏览器上运行。 源代码 也可用。

如果您想比较自我们上次在博客中讨论MediaRecorder以来我们走了多远,请阅读 2014 年 6 月发布的 Chris Mills 的文章。提示:我们已经走了很长一段路。

让我们知道您构建的酷炫的东西!

关于 Soledad Penadés

Sole 在 Mozilla 的开发者工具团队工作,帮助人们在 Web 上创造令人惊叹的东西,最好是实时的。在 irc.mozilla.org 上的 #devtools 找到她

更多 Soledad Penadés 的文章…


10 条评论

  1. Valentin C.

    它可以用来录制完整的浏览器窗口(HTMLDocument)吗?就像 Firefox Hello 一样?

    2016 年 4 月 7 日 上午 10:45

    1. Maire Reavy

      您可以录制可以渲染到画布上的内容 - 对于 Web JS 代码来说,这与 Hello 的标签捕获不同,后者可以渲染跨域内容。这是将内容渲染到画布上的普遍限制。

      您可以通过捕获整个窗口,使用 getUserMedia() 窗口屏幕捕获来捕获“浏览器窗口”。用户必须批准该捕获(并选择要共享的窗口)。请注意,屏幕/窗口捕获的“持久权限”不存在;用户必须始终明确批准它。此外,由于与此相关的跨源捕获风险,除非请求的域已添加到白名单 configvar 中,或通过扩展程序添加到白名单中,否则浏览器甚至不会询问用户。

      您可以在此处试用屏幕和窗口捕获:https://mozilla.github.io/webrtc-landing/gum_test.html

      2016 年 4 月 7 日 下午 12:06

  2. darktrojan

    这是我制作的。本周早些时候,我使用这些 API(以及一个执行复杂数学运算的工作线程)对饼干怪兽进行了绿屏处理。我已经对其进行了整理,以便在此处发布并展示正在发生的事情

    https://www.darktrojan.net/test/green/green.html

    2016 年 4 月 8 日 上午 02:13

    1. Soledad Penadés

      嘿 @darktrojan,这真是太棒了!

      2016 年 4 月 14 日 上午 05:22

  3. Szymon Nowak

    硬件编码完成后,是否可以在移动设备上实时录制带音频的视频?

    2016 年 4 月 13 日 上午 07:41

    1. Maire Reavy

      即使使用软件编码,也可以实时录制视频和音频(就像当今使用软件编码的移动设备上的 WebRTC 调用一样)。使用硬件编码通常可以获得更好的性能。

      2016 年 4 月 13 日 上午 08:32

  4. Fuad anuar

    帮助

    2016 年 4 月 18 日 下午 22:01

  5. Natalie

    感谢详细的解释。效果很好!

    2016 年 4 月 22 日 上午 04:52

  6. Noitidart

    这非常酷。是否有任何从 Firefox 扩展程序执行此操作的示例?例如,向扩展程序呈现可用麦克风的列表,并选择默认麦克风?

    2016 年 5 月 1 日 上午 04:40

    1. Maire Reavy

      据我所知,还没有人这样做过,但这是可行的。只是在 UI 方面需要大量的工作。

      2016 年 5 月 2 日 上午 11:33

本文的评论已关闭。