8、媒体流录制:MediaRecorder API介绍、录制音视频流、获取录制数据(Blob)
好,咱们今天聊点实在的。前面几章我们把摄像头、麦克风、屏幕共享都折腾了一遍,拿到了 MediaStream。但有个问题——你拿到了流,然后呢?总得存下来吧?
我记得我第一次做 WebRTC 录制功能时,天真地以为浏览器会自带一个「保存视频」的按钮。结果找了半天,发现得自己动手。嗯,MediaRecorder 就是干这个的。
8.1 MediaRecorder 是什么?
说白了,MediaRecorder 是浏览器提供的一个原生 API。它能把 MediaStream 里的音视频数据,实时编码成你想要的格式,然后一段一段吐给你。
你想想看,如果没有它,你要自己处理音视频编码、分片、存储……那工作量可就大了去了。MediaRecorder 把这些脏活累活都包了。
8.2 基本用法
用法其实很简单。三步走:
- 创建 MediaRecorder 实例,传入 MediaStream
- 监听 dataavailable 事件,拿到数据块
- 调用 start() 开始录制,stop() 停止录制
来,直接看代码:
// 假设 stream 是之前获取到的 MediaStream
const mediaRecorder = new MediaRecorder(stream, {
mimeType: 'video/webm;codecs=vp9'
});
let recordedChunks = [];
mediaRecorder.ondataavailable = function(event) {
if (event.data.size > 0) {
recordedChunks.push(event.data);
}
};
mediaRecorder.onstop = function() {
// 所有数据收集完毕,生成最终的 Blob
const blob = new Blob(recordedChunks, {
type: 'video/webm'
});
// 接下来你可以下载或上传这个 blob
};
// 开始录制,每 1000ms 触发一次 dataavailable
mediaRecorder.start(1000);
这里有个细节我提一下:start(1000) 里的参数是 timeslice,单位毫秒。它告诉浏览器每隔多久给你一次数据。我个人习惯设 1000,也就是每秒一个数据块。这样即使录制中途崩溃,你至少能拿到前面几秒的数据。
8.3 支持的格式
不是所有浏览器都支持所有格式。我踩过这个坑。你得先检查一下:
// 检查浏览器支持的录制格式
function getSupportedMimeTypes() {
const types = [
'video/webm;codecs=vp9',
'video/webm;codecs=vp8',
'video/webm;codecs=h264',
'video/mp4',
'audio/webm'
];
return types.filter(type => MediaRecorder.isTypeSupported(type));
}
console.log('支持的格式:', getSupportedMimeTypes());
我曾经在 Chrome 上测试得好好的,换到 Firefox 发现 h264 不支持。所以,上线前一定要做兼容性检查。
| 浏览器 | 支持的常见格式 |
|---|---|
| Chrome | video/webm (vp8/vp9), audio/webm |
| Firefox | video/webm (vp8/vp9), audio/webm |
| Safari | video/mp4 (h264), audio/mp4 |
| Edge | video/webm (vp8/vp9), video/mp4 |
8.4 录制状态管理
MediaRecorder 有几种状态:inactive、recording、paused。你可以通过 state 属性查看。
console.log(mediaRecorder.state); // 'inactive' | 'recording' | 'paused'
// 暂停录制
mediaRecorder.pause();
// 恢复录制
mediaRecorder.resume();
// 停止录制
mediaRecorder.stop();
嗯,这里要注意:pause() 和 resume() 不会触发 dataavailable。只有 start() 和 stop() 才会。我刚开始用的时候以为暂停也会吐数据,结果 debug 了半天。
8.5 获取录制数据:Blob 对象
录制完成后,我们拿到的是一个 Blob。Blob 是二进制大对象,你可以把它转成 URL 下载,或者用 FormData 上传到服务器。
// 生成下载链接
function downloadBlob(blob, filename) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename || 'recording.webm';
a.click();
URL.revokeObjectURL(url); // 释放内存
}
// 上传到服务器
function uploadBlob(blob) {
const formData = new FormData();
formData.append('video', blob, 'recording.webm');
fetch('/upload', {
method: 'POST',
body: formData
}).then(response => {
console.log('上传成功');
});
}
8.6 完整示例:录制并下载
把上面所有知识点串起来,就是一个完整的录制功能:
async function startRecording() {
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
const mimeType = MediaRecorder.isTypeSupported('video/webm;codecs=vp9')
? 'video/webm;codecs=vp9'
: 'video/webm';
const mediaRecorder = new MediaRecorder(stream, { mimeType });
let chunks = [];
mediaRecorder.ondataavailable = (e) => {
if (e.data.size > 0) chunks.push(e.data);
};
mediaRecorder.onstop = () => {
const blob = new Blob(chunks, { type: mimeType });
downloadBlob(blob, `recording-${Date.now()}.webm`);
stream.getTracks().forEach(track => track.stop());
};
mediaRecorder.start(1000);
console.log('录制中...');
// 5秒后自动停止
setTimeout(() => {
mediaRecorder.stop();
console.log('录制结束');
}, 5000);
}
8.7 核心流程示意图
下面这张图,把整个录制流程串起来了。你看一眼就能明白数据是怎么流动的:
8.8 避坑指南
最后,分享几个我踩过的坑:
- 格式不兼容: 我曾经在 Safari 上录视频,默认用了 webm,结果 Safari 不支持。后来加了 isTypeSupported 检查才解决。
- 内存泄漏: 录制长时间视频时,如果 chunks 数组一直不释放,内存会暴涨。建议每收集到一定数量的数据块,就合并一次并清空数组。
- 音频缺失: 有时候 getUserMedia 获取的流没有音频轨道,但录制时你指定了 audio: true。MediaRecorder 不会报错,但录出来的视频没声音。记得先检查 track 是否存在。
- 停止后释放资源: 录制结束后,一定要调用 stream.getTracks().forEach(track => track.stop()),否则摄像头和麦克风会一直亮着。
好了,MediaRecorder 的核心内容就这些。说白了就是三步:拿流、录数据、存 Blob。你动手试一遍,比看十遍文档都管用。