21、录制与回放:本地录制、云端录制、回放功能实现
录制与回放,说白了就是给实时通信加上「时光机」功能。我最早接触这个需求,是在一个在线教育项目里——老师上完课,学生想回头复习,结果发现没录上,那叫一个尴尬。从那以后,我对录制这块就特别上心。
今天咱们聊聊 WebRTC 移动端的录制方案。嗯,这里要注意,移动端和 PC 端差别不小,尤其是性能瓶颈和网络波动,搞不好就翻车。
21.1 录制的核心思路
WebRTC 的录制,本质上就是把音视频流「抓」下来,存成文件。你想想看,通话中的每一帧画面、每一段声音,都是通过 MediaStream 传递的。我们只要拿到这个流,就能做文章。
具体来说,有两种主流方式:
- 本地录制:在客户端直接录制,用 MediaRecorder API 搞定
- 云端录制:把流推到服务器,由服务端完成录制
我个人习惯是:小规模场景用本地录制,省服务器成本;大规模或需要合规存档的场景,必须上云端录制。
核心知识点:录制不是简单的「录屏」,而是从 MediaStream 中提取音视频轨道,再编码封装。移动端还要考虑电池、内存、存储空间。
21.2 本地录制实现
本地录制最常用的 API 就是 MediaRecorder。它能把 MediaStream 实时编码成 WebM 或 MP4 片段。我在项目中遇到过一个问题:Android 上某些浏览器不支持 MP4,只能录 WebM,但 iOS 又对 WebM 支持不好。嗯,这坑我踩过。
来看一段核心代码:
// 获取本地流
const localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
// 创建 MediaRecorder
const options = {
mimeType: 'video/webm;codecs=vp9',
videoBitsPerSecond: 2500000
};
const mediaRecorder = new MediaRecorder(localStream, options);
// 收集数据
const chunks = [];
mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
chunks.push(event.data);
}
};
// 录制结束,生成文件
mediaRecorder.onstop = () => {
const blob = new Blob(chunks, { type: 'video/webm' });
const url = URL.createObjectURL(blob);
// 保存或播放
};
// 开始录制
mediaRecorder.start(1000); // 每秒生成一个片段
小技巧:start(1000) 里的参数是 timeslice,建议设成 1000ms。这样即使录制中断,也能拿到部分数据,不至于全丢。
移动端录制有个大坑:长时间录制会导致内存暴涨。我曾经在测试时录了半小时,结果 App 直接闪退。解决方案是分段录制,每 5 分钟存一个文件,然后释放内存。
21.3 云端录制实现
云端录制,说白了就是把客户端的流推到服务器,由服务端来录。这样做的好处是:客户端不用操心存储和编码,省电省内存。
实现思路分三步:
- 客户端把本地流通过 WebRTC 推送到媒体服务器(比如 LiveKit、Janus、或者自建 SFU)
- 服务器接收流,调用 FFmpeg 或 GStreamer 进行录制
- 录制完成后,把文件存到对象存储(OSS/S3)
来看一个服务端录制的伪代码(基于 Node.js + FFmpeg):
// 接收 WebRTC 流
const rtcStream = await getRTCStream(roomId, userId);
// 用 FFmpeg 录制
const ffmpeg = spawn('ffmpeg', [
'-i', rtcStream.url,
'-c:v', 'libx264',
'-preset', 'ultrafast',
'-c:a', 'aac',
'-f', 'mp4',
`recordings/${roomId}_${Date.now()}.mp4`
]);
ffmpeg.on('close', (code) => {
if (code === 0) {
// 上传到 OSS
uploadToOSS(`recordings/${roomId}_${Date.now()}.mp4`);
}
});
注意:云端录制对服务器带宽和 CPU 要求很高。如果同时录制 100 路流,建议用 GPU 加速编码,否则服务器分分钟被打满。
21.4 回放功能实现
回放,就是把录好的文件播出来。听起来简单,但移动端回放有几个坑:
- 格式兼容:iOS 不支持 WebM,Android 部分机型不支持某些编码
- 进度同步:如果录的是多人通话,回放时要把多路流对齐
- 交互控制:暂停、快进、倍速播放,这些在移动端 WebView 里表现不一
我建议的做法是:录制时统一转成 HLS 格式。HLS 在 iOS 和 Android 上都原生支持,而且支持自适应码率。回放时直接用 <video> 标签加载 m3u8 文件就行。
<video controls autoplay>
<source src="https://cdn.example.com/recordings/lesson21.m3u8" type="application/x-mpegURL">
</video>
如果是本地录制,回放时要注意:从 blob:url 读取文件,不要直接操作大文件。我习惯先把 blob 转成 ArrayBuffer,再分段加载,避免卡 UI 线程。
21.5 整体架构图
下面这张图展示了本地录制、云端录制和回放的完整流程。你可以看到,客户端既可以本地录,也可以推流到服务器。回放时,本地文件直接播,云端文件通过 CDN 分发。
21.6 避坑指南
做录制功能,我踩过的坑能写满一页纸。挑几个最典型的说说:
- 音频和视频不同步:MediaRecorder 在低端手机上容易音画不同步。我曾经在红米手机上测试,录了 10 分钟,声音比画面快了 3 秒。解决方案是录制时加上时间戳校准,或者用
requestVideoFrameCallback做同步。 - 录制中断:App 切到后台,录制可能被系统杀掉。Android 上可以用前台 Service 保活,iOS 上要申请后台权限。
- 文件太大:高清视频 1 小时能到 10GB。建议录制时控制码率,或者用 HLS 切片,每段 10 秒,方便上传和回放。
- 云端录制延迟:从推流到文件可用,通常有 5-10 秒延迟。如果要求实时回放,可以考虑用「边录边播」模式,服务器每生成一个切片就推送给客户端。
我的经验:移动端录制,优先考虑云端方案。本地录制虽然简单,但用户手机存储空间有限,而且录制过程中如果来电话、切应用,很容易失败。云端录制虽然复杂一点,但稳定性和可扩展性都好得多。
21.7 性能优化建议
最后给几个性能优化的方向:
| 优化项 | 说明 | 推荐做法 |
|---|---|---|
| 编码器选择 | VP9 画质好但编码慢,H.264 兼容性好 | 移动端优先 H.264,服务端用硬件编码 |
| 码率控制 | 码率越高文件越大,传输越慢 | 视频 2-4 Mbps,音频 128 Kbps |
| 分段录制 | 避免单文件过大,降低内存压力 | 每 5 分钟或 100MB 切一段 |
| 上传策略 | 边录边传,避免录完再传耗时 | 每生成一个片段立即上传 |
| 回放预加载 | 减少用户等待时间 | 提前加载下一个切片,用 preload="auto" |
嗯,录制与回放这块内容就这些。说白了,核心就是拿到流、存下来、播出去。但每个环节都有不少细节,尤其是移动端,性能和兼容性要格外注意。希望这些经验能帮你少踩几个坑。