录制与回放:MediaRecorder API、录制音视频流、录制文件下载、录制文件回放
聊到WebRTC,大家最关心的往往是“怎么把画面传过去”。但实际项目中,录制和回放的需求一点都不少。比如在线教育要录课、视频面试要存档、远程医疗要留证……说白了,光能实时通话还不够,得能把流“存下来”才行。
我个人习惯把录制看作WebRTC的“最后一公里”。流都拿到了,不录下来多可惜?好在浏览器给了我们一个非常顺手的工具——MediaRecorder API。今天我们就把它彻底讲透。
MediaRecorder API 是什么?
MediaRecorder 是浏览器原生提供的录制接口。它能接收一个 MediaStream 对象,然后按你指定的格式把数据一块块吐出来。你想想看,这不就是给音视频流装了个“录像机”吗?
它的核心工作流程其实就三步:
- 拿到流(从摄像头、屏幕共享或者WebRTC的
RTCPeerConnection里取) - 创建 MediaRecorder 实例,绑定流
- 监听
ondataavailable事件,把数据攒起来,最后生成一个 Blob
核心要点:MediaRecorder 不关心你的流是从哪来的。只要是 MediaStream,它就能录。WebRTC 的远端流、本地流、甚至混合流,统统可以喂给它。
录制音视频流:从拿到流到开始录制
我们先看一个最基础的录制示例。假设你已经通过 getUserMedia 拿到了本地音视频流:
// 假设 stream 已经通过 getUserMedia 获取
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
// 创建 MediaRecorder 实例
// 我一般习惯指定 mimeType,避免浏览器默认选一个不支持的格式
let mimeType = 'video/webm;codecs=vp9,opus';
if (!MediaRecorder.isTypeSupported(mimeType)) {
mimeType = 'video/webm;codecs=vp8,opus';
console.warn('VP9 不支持,回退到 VP8');
}
const mediaRecorder = new MediaRecorder(stream, {
mimeType: mimeType
});
// 存储录制的数据块
const recordedChunks = [];
mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
recordedChunks.push(event.data);
}
};
// 开始录制,每 1000 毫秒触发一次 ondataavailable
mediaRecorder.start(1000);
// 5 秒后停止录制
setTimeout(() => {
mediaRecorder.stop();
}, 5000);
嗯,这里要注意一个细节:start(1000) 里的参数是 timeslice。它告诉浏览器每隔多少毫秒吐一次数据。如果不传这个参数,浏览器会在 stop() 时才一次性吐出所有数据。我个人建议设一个合理的值(比如 1000ms),这样即使录制中途崩溃,你至少还有一部分数据能抢救回来。
小技巧:录制 WebRTC 的远端流时,记得先确认 ontrack 事件已经触发,流已经到达。我曾经在 ontrack 还没触发时就创建 MediaRecorder,结果录了个寂寞……
录制文件下载:把 Blob 变成可下载的文件
录制完成后,recordedChunks 数组里存了一堆 Blob 片段。我们需要把它们合并成一个完整的 Blob,然后触发下载。
// 合并所有数据块
const blob = new Blob(recordedChunks, {
type: mimeType
});
// 生成下载链接
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `recording_${Date.now()}.webm`;
a.click();
// 释放内存
URL.revokeObjectURL(url);
这段代码看起来简单,但我在项目中踩过一个坑:文件名后缀要和 mimeType 对应。如果你录的是 video/webm,后缀就用 .webm;如果录的是 audio/webm,后缀用 .webm 或 .ogg。千万别写成 .mp4,否则播放器会不认。
警告: URL.createObjectURL 创建的 URL 会占用内存。下载完成后记得调用 revokeObjectURL 释放。否则长时间运行会导致内存泄漏,尤其是在录制多段视频的场景下。
录制文件回放:把录好的视频播出来
回放其实更简单。拿到 Blob 后,直接用 <video> 标签播放就行。
<video id="playback" controls autoplay muted></video>
const video = document.getElementById('playback');
const url = URL.createObjectURL(blob);
video.src = url;
video.play();
这里有个小坑:autoplay 策略。现代浏览器不允许带声音的视频自动播放。所以我在示例里加了 muted 属性。如果你需要播放声音,让用户手动点击播放按钮会更稳妥。
另外,回放时我建议把 controls 属性加上。这样用户能自己控制进度条、暂停、音量,体验会好很多。
知识体系总览
下面这张图把录制与回放的完整流程串起来了。你可以把它当作一个“速查地图”:
避坑指南与实战经验
做录制功能这几年,我踩过的坑还真不少。挑几个典型的说说:
- mimeType 兼容性:不是所有浏览器都支持
video/webm;codecs=vp9。我建议每次都用MediaRecorder.isTypeSupported()检查一遍,然后做降级处理。 - 录制时长控制:如果你需要限制录制时长(比如最长 30 分钟),记得在
start()之后启动一个定时器。但要注意,定时器回调里调用stop()时,要确保 MediaRecorder 的状态还是recording,否则会抛异常。 - 音频录制延迟:我曾经遇到一个情况——录出来的音频比视频慢半秒。排查后发现是
timeslice设得太大了。把timeslice从 5000ms 改成 1000ms 后,问题就解决了。 - 内存管理:录制长时间视频时,
recordedChunks数组会越来越大。如果你不需要全部保留,可以考虑边录边写磁盘(通过FileWriter或 IndexedDB),而不是全攒在内存里。
一个实用的小功能:录制过程中显示“已录制时长”。你可以在 start() 时记录开始时间,然后用 setInterval 每秒更新 UI。停止录制时清除定时器。这个功能虽然简单,但用户体验提升很明显。
总结
MediaRecorder API 是浏览器原生录制方案里最直接、最稳定的选择。它不依赖任何第三方库,和 WebRTC 的 MediaStream 无缝衔接。你只需要记住三个关键点:
- 拿到流 → 创建 MediaRecorder → 监听数据
- 合并 Blob → 生成下载链接 → 触发下载
- Blob 转 URL → 塞进 video 标签 → 播放
嗯,说白了就是“录、存、播”三个字。但每个环节都有细节要注意。希望今天的内容能帮你少走一些弯路。