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

Expo Audio在iOS端Chrome和Safari(真机)播放连续音频片段的异常问题及解决方案咨询

Expo Audio在iOS端Chrome和Safari(真机)播放连续音频片段的异常问题及解决方案咨询

大家好,我最近基于Expo Audio做了一个按列表播放音频片段的功能,碰到了一个只在iOS真机上出现的诡异问题,想请教下有没有同学遇到过类似情况,或者有什么解决思路?

问题背景

这个功能是接收一组音频URL的列表,按顺序播放,列表里的单元格可能是音频段或者暂停段。在除了iOS Chrome和Safari(真机)之外的所有浏览器里都运行流畅,甚至iOS模拟器上的Safari和Chrome iOS都能正常工作,但iOS真机的Safari就是不行。

具体诡异现象

  • 用户第一次点击播放后,第一个音频单元格能正常播放
  • 当从第一个单元格切换到第二个单元格(不管是什么类型,中间一定会经过一个暂停单元格)时,从日志能看到代码已经触发了所有播放逻辑,但音频就是完全不播放
  • 只要用户手动点一下“下一曲”按钮,或者切换一次播放/暂停状态,之后所有的播放就都正常了,再也不会出问题

之前还遇到过Safari iOS的权限拒绝错误,后来调整了暂停的异步逻辑后,Chrome iOS的问题解决了,但Safari真机的问题依然存在——它就像没识别到第一次的播放交互一样,完全不响应后续的自动播放触发。

已尝试的临时方案&调整

  1. 目前临时的解决办法是在第一次切换失败时显示“点击继续”按钮,用户点一次后就正常了,但这样用户需要点两次才能进入正常播放流程,体验不太好
  2. 调整Chrome iOS的暂停逻辑:把原本不必要的异步暂停函数改成同步,成功解决了Chrome iOS的问题
  3. 在代码里加了异常检测逻辑:当isPlaying为true但actualIsPlaying为false时,延迟2秒后尝试重新替换音频源并播放,但对Safari真机无效

想请教的问题

  1. 有没有同学碰到过这种iOS真机Safari上Expo Audio自动播放失效的情况?你们是怎么解决的?
  2. 有没有什么“预激活”音频上下文的技巧,能让用户只点一次播放就能正常走完全流程,不需要额外手动触发?

核心代码片段

import { useAudioPlayer, useAudioPlayerStatus } from "expo-audio";
import React, { useCallback, useEffect, useRef } from "react";
import AudioControls from "./AudioControls";

interface Cell {
  text: string;
  audioUrl: string | null;
  isPause: boolean;
}

interface AudioPlayerProps {
  cells: Cell[];
  currentCellIndex: number;
  onCellChange: (index: number) => void;
  isPlaying: boolean;
  onPlayStateChange: (playing: boolean) => void;
  onTranscriptPress?: () => void;
  onFinish?: () => void;
  onPauseTimerStateChange?: (isActive: boolean) => void;
  onPlaybackBlocked?: (reason: string) => void;
}

export default function AudioPlayer({
  cells,
  currentCellIndex,
  onCellChange,
  isPlaying,
  onPlayStateChange,
  onTranscriptPress,
  onFinish,
  onPlaybackBlocked,
}: AudioPlayerProps) {
  const currentAudioUrl = cells[currentCellIndex]?.audioUrl ?? require("../../assets/5-seconds-of-silence.mp3");
  const player = useAudioPlayer();
  const { didJustFinish, playing: actualIsPlaying } = useAudioPlayerStatus(player);
  const isPlayingRef = useRef(isPlaying);

  // 播放速率设为1.0
  const PLAYBACK_RATE = 1.0;

  // 检测预期播放但实际未播放的异常
  useEffect(() => {
    console.log("isActuallyPlaying", actualIsPlaying);
    if (isPlaying && !actualIsPlaying) {
      const detectionDelay = 2000; // 2秒延迟避免音频启动时误判
      const timeoutId = setTimeout(() => {
        if (isPlaying && !actualIsPlaying) {
          const reason = "Browser autoplay restrictions or playback issues detected";
          console.warn(
            "AudioPlayer: Detected discrepancy - expected playing but audio is not actually playing"
          );
          console.warn(
            "This could be due to browser autoplay restrictions or other playback issues"
          );
          onPlaybackBlocked?.(reason);
          
          // 尝试重新加载音频并播放
          player.replace({ uri: currentAudioUrl });
          player.play();
        }
      }, detectionDelay);

      return () => clearTimeout(timeoutId);
    }
  }, [
    isPlaying,
    actualIsPlaying,
    currentCellIndex,
    cells,
    player,
    onPlayStateChange,
    onPlaybackBlocked,
    currentAudioUrl,
  ]);

  const handleAudioComplete = useCallback(() => {
    console.log("handleAudioComplete", {
      currentCellIndex,
      cellsLength: cells.length,
      isPlaying: isPlayingRef.current,
      nextIndex: currentCellIndex + 1,
    });
    if (!isPlayingRef.current) {
      console.log("handleAudioComplete: not playing, returning early");
      return;
    }
    const nextIndex = currentCellIndex + 1;
    if (nextIndex < cells.length) {
      onCellChange(nextIndex);
    } else {
      onFinish?.();
    }
  }, [currentCellIndex, cells.length, isPlayingRef, onCellChange, onFinish]);

  // 省略其他状态监听与控制逻辑
}

有没有什么办法能提前“激活”音频上下文,让第一次播放后的自动切换能正常工作?或者有没有我没注意到的iOS Safari播放限制细节?

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 11:08:11