录制与回放:MediaRecorder API、录制音视频流、录制文件下载、录制文件回放

聊到WebRTC,大家最关心的往往是“怎么把画面传过去”。但实际项目中,录制回放的需求一点都不少。比如在线教育要录课、视频面试要存档、远程医疗要留证……说白了,光能实时通话还不够,得能把流“存下来”才行。

我个人习惯把录制看作WebRTC的“最后一公里”。流都拿到了,不录下来多可惜?好在浏览器给了我们一个非常顺手的工具——MediaRecorder API。今天我们就把它彻底讲透。

MediaRecorder API 是什么?

MediaRecorder 是浏览器原生提供的录制接口。它能接收一个 MediaStream 对象,然后按你指定的格式把数据一块块吐出来。你想想看,这不就是给音视频流装了个“录像机”吗?

它的核心工作流程其实就三步:

  1. 拿到流(从摄像头、屏幕共享或者WebRTC的 RTCPeerConnection 里取)
  2. 创建 MediaRecorder 实例,绑定流
  3. 监听 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 属性加上。这样用户能自己控制进度条、暂停、音量,体验会好很多。

知识体系总览

下面这张图把录制与回放的完整流程串起来了。你可以把它当作一个“速查地图”:

录制与回放核心流程 获取 MediaStream getUserMedia / RTCPeerConnection 创建 MediaRecorder 指定 mimeType、timeslice 录制中 ondataavailable 停止录制 mediaRecorder.stop() 生成 Blob new Blob(chunks) 下载文件 URL.createObjectURL 回放视频 <video> 标签播放 也可直接回放 完整链路:获取流 → 录制 → 生成文件 → 下载 / 回放

避坑指南与实战经验

做录制功能这几年,我踩过的坑还真不少。挑几个典型的说说:

  • 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 标签 → 播放

嗯,说白了就是“录、存、播”三个字。但每个环节都有细节要注意。希望今天的内容能帮你少走一些弯路。


公众号:蓝海资料掘金营,微信deep3321