MediaRecorder API 允许您录制媒体流,即移动图像和音频。这些录制的成果可以是,例如,一个 OGG 文件,就像您用来听音乐的文件一样。
在浏览器方面,我们可以通过多种方式获取流。让我们从您可能熟悉的内容开始:我们将从网络摄像头获取一个流,使用 MediaDevices 接口
navigator.mediaDevices.getUserMedia({
audio: true
}).then(function (stream) {
// do something with the stream
}
一旦我们有了流,我们就可以创建MediaRecorder实例
var recorder = new MediaRecorder(stream);
这个实例就像其他 JavaScript 对象一样:它**有我们可以调用的方法**,并且它**发出我们可以监听的事件**。最重要的的方法是start和stop。最重要的事件是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 Audio 和 WebGL 特别适合以高效、高性能的方式操作数据,因此您会经常看到它们与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>元素,但浏览器中尚未实现此功能。
从AudioContext到流到AudioContext
流可以用作 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://flags和opera://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 找到她
10 条评论