录制错误处理:常见录制错误类型、设备被占用处理、录制中断恢复、用户权限撤销处理

做 WebRTC 录制,说白了就是在跟各种意外打交道。

我刚开始做这个功能时,以为核心逻辑写好了就万事大吉。结果上线第一天,用户反馈就炸了——有的录到一半没画面,有的麦克风没声音,有的干脆点开始录制就报错。嗯,那时候我才意识到,错误处理才是录制的重头戏。

常见录制错误类型

先说说我遇到最多的几类错误。你想想看,录制本质上是在操作两样东西:媒体流和文件写入。所以错误也基本围绕这两块展开。

错误类型 典型场景 错误名称
设备不可用 摄像头被拔掉、麦克风驱动异常 NotReadableError
权限拒绝 用户点了「禁止」、浏览器设置拦截 NotAllowedError
设备被占用 另一个标签页正在使用摄像头 TrackStartError
录制中断 网络抖动导致 MediaRecorder 停止 MediaRecorder 的 onerror
文件写入失败 磁盘空间不足、浏览器沙箱限制 Blob 创建异常

我个人习惯把错误分成两类:可恢复的不可恢复的。比如权限拒绝,用户重新授权就能解决,属于可恢复。而设备硬件损坏,那就只能提示用户换设备了。

设备被占用处理

这个坑我踩过好几次。用户同时打开两个标签页,都调用了摄像头,第二个标签页就会报 TrackStartError。浏览器不会帮你排队,它直接拒绝。

核心思路:捕获到设备占用错误后,引导用户关闭其他占用标签页,或者释放设备。

代码里怎么判断?看错误名称就行:

try {
  const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
  // 开始录制
} catch (err) {
  if (err.name === 'TrackStartError') {
    // 设备被占用
    showToast('摄像头或麦克风被其他应用占用,请关闭后重试');
  } else if (err.name === 'NotReadableError') {
    // 设备不可读
    showToast('设备出现硬件问题,请检查连接');
  }
}

我曾经在项目里加了一个「检测设备状态」的按钮。用户点击后,我们先用 enumerateDevices() 列出所有设备,再逐个尝试获取流。如果某个设备返回 TrackStartError,就标记为「被占用」,在界面上用红色标签显示。这样用户不用猜,一眼就知道哪个设备出了问题。

小技巧:调用 getUserMedia 之前,先调用 enumerateDevices() 检查设备 label 是否为空。如果 label 为空,说明用户还没授权,这时候直接请求权限,而不是报设备占用。

录制中断恢复

录制中断是最让用户崩溃的场景。录了半小时,突然断了,什么都没保存。我遇到过最典型的情况是:用户手机内存不足,浏览器把录制进程杀了。

MediaRecorder 有一个 onerror 事件,但说实话,它给的信息很有限。我建议你主要监听 ondataavailableonstop

let recordedChunks = [];

mediaRecorder.ondataavailable = (event) => {
  if (event.data.size > 0) {
    recordedChunks.push(event.data);
    // 每收到一段数据,就存一次临时文件
    saveTempChunk(event.data);
  }
};

mediaRecorder.onstop = () => {
  if (recordedChunks.length === 0) {
    // 录制中断,没有有效数据
    showToast('录制意外中断,请检查设备状态');
    // 尝试恢复:重新获取流,从上次断点续录
    attemptRecovery();
  } else {
    // 正常结束,合并数据
    const blob = new Blob(recordedChunks, { type: 'video/webm' });
    saveFinalFile(blob);
  }
};

这里有个关键点:分段保存。我习惯设置 timeslice 参数,比如每 5 秒触发一次 ondataavailable。这样即使录制中断,我们也至少保存了前几秒的数据,用户不会一无所获。

注意:分段保存会增加磁盘写入频率,对移动端不太友好。建议在桌面端用 5 秒间隔,移动端用 10 秒。我踩过坑,移动端写入太频繁会导致浏览器卡死。

如果真的发生了中断,怎么恢复?我的做法是:

  1. 保存当前已录制的所有 chunk 到 IndexedDB
  2. 重新调用 getUserMedia 获取新流
  3. 创建新的 MediaRecorder,继续录制
  4. 录制结束后,把新旧 chunk 合并成一个 Blob

说白了,就是手动实现一个「断点续录」。虽然不能做到完全无缝,但至少比全部丢失强得多。

用户权限撤销处理

这个场景比较隐蔽。用户一开始给了权限,录到一半突然去浏览器设置里把摄像头权限关了。这时候你的 MediaRecorder 不会立刻报错,但 getUserMedia 返回的 MediaStream 会变成 inactive 状态。

怎么检测?监听 track.onended 事件:

stream.getTracks().forEach(track => {
  track.onended = () => {
    // 权限被撤销,或者设备被拔出
    console.warn('Track ended unexpectedly:', track.kind);
    mediaRecorder.stop();
    showToast('录制已停止:摄像头或麦克风权限被撤销');
    // 释放资源
    stream.getTracks().forEach(t => t.stop());
  };
});

我曾经遇到一个奇葩情况:用户用的是外接 USB 摄像头,录到一半不小心踢到了 USB 线。摄像头断开,track.onended 触发,但用户重新插上后,浏览器不会自动恢复录制。所以我在界面上加了一个「恢复录制」按钮,用户重新插好设备后,点击按钮就能继续。

最佳实践:权限撤销后,不要立刻清空已录制的数据。把数据暂存起来,等用户重新授权后,尝试合并。如果用户拒绝重新授权,再提示下载已录制的部分。

知识体系总览

下面这张图是我自己梳理的录制错误处理流程,你可以对照着看:

录制错误处理流程 开始录制 getUserMedia 获取流 成功? 错误类型判断 NotAllowedError → 引导授权 TrackStartError → 释放设备 NotReadableError → 检查硬件 开始录制 录制中(分段保存) 中断? 尝试恢复录制

这张图把整个流程串起来了。从开始录制到获取设备,再到错误分支和录制中断恢复,每一步都有对应的处理策略。我个人建议你把这张图打印出来贴在工位上,写代码时对照着看,不容易漏掉边界情况。

我的习惯:每次上线前,我会手动模拟三种错误场景:拔掉摄像头、关闭权限、杀掉录制进程。确保每个错误都有对应的 UI 提示和恢复路径。别嫌麻烦,上线后少挨骂。

录制错误处理这块,说白了就是「防患于未然」加上「亡羊补牢」。把能想到的异常都提前处理了,用户遇到问题时至少不会一脸懵。嗯,今天就聊到这儿,下次我们聊聊录制的性能优化。

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