如何解决ios中audio音频无法播放问题?

问题

在ios的浏览器,audio无法正常的播放音频(但是有些ios设备可以正常播放出来),其中有一次报错为Uncaught (in promise) {"name": "NotAllowedError", "message": "The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission.", "stack": "play@[native code]\nkt@https://pingtanchat.10wei.top/static/js/index-C3rnxwNY.js:148...9"}

描述:

大概的逻辑为,初始化 MediaSource 并设置播放器的源,接收音频数据并将其添加到队列中,然后逐个处理队列中的数据,监听audio-play,设置player播放和暂停,audio-play的触发时机是在获取到音频数据的时候,触发一次,以下是代码

const MyAudio = () => {
  const player = useRef(null);
  let queue = useRef([]); // 队列,用于管理待处理的音频数据
  let sourceBuffer = useRef(null);
  let mediaSource = useRef(null);

  // 处理队列中的数据
  const processQueue = () => {
    if (queue.current.length > 0 && !sourceBuffer.current.updating) {
      sourceBuffer.current.appendBuffer(queue.current.shift());
    }
  };

  // 接收二进制的音频数据,并读取
  useEventBus('Global/tts_addSourceBuffer', (data) => {
    const reader = new FileReader(); // 创建 FileReader 对象读取二进制数据
    reader.onload = () => {
      const arrayBuffer = reader.result; // 获取读取结果(ArrayBuffer)
      queue.current.push(arrayBuffer); // 将数据添加到队列
      processQueue(); // 处理队列中的数据
    };
    reader.readAsArrayBuffer(data); // 读取 Blob 数据为 ArrayBuffer
  });

  // 初始化
  useEventBus('Global/initSourceBuffer', () => {
    safeExecute(() => {
      mediaSource.current = new MediaSource(); // 创建 MediaSource 实例
      player.current.src = URL.createObjectURL(mediaSource.current); // 设置播放器的源为 MediaSource 对象
    });

    // 处理 sourceopen 事件
    const handleSourceopen = () => {
      sourceBuffer.current = mediaSource.current.addSourceBuffer('audio/mpeg');
      sourceBuffer.current.addEventListener('updateend', processQueue);
    };

    player.current.addEventListener('error', (e) => {
      console.error('数字人播放音频错误:', e);
    });

    // 监听 MediaSource 的 sourceopen 事件
    mediaSource.current.addEventListener('sourceopen', handleSourceopen);
  });

  // 监听 播放和暂停
  useEventBus('Global/audio-play', ({ type }) => {
    if (type === 'start') {
      player.current &&
        player.current.play().catch((error) => {
          console.error('播放失败:', error);
          alert('由于浏览器安全策略,音频无法自动播放,请点击播放按钮手动播放。');
        });
    } else if (type === 'end') {
      player.current && player.current.pause();
    }
  });

  const isMute = useSelector((state) => state.chat.isMute);
  useEffect(() => {
    player.current.muted = !!isMute;
  }, [isMute]);
  return <audio ref={player} src="" />;
};

希望可以有一个兼容ios的方案,因为业务逻辑是需要在有音频数据的时候,自动播放音频,不太可能去手动点击,播放音频

修改部分

下方代码是修改版本,可以自动播放,并且有声音了,但是出现一个问题,如果blobQueue.current.shift()取到的blob数据太多,audio播放的音频时间长,会出现播放的过程中突然没有声音的问题,并且进度播完的时候,不会触发onEnded,有没有大佬懂这是什么原因

import { useEventBus } from 'event-bus-hooks';
import { useSelector } from 'react-redux';

const IosAudio = () => {
  const player = useRef(null);
  const blobQueue = useRef([]); // 用于存储 Blob 的队列
  const currentIndex = useRef(0);

  const handlePlay = () => {
    if (currentIndex.current > blobQueue.current.length - 1) {
      currentIndex.current = 0;
      return;
    }
    console.log('------------ 下一个 -------------');
    let obj = blobQueue.current.shift();
    console.log(obj, obj.length);
    const blob = new Blob(obj, { type: 'audio/mp3' }); // 合并队列中的 Blob
    player.current.src = URL.createObjectURL(blob);
    player.current.load();
  };

  // 接收二进制的音频数据(Blob 格式),并处理
  useEventBus('Global/tts_BlobList', (blobDataArr) => {
    blobQueue.current.push(blobDataArr);
  });

  const handleStop = () => {
    if (!player.current) return;
    blobQueue.current = [];
    player.current.pause();
    // 释放资源
    URL.revokeObjectURL(player.current.src);
  };

  const loadedmetadata = () => {
    console.log('音频元数据加载完成');
    player.current
      .play()
      .then(() => {
        console.log('播放成功');
      })
      .catch((error) => {
        console.error('播放失败:', error);
      });
  };

  // 播放结束
  const onended = () => {
    currentIndex.current++;
    handlePlay();
  };

  // 监听播放和暂停
  useEventBus('Global/audio-play', ({ type }) => {
    if (type === 'start') {
      handlePlay();
    } else if (type === 'end') {
      handleStop();
    }
  });

  const isMute = useSelector((state) => state.chat.isMute);
  useEffect(() => {
    player.current.muted = !!isMute;
  }, [isMute]);

  return (
    <audio
      ref={player}
      src=""
      onLoadedMetadata={loadedmetadata}
      onEnded={onended}
      onError={(e) => console.error('数字人播放音频错误:', e.message)}
      controls
    />
  );
};

export default IosAudio;
阅读 840
avatarAI BotBETA

回答

iOS 对自动播放音频有严格的限制,特别是在移动设备上,为了防止滥用和节省用户的数据流量及电量,iOS 要求音频播放必须是由用户的直接交互触发的。针对你描述的情况,可以尝试以下解决方案:

  1. 用户交互触发播放
    由于 iOS 的限制,无法完全实现自动播放。因此,建议在设计上改为用户交互触发播放。例如,可以在页面加载时显示一个“播放音频”的按钮,用户点击后才开始播放。
  2. 使用 Web Audio API
    对于需要自动播放的场景,可以考虑使用 Web Audio API 来播放音频。虽然这仍然受限于用户交互的初始触发,但一旦用户交互后,你可以更灵活地控制音频的播放、暂停等。
  3. 调整 autoplay 策略
    如果你的音频播放是页面加载的一部分,考虑将音频播放逻辑调整为页面加载完成后,通过某种用户交互(如滚动页面、点击按钮等)来触发播放。
  4. 处理权限问题
    从错误信息来看,还涉及到用户权限问题。确保你的应用已经请求并获得了用户音频播放的权限。如果权限被拒绝,你需要处理这种情况,并向用户解释为什么需要这个权限。
  5. 使用 playsinline 属性
    <audio><video> 标签中添加 playsinline 属性,这有助于避免在某些浏览器中(特别是 iOS 的 Safari)自动全屏播放音频或视频。
  6. 错误处理
    在你的代码中,已经添加了错误处理逻辑,这是很好的。确保在捕获到错误时,向用户显示有用的信息,并引导他们如何解决问题(如点击播放按钮)。

综上所述,由于 iOS 的限制,完全自动播放音频在大多数情况下是不可行的。你需要调整你的应用逻辑,以适应这些限制。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏