MediaRecorder 入门:从零开始录制浏览器音视频
说实话,我第一次接触 MediaRecorder 的时候,心里还挺没底的。那时候项目要求做一个浏览器端的录音功能,我第一反应是「这玩意儿浏览器能搞定?」。结果翻了一下 MDN 文档,发现 MediaRecorder 这个 API 早在 2016 年左右就已经被各大浏览器支持了。嗯,是我孤陋寡闻了。
这一章,我们就来聊聊 MediaRecorder 到底是什么,它能干什么,以及怎么上手用起来。我会把我踩过的坑、总结的经验一并分享给你。
MediaRecorder API 概述
MediaRecorder 是 WebRTC 生态里一个非常重要的接口。它的作用很简单:把从摄像头、麦克风或者屏幕捕获到的媒体流,编码成文件。说白了,就是帮你把实时流「录下来」。
你想想看,如果没有 MediaRecorder,我们要实现浏览器端录制,就得靠 Flash 或者后端转码,那体验简直不敢想。现在有了它,前端自己就能搞定录制、暂停、恢复、停止这一整套流程。
核心要点:MediaRecorder 接收一个 MediaStream 对象,输出一个 Blob 文件。中间的过程——编码、封装、分片——全部由浏览器底层完成,我们只需要调用几个方法就行。
我个人习惯把 MediaRecorder 理解成一个「管道」:
- 入口:MediaStream(来自 getUserMedia 或 getDisplayMedia)
- 管道本身:MediaRecorder 实例
- 出口:Blob 数据(通过 dataavailable 事件获取)
这个模型一旦建立起来,后面所有操作都会变得很清晰。
支持的编码格式与 MIME 类型
这里有个坑,我必须先跟你说清楚:不同浏览器支持的编码格式是不一样的。我在项目中遇到过,Chrome 上跑得好好的录制代码,换到 Firefox 上直接报错。原因就是 MIME 类型不兼容。
常见的 MIME 类型有这些:
| MIME 类型 | 说明 | 浏览器支持情况 |
|---|---|---|
video/webm |
WebM 容器,VP8/VP9 编码 | Chrome、Firefox、Edge 均支持 |
video/webm;codecs=vp9 |
指定 VP9 编码,压缩率更高 | Chrome 支持较好,Firefox 部分支持 |
video/mp4 |
MP4 容器,H.264 编码 | 仅部分浏览器(如 Safari)支持 |
audio/webm |
纯音频 WebM,Opus 编码 | Chrome、Firefox 均支持 |
audio/wav |
WAV 格式,PCM 编码 | 支持有限,不建议用于录制 |
注意:不要硬编码 MIME 类型!我曾经在代码里直接写了 'video/webm;codecs=vp9',结果用户用 Safari 打开,直接抛异常。正确的做法是先调用 MediaRecorder.isTypeSupported() 检测一下。
检测方法很简单:
const mimeTypes = [
'video/webm;codecs=vp9',
'video/webm;codecs=vp8',
'video/webm',
'audio/webm'
];
const supported = mimeTypes.filter(type =>
MediaRecorder.isTypeSupported(type)
);
console.log('当前浏览器支持的格式:', supported);
我个人建议,优先使用 video/webm;codecs=vp8 作为保底方案。因为 VP8 的兼容性最好,几乎所有现代浏览器都支持。如果你追求更高的压缩率,再尝试 VP9。
创建 MediaRecorder 实例
创建实例其实就一行代码,但有几个参数值得细说。
const mediaRecorder = new MediaRecorder(stream, {
mimeType: 'video/webm;codecs=vp8',
audioBitsPerSecond: 128000,
videoBitsPerSecond: 2500000
});
第一个参数是 MediaStream,这个不用多说。第二个参数是配置对象,我重点讲两个:
- mimeType:指定编码格式。如果不传,浏览器会使用默认格式。但我建议你显式指定,否则不同浏览器行为不一致,调试起来很头疼。
- bitsPerSecond:控制码率。音频一般 128kbps 就够用,视频的话 2.5Mbps 是个不错的起点。码率设得太低画面会糊,设得太高文件又太大。
小技巧:如果你不确定该设多少码率,可以先用默认值跑一遍,看看文件大小和画质,再逐步调整。我在做录屏工具时就是这么干的,先跑一轮测试,再根据用户反馈微调参数。
创建实例时还有一个容易忽略的点:stream 必须是活跃的。如果摄像头已经被关闭,或者 track 已经 ended,new MediaRecorder 会直接抛出一个 InvalidStateError。所以创建实例前,最好检查一下 stream 的状态。
录制状态管理
MediaRecorder 有四种状态,理解它们对调试非常重要:
- inactive:初始状态,还没开始录制
- recording:正在录制中
- paused:已暂停录制
- error:发生错误
你可以通过 mediaRecorder.state 随时查看当前状态。状态之间的转换关系是这样的:
inactive → recording (调用 start())
recording → paused (调用 pause())
paused → recording (调用 resume())
recording → inactive (调用 stop())
paused → inactive (调用 stop())
嗯,这里要注意:从 paused 状态调用 stop() 是合法的,它会直接结束录制,而不是先恢复再停止。我刚开始用的时候以为必须 resume 之后才能 stop,结果代码里多写了一步,浪费了不少时间。
状态变化会触发对应的事件:
onstart:开始录制时触发onpause:暂停时触发onresume:恢复时触发onstop:停止时触发onerror:出错时触发
一个完整的录制流程大概是这样的:
const mediaRecorder = new MediaRecorder(stream);
mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
// 这里拿到录制的数据块
chunks.push(event.data);
}
};
mediaRecorder.onstop = () => {
const blob = new Blob(chunks, { type: mediaRecorder.mimeType });
// 现在你可以下载或者上传这个 blob 了
};
// 开始录制,每 1 秒触发一次 dataavailable
mediaRecorder.start(1000);
这里有个细节:start(1000) 里的参数是 timeslice,单位毫秒。它决定了 dataavailable 事件触发的频率。如果不传这个参数,dataavailable 只会在 stop() 时触发一次。我个人习惯传一个 1000 或 2000,这样录制过程中就能拿到中间数据,方便做实时预览或者分段上传。
重要提醒:录制过程中一定要监听 dataavailable 事件,把数据块存起来。否则 stop 之后你什么都拿不到。这个坑我踩过,当时 debug 了半天才发现是忘了加事件监听。
本章知识体系
为了让你更直观地理解 MediaRecorder 的整体结构,我画了一张流程图:
这张图把 MediaRecorder 的输入、输出、状态转换都串起来了。你可以把它当作一个速查表,写代码的时候瞄一眼,思路会清晰很多。
好了,MediaRecorder 的基础知识就讲到这里。下一章我们会深入 dataavailable 事件和分片录制,到时候我会分享一个我在做录屏工具时遇到的「数据丢失」的坑,保证让你印象深刻。