必须先获取合法MediaStream,再经MediaRecorder录制、分片收集、Blob合并、URL.createObjectURL导出;跳过任一环或传入无效流(无音视频轨道)均会失败,且必须监听dataavailable事件并校验流状态。

能直接在前端完成,但必须走 getUserMedia → MediaRecorder → 分片收集 → Blob 合并 → URL.createObjectURL 这条链,跳过任意一环都会失败。
必须先获取合法 MediaStream,不能用 video.src 或空流初始化
MediaRecorder 只认真实、启用的音视频轨道,new MediaRecorder(stream) 传入无效流会立刻抛 TypeError: The stream has no audio and no video tracks。常见错误包括:
- 只调用
getUserMedia({})而没明确写{ video: true, audio: true },旧写法已被废弃 - 权限被拒后静默返回空流,没
catchNotAllowedError就继续往下走 - 流拿到后没检查
stream.getVideoTracks().length > 0,尤其在 iOS Safari 中摄像头可能因后台切换断开 - 把
<video></video>元素本身传给MediaRecorder,它只接受MediaStream对象,不是 DOM 元素
初始化 MediaRecorder 时 mimeType 必须做兼容性检测
video/webm 是最稳的选择,但硬写 video/mp4 在 Firefox 和旧 Safari 里会直接初始化失败。正确做法是:
- 先用
MediaRecorder.isTypeSupported('video/webm')检查,返回true再传入 - 避免加 codecs 参数(如
video/webm;codecs=vp9),Safari 17.5 不支持 VP9,Chrome 却默认用它,导致跨浏览器行为不一致 - 如果只录视频不录音频,
{ video: true, audio: false }获取的流初始化时仍建议用video/webm,别改用video/webm;codecs=vp8—— 多余指定反而降低兼容性
dataavailable 事件不是“结束才触发”,漏监听就等于没录到数据
很多人只监听 stop 事件,却忘了真正装数据的是 dataavailable。这个事件的行为取决于是否设了 timeslice:
立即学习“前端免费学习笔记(深入)”;
- 没设
timeslice:只在recorder.stop()后触发一次,event.data是全部内容 - 设了
recorder.start(1000):每秒触发一次,每次event.data是一个分片 Blob,类型必须统一(不能混video/webm和audio/webm) - 移动端(尤其 iOS Safari)可能在切后台时不触发
stop或error,建议监听document.addEventListener('visibilitychange', ...)主动stop() - 录制末尾几秒常丢失,可在
stop()前加一句recorder.requestData()强制输出最后一块
合并 Blob 后必须用 URL.createObjectURL,不能直接 fetch 或 base64
生成的 Blob 是内存对象,生命周期仅限当前 JS 上下文。导出时容易踩的坑:
-
atob()解码event.data?错——event.data是原始二进制Blob,不是 base64 字符串 - 用
fetch()发送到后端再回传下载链接?没必要,纯前端就能搞定 - 忘记检查
chunks.length === 0,结果生成空文件还提示“下载成功” -
URL.createObjectURL(blob)创建的链接必须在用户交互中触发下载(比如按钮点击),否则 Safari 会拦截 - 下载完成后记得调用
URL.revokeObjectURL(url)释放内存,尤其在 Vue/React 组件卸载前
整个流程里最容易被忽略的是流状态校验和 dataavailable 的必监听——前者决定能不能开始,后者决定有没有数据。只要这两点卡住,后面所有操作都只是在处理空数组。



















