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

使用Expo Audio API遇类型转换错误问题求助

问题:Expo Audio API切换音频/重进页面报错

在已执行eject操作的React Native项目中使用新版Expo Audio API,首次进入页面播放音频时无报错,但切换音频或重新进入该页面时触发错误:
Error: The 1st argument cannot be cast to type expo.modules.audio.AudioPlayer (received class java.lang.Integer)

代码片段

function LoadedPlayer({ book }: { book: book }) {
  const bookProgress = useAppSelector(
    (state) => state.books.bookProgress[book.id || ""]
  );

  let audioSource = {
    uri: getAudioURL(book?.chapters[0].audio_id),
  };

  let AudioPlayer = useAudioPlayer(audioSource, 250);
  const AudioPlayerStatus = useAudioPlayerStatus(AudioPlayer);

  const sliderTouchableRef = useRef<View>(null);

  const loadChapterToTrack = (chapterNumber: number) => {
    if (!book.chapters || !book.chapters[chapterNumber] || !AudioPlayer) {
      return;
    }
    try {
      AudioPlayer.replace({
        uri: getAudioURL(book.chapters[chapterNumber].audio_id),
      });
    } catch (e) {}
    dispatch(
      updateBookProgress({
        bookId: book.id || "",
        currentChapter: chapterNumber,
        currentProgressSeconds: 0,
      })
    );
  };

  useEffect(() => {
    setAudioModeAsync({
      shouldPlayInBackground: true,
    });
    if (!bookProgress) {
      dispatch(
        updateBookProgress({
          bookId: book.id || "",
          currentChapter: 0,
          currentProgressSeconds: 0,
        })
      );
    }

    (async () => {
      if (!book.chapters) {
        console.error("No chapters found for this book.");
        return;
      }
      // Start playing it
      AudioPlayer.play();
      if (bookProgress?.currentProgressSeconds) {
        AudioPlayer.seekTo(bookProgress.currentProgressSeconds);
      }
    })();

    setTimeout(() => {
      dispatch(addToCurrentlyReading(book.id || ""));
    }, 5000);
    const progressInterval = setInterval(() => {
      if (AudioPlayer.currentTime && AudioPlayer.duration) {
        dispatch(
          updateBookProgress({
            bookId: book.id || "",
            currentChapter: bookProgress?.currentChapter || 0,
            currentProgressSeconds: AudioPlayer.currentTime,
          })
        );
      }
    }, 5000);

    return () => {
      clearInterval(progressInterval);
      AudioPlayer.release();
    };
  }, []);

  useEffect(() => {
    if (AudioPlayerStatus?.playbackState === "ended") {
      console.log("ended");
      loadChapterToTrack(bookProgress?.currentChapter + 1 || 0);
    }
  }, [AudioPlayerStatus.playbackState]);

  return (
    <ScreenContents />
  );
}

问题原因

  1. 闭包 stale 引用:第一个useEffect依赖数组为空,内部捕获的AudioPlayer、bookProgress都是组件初始渲染时的旧值。当组件重新渲染(切换章节、重进页面),新的AudioPlayer实例生成,但旧的useEffect回调仍在使用已被release的旧实例,调用replace或play时触发类型转换错误。
  2. 播放器生命周期管理不当:组件卸载时调用AudioPlayer.release()后,仍有代码(如loadChapterToTrack、定时器)尝试访问已释放的实例,此时实例内部状态失效,被转换为Integer类型标识,导致参数类型不匹配。

解决方案

1. 修复useEffect依赖数组

将useEffect中用到的所有外部变量加入依赖数组,确保回调能获取最新的实例和状态:

useEffect(() => {
  // ... 原有逻辑
}, [AudioPlayer, bookProgress, book.chapters, book.id]);

2. 用Ref保存AudioPlayer实例

避免闭包中的 stale 引用,将AudioPlayer存入ref,确保后续操作访问最新实例:

const audioPlayerRef = useRef<ReturnType<typeof useAudioPlayer> | null>(null);
audioPlayerRef.current = useAudioPlayer(audioSource, 250);
const AudioPlayerStatus = useAudioPlayerStatus(audioPlayerRef.current);

// 后续操作改为使用audioPlayerRef.current
const loadChapterToTrack = (chapterNumber: number) => {
  const player = audioPlayerRef.current;
  if (!book.chapters || !book.chapters[chapterNumber] || !player) {
    return;
  }
  try {
    player.replace({
      uri: getAudioURL(book.chapters[chapterNumber].audio_id),
    });
  } catch (e) {}
  // ... 其他逻辑
};

3. 完整清理定时器与播放器

确保组件卸载时清理所有定时器,避免内存泄漏和无效实例访问:

useEffect(() => {
  const addReadingTimeout = setTimeout(() => {
    dispatch(addToCurrentlyReading(book.id || ""));
  }, 5000);
  const progressInterval = setInterval(() => {
    const player = audioPlayerRef.current;
    if (player?.currentTime && player.duration) {
      dispatch(
        updateBookProgress({
          bookId: book.id || "",
          currentChapter: bookProgress?.currentChapter || 0,
          currentProgressSeconds: player.currentTime,
        })
      );
    }
  }, 5000);

  return () => {
    clearInterval(progressInterval);
    clearTimeout(addReadingTimeout);
    audioPlayerRef.current?.release();
  };
}, [audioPlayerRef.current, bookProgress, book.id]);

4. 前置检查播放器有效性

在调用播放器方法前,先确认实例是否存在且可用:

(async () => {
  const player = audioPlayerRef.current;
  if (!book.chapters || !player) {
    console.error("No chapters found or invalid audio player.");
    return;
  }
  player.play();
  if (bookProgress?.currentProgressSeconds) {
    player.seekTo(bookProgress.currentProgressSeconds);
  }
})();

内容的提问来源于stack exchange,提问作者10xNerd

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 14:52:24