iOS端音频录播循环异常:MediaStreamTrack捕获失败报错求助
iOS 下音频录制捕获失败问题解决方案
问题背景
实现类对话循环任务:播放音频轨道后录制用户语音,按1秒分片发送至服务器。iOS设备上偶现异常:ondataavailable持续触发但录制无声音,约10秒后控制台报错:
A MediaStreamTrack ended due to a capture failure
相关代码:
播放器代码
const audio = new Audio(); audio.loop = false; const playAudio = (base64: string) => new Promise((resolve, reject) => { audio.onended = resolve; audio.onerror = reject; audio.src = base64; audio.play().catch(reject); });
录制器代码
import { IMediaRecorder, MediaRecorder, register, } from 'extendable-media-recorder'; import { connect } from 'extendable-media-recorder-wav-encoder'; let mediaRecorder: IMediaRecorder; navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true } }) .then(stream => (mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/wav' }))); export const start = async (listener: (buffer: Blob) => void) => { stop(); mediaRecorder?.ondataavailable = ({ data }) => listener(blob); mediaRecorder?.start(1000); }; export const stop = () => mediaRecorder?.stop();
核心原因
- iOS WebKit媒体会话优先级:播放音频时系统优先分配资源,可能导致录制流被挂起,超时后触发捕获失败
- 全局
Audio实例资源残留:复用同一实例会导致音频上下文未正确释放,干扰后续录制 - 媒体轨道生命周期未处理:
getUserMedia获取的流被系统中断后,录制器仍使用失效轨道空转 - 第三方录制库的iOS兼容性问题:编码器未正确处理轨道状态变化
解决方案
1. 重构播放逻辑,避免资源残留
每次播放创建新Audio实例,播放完成后主动清空资源释放会话:
const playAudio = (base64: string) => new Promise((resolve, reject) => { const audio = new Audio(); audio.onended = () => { audio.src = ''; audio.pause(); resolve(null); }; audio.onerror = reject; audio.src = base64; audio.play().catch(reject); });
2. 监听轨道状态,自动重建录制流
当音频轨道触发ended/error事件时,重新获取媒体流并初始化录制器:
import { IMediaRecorder, MediaRecorder, register, } from 'extendable-media-recorder'; import { connect } from 'extendable-media-recorder-wav-encoder'; let mediaRecorder: IMediaRecorder | null = null; let audioStream: MediaStream | null = null; const initRecorder = async () => { // 释放旧资源 if (audioStream) { audioStream.getTracks().forEach(track => track.stop()); } // 获取带明确约束的音频流 audioStream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, sampleRate: 16000, channelCount: 1, latency: 0.05 } }); // 监听轨道异常,自动重建 const audioTrack = audioStream.getAudioTracks()[0]; audioTrack.addEventListener('ended', initRecorder); audioTrack.addEventListener('error', initRecorder); mediaRecorder = new MediaRecorder(audioStream, { mimeType: 'audio/wav' }); // 监听录制错误 mediaRecorder.addEventListener('error', () => { stop(); initRecorder(); }); }; // 提前初始化 initRecorder(); export const start = async (listener: (buffer: Blob) => void) => { stop(); if (!mediaRecorder) await initRecorder(); mediaRecorder.ondataavailable = ({ data }) => { // 过滤空分片 if (data.size > 0) listener(data); }; mediaRecorder.start(1000); }; export const stop = () => { mediaRecorder?.stop(); };
3. 严格控制播放与录制的时序
确保播放完全结束后再启动录制,避免资源竞争:
// 对话流程示例 const runConversationTurn = async (audioBase64: string, onRecordChunk: (blob: Blob) => void) => { await playAudio(audioBase64); start(onRecordChunk); // 后续可通过用户操作或服务器指令停止录制 };
内容的提问来源于stack exchange,提问作者Artur
相关产品推荐
相关产品推荐

