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

ReactJS中HTMLAudioElement pause方法不触发问题排查

问题排查与解决方案

核心问题分析

你的代码存在两个关键问题导致无法暂停音频:

  1. 多次创建Audio实例且未清理旧实例:当metadata更新时,useEffect会重新执行并创建新的Audio对象赋值给audio.current,但之前播放的旧Audio实例并未被暂停或销毁,仍然在后台播放。此时调用pause()操作的是新创建的未播放实例,旧实例不受影响,自然无法暂停。
  2. 异步元数据加载时机问题:fetchMetadata是异步函数,你在useEffect中直接调用却未等待其完成,可能导致第一次执行useEffect时metadata仍为undefined,后续metadata更新后又重复创建Audio实例,加剧多实例问题。

修复代码

export const Player = (): JSX.Element => {
  const [playingAudio, setPlayingAudio] = useState<boolean>(false);

  const { fetchMetadata, metadata } = useContract();

  const theme: Theme = useTheme();

  const audio = useRef<HTMLAudioElement | null>(null);

  useEffect(() => {
    // 定义异步函数并等待执行
    const initPlayer = async () => {
      await fetchMetadata();
      // 清理旧实例:如果之前有Audio实例,先暂停并销毁
      if (audio.current) {
        audio.current.pause();
        audio.current.src = ''; // 清空资源释放内存
        audio.current = null;
      }
      if (metadata) {
        audio.current = new Audio(metadata.animation_url);
        if (audio.current) {
          audio.current.onpause = () => setPlayingAudio(false);
          audio.current.onended = () => setPlayingAudio(false);
          audio.current.onplay = () => setPlayingAudio(true);
        }
      }
    };

    initPlayer();

    // 组件卸载或依赖变更时清理Audio实例
    return () => {
      if (audio.current) {
        audio.current.pause();
        audio.current.src = '';
        audio.current = null;
      }
    };
  }, [fetchMetadata, metadata]);

  const playAudio = async (): Promise<void> => {
    await audio?.current?.play();
  };

  const pauseAudio = (): void => {
    audio?.current?.pause();
  };

  return (
    <Card sx={{ width: '100%', maxWidth: '600px', display: 'flex', justifyContent: 'space-between', marginY: 3 }}>
      <Box sx={{ display: 'flex', flexDirection: 'column' }}>
        <CardContent sx={{ flex: '1 0 auto' }}>
          <Typography component="div" variant="h5">
            {metadata?.name}
          </Typography>
          <Typography variant="subtitle1" color="text.secondary" component="div">
            {metadata?.description}
          </Typography>
        </CardContent>
        <Box sx={{ display: 'flex', alignItems: 'center', pl: 1, pb: 1 }}>
          <IconButton aria-label="previous">
            {theme.direction === 'rtl' ? <SkipNextIcon /> : <SkipPreviousIcon />}
          </IconButton>
          {playingAudio ? (
            <IconButton aria-label="play/pause" onClick={pauseAudio}>
              <PauseCircle sx={{ height: 38, width: 38 }} />
            </IconButton>
          ) : (
            <IconButton aria-label="play/pause" onClick={playAudio}>
              <PlayArrowIcon sx={{ height: 38, width: 38 }} />
            </IconButton>
          )}
          <IconButton aria-label="next">
            {theme.direction === 'rtl' ? <SkipPreviousIcon /> : <SkipNextIcon />}
          </IconButton>
        </Box>
      </Box>
      <CardMedia
        component="img"
        sx={{ width: 200 }}
        image={metadata?.image}
        alt={metadata?.name}
      />
    </Card>
  );
};

关键修复点

  • 添加实例清理逻辑:在useEffect的清理函数(返回的函数)中,暂停旧Audio实例并清空资源,避免组件卸载或依赖变更时残留播放中的实例。同时在每次初始化新实例前,先清理旧实例。
  • 等待异步元数据加载:将fetchMetadata的调用包裹在异步函数中并使用await,确保metadata加载完成后再创建Audio实例,避免因元数据未就绪导致的无效实例创建。
  • 避免多实例冲突:每次metadata更新时,先销毁旧的Audio实例,再创建新实例,保证audio.current始终指向当前有效的播放实例。

内容的提问来源于stack exchange,提问作者Balint Laszlo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 22:10:34