You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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();

核心原因

  1. iOS WebKit媒体会话优先级:播放音频时系统优先分配资源,可能导致录制流被挂起,超时后触发捕获失败
  2. 全局Audio实例资源残留:复用同一实例会导致音频上下文未正确释放,干扰后续录制
  3. 媒体轨道生命周期未处理:getUserMedia获取的流被系统中断后,录制器仍使用失效轨道空转
  4. 第三方录制库的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 11:57:43