17、媒体录制:MediaRecorder API、录制格式选择、分段录制与下载
聊到WebRTC,大家第一反应都是实时通信。但有个功能经常被忽略——媒体录制。说白了,就是把音视频流存成文件。
我在做第一个WebRTC项目时,客户要求“通话过程必须能回放”。当时我第一反应是架个流媒体服务器做录制。后来发现,浏览器自带的MediaRecorder就能搞定大部分场景。嗯,这里要注意,它和服务器端录制是两码事,但很多场景下够用了。
MediaRecorder API 基础
MediaRecorder是浏览器原生接口。你给它一个MediaStream,它就能把数据编码成文件。我个人习惯把它理解成“浏览器里的录像机”。
基本用法很简单:
// 获取媒体流
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
// 创建录制器
const recorder = new MediaRecorder(stream, {
mimeType: 'video/webm;codecs=vp9,opus'
});
// 收集数据块
const chunks = [];
recorder.ondataavailable = (e) => {
if (e.data.size > 0) {
chunks.push(e.data);
}
};
// 录制结束,生成文件
recorder.onstop = () => {
const blob = new Blob(chunks, { type: 'video/webm' });
const url = URL.createObjectURL(blob);
// 下载或预览
};
// 开始录制
recorder.start(1000); // 每秒触发一次 ondataavailable
这里有个坑:start()的参数是timeslice,单位毫秒。如果不传,ondataavailable只在stop()时触发一次。传了的话,会按时间切片触发。我曾经因为没传这个参数,导致录制大文件时内存爆了——所有数据攒到一块才处理,你想想看,录个半小时的视频,那数据量有多大。
录制格式选择
格式选择是个头疼的问题。浏览器支持的格式有限,而且各平台不一样。
| 格式 | 容器 | 编码 | 兼容性 |
|---|---|---|---|
| video/webm | WebM | VP8/VP9 + Opus | Chrome、Firefox、Edge |
| video/mp4 | MP4 | H.264 + AAC | Safari、部分Chrome |
| audio/webm | WebM | Opus | 所有现代浏览器 |
怎么选?我的经验是:先检测浏览器支持哪些格式。
function getSupportedMimeType() {
const types = [
'video/webm;codecs=vp9,opus',
'video/webm;codecs=vp8,opus',
'video/webm;codecs=h264,opus',
'video/mp4;codecs=h264,aac',
'audio/webm;codecs=opus'
];
for (const type of types) {
if (MediaRecorder.isTypeSupported(type)) {
return type;
}
}
return ''; // 都不支持就默认
}
说白了,VP9画质好但编码慢,VP8兼容性好,H.264在Safari上必须。我一般优先选VP9,如果浏览器不支持就降级到VP8。
分段录制
为什么要分段录制?两个原因:一是防止单文件过大,二是实现“回滚录制”——只保留最近N分钟的内容。
分段录制的核心逻辑:监听ondataavailable,每收到一段数据就保存成一个独立文件。
class SegmentedRecorder {
constructor(stream, options = {}) {
this.stream = stream;
this.segmentDuration = options.segmentDuration || 10000; // 每段10秒
this.maxSegments = options.maxSegments || 0; // 0表示不限
this.segments = [];
this.recorder = null;
this.isRecording = false;
}
start() {
this.isRecording = true;
this._createRecorder();
}
_createRecorder() {
const recorder = new MediaRecorder(this.stream, {
mimeType: getSupportedMimeType()
});
recorder.ondataavailable = (e) => {
if (e.data.size > 0) {
this.segments.push(e.data);
// 如果限制了最大段数,删除最旧的
if (this.maxSegments > 0 && this.segments.length > this.maxSegments) {
this.segments.shift();
}
}
};
recorder.onstop = () => {
if (this.isRecording) {
this._createRecorder(); // 继续下一段
}
};
recorder.start(this.segmentDuration);
this.recorder = recorder;
}
stop() {
this.isRecording = false;
if (this.recorder && this.recorder.state !== 'inactive') {
this.recorder.stop();
}
}
getBlob() {
return new Blob(this.segments, { type: 'video/webm' });
}
}
这个模式有个好处:你可以实现“只保留最近30秒”的循环录制。我在做安防监控项目时就用过这个思路——摄像头一直录,但只保留最近5分钟的数据,超出就丢弃。这样既不会爆内存,又能保证随时有回放可用。
timeslice不要设得太小。我建议至少1秒,否则会产生大量小文件,合并时也麻烦。10秒一段是比较平衡的选择。
下载与保存
录制完成后怎么下载?最直接的方式是用<a>标签触发下载。
function downloadBlob(blob, filename) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename || `recording_${Date.now()}.webm`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url); // 释放内存
}
如果你录了多个分段,想合并成一个文件再下载,可以这样:
async function mergeAndDownload(segments, filename) {
const blob = new Blob(segments, { type: 'video/webm' });
downloadBlob(blob, filename);
}
注意:WebM格式本身支持拼接,只要编码一致,直接合并Blob就能播放。但MP4不行,MP4需要重新封装。所以如果你用MP4格式,分段录制后合并会比较麻烦,得借助mp4box.js之类的库。
- MediaRecorder接收MediaStream,输出Blob
- 格式优先选WebM(VP9/VP8 + Opus),兼容性最好
- 分段录制用timeslice参数,ondataavailable回调处理每段
- 下载用createObjectURL + a标签点击
- 合并分段时,WebM可以直接拼,MP4需要额外处理
知识体系图
这张图把整个媒体录制的流程串起来了。从输入流开始,经过格式选择、分段录制,最后下载保存。底部三个应用场景是我在实际项目中经常用到的。
最后说一句:MediaRecorder虽然方便,但功能有限。如果你需要更精细的控制——比如指定编码参数、做视频裁剪、加滤镜——那就得用WebCodecs + Canvas的组合了。不过那是进阶内容,先把MediaRecorder玩熟再说。