如何解决MediaRecorder API在移动端浏览器无法存储录制流的问题
移动端MediaRecorder录屏失效常见原因及修复方案
核心问题排查点
- getDisplayMedia兼容性限制
iOS Safari仅14及以上版本支持屏幕共享,安卓端大部分第三方浏览器(微信内置、QQ浏览器等)默认禁用getDisplayMedia权限,且iOS端getDisplayMedia不支持audio: true参数,传入会直接抛出异常。 - MimeType不匹配
你代码中硬指定Blob类型为video/mp4,但绝大多数安卓移动浏览器默认支持的录制格式为video/webm,只有高版本Safari支持mp4格式录制,MediaRecorder初始化时如果传入不支持的格式会直接静默失败。 - AudioContext被自动暂停
移动端浏览器存在音频自动播放限制,非用户交互(点击/触摸)触发的AudioContext会默认处于suspended状态,你当前在componentDidMount生命周期中创建AudioContext,没有用户触发的前提下无法正常输出音频流,导致合并后的MediaStream异常。 - ondataavailable未触发
桌面端MediaRecorder不传时间分片参数也会自动触发ondataavailable,但移动端必须显式传入timeslice参数才会按周期返回录制分片,你代码中未传该参数,只有调用mediaRecorder.stop()时才会返回一次数据,不主动停止的话永远拿不到录制内容。 - 逻辑漏洞问题
你当前在每次ondataavailable触发时就调用download生成Blob,会导致每次生成的Blob仅包含当前分片,且频繁生成大体积Blob会触发移动端内存限制导致进程崩溃。另外如果本地/远端流没有音频轨,mergeAudioStreams中调用undefined的connect方法会直接报错。
修复代码示例
// 提前检测支持的MimeType const getSupportedMimeType = () => { const types = [ 'video/webm;codecs=vp8,opus', 'video/mp4;codecs=h264,aac', 'video/webm', 'video/mp4' ] return types.find(type => MediaRecorder.isTypeSupported(type)) } // 录制逻辑必须绑定到用户点击/触摸事件触发,不要放在componentDidMount自动执行 handleStartRecord = async () => { // 先恢复AudioContext,避免被暂停 const audioCtx = new (window.AudioContext || window.webkitAudioContext)() if (audioCtx.state === 'suspended') { await audioCtx.resume() } // 移动端不要给getDisplayMedia传audio: true recordingStream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ? false : true, }); const mergedAudio = await this.mergeAudioStreams(audioCtx); const tracks = [ ...recordingStream.getVideoTracks(), ...mergedAudio, ]; captureStream = new MediaStream(tracks); // 传入支持的编码格式 const options = { mimeType: getSupportedMimeType() } mediaRecorder = new MediaRecorder(captureStream, options); mediaRecorder.ondataavailable = this.handleDataAvailable; // 录制结束时再生成文件 mediaRecorder.onstop = this.download; // 传入1000ms分片,保证每1秒返回一次数据 mediaRecorder.start(1000); } handleDataAvailable = async event => { if (event.data.size > 0) { recordedChunks.push(event.data); } }; download = async () => { const mimeType = getSupportedMimeType() var blob = new Blob(recordedChunks, { type: mimeType, }); // 调用API存储视频 } mergeAudioStreams = async (ctx) => { const dest = ctx.createMediaStreamDestination(); let localSource, remoteSource; const localGain = ctx.createGain(); const remoteGain = ctx.createGain(); localGain.gain.value = 0.7; remoteGain.gain.value = 0.7; if (this.state.localStream?.getAudioTracks().length > 0) { localSource = ctx.createMediaStreamSource(this.state.localStream); localSource.connect(localGain).connect(dest); } if (this.state.selectedVideo?.stream?.getAudioTracks().length > 0) { remoteSource = ctx.createMediaStreamSource( this.state.selectedVideo.stream ); remoteSource.connect(remoteGain).connect(dest); } return dest.stream.getAudioTracks(); };
额外注意事项
- iOS端页面切后台后录制会自动终止,需要引导用户录制期间保持页面在前台
- 部分安卓浏览器不支持多音轨合并,测试时需要单独验证音视频是否正常同步
- 录制前需要提前申请屏幕录制、麦克风、摄像头权限,避免权限被拒导致流程中断
内容的提问来源于stack exchange,提问作者Jayna Tanawala
相关产品推荐
相关产品推荐

