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

React页面切换时自动停止后台音频播放的实现问询

页面切换时音频持续播放的解决方法

我基于React Hooks开发了音频播放器组件,嵌套在BirdCard组件中并用于14个不同页面。播放器功能正常,但页面切换时音频仍在后台播放。尝试用beforeunload事件实现自动停止但效果不理想,求可行的实现方案。

相关代码

AudioPlayer组件

const AudioPlayer = ({ song }) => {
  const [isPlaying, setIsPlaying] = useState(false);
  const [play, { pause, duration, sound }] = useSound(song);
  const [seconds, setSeconds] = useState();

  const playingButton = () => {
    if (isPlaying) {
      pause(); // 暂停音频
      setIsPlaying(false);
    } else {
      play(); // 播放音频
      setIsPlaying(true);
    }
  };
  const [currTime, setCurrTime] = useState({
    min: "",
    sec: "",
  }); // 音频当前播放位置(分/秒)

  const sec = duration / 1000;
  const min = Math.floor(sec / 60);
  const secRemain = Math.floor(sec % 60);
  const time = {
    min: min,
    sec: secRemain,
  };

  useEffect(() => {
    const interval = setInterval(() => {
      if (sound) {
        setSeconds(sound.seek([])); // 更新当前播放秒数
        const min = Math.floor(sound.seek([]) / 60);
        const sec = Math.floor(sound.seek([]) % 60);
        setCurrTime({
          min,
          sec,
        });
      }
    }, 1000);
    return () => clearInterval(interval);
  }, [sound]);

  const audioRef = useRef(null);

  useEffect(() => {
    const handleUnload = () => {
      audioRef.current.pause();
      audioRef.current.currentTime = 0;
    };

    window.addEventListener("beforeunload", handleUnload);

    return () => {
      window.removeEventListener("beforeunload", handleUnload);
    };
  }, []);
  return (
    <div ref={audioRef} className='items-center mx-auto text-center'>
      <div>
        {!isPlaying ? (
          <button className='playButton' onClick={playingButton}>
            <IconContext.Provider value={{ size: "40px", color: "#28332B" }}>
              <AiFillPlayCircle />
            </IconContext.Provider>
          </button>
        ) : (
          <button className='playButton' onClick={playingButton}>
            <IconContext.Provider value={{ size: "40px", color: "#28332B" }}>
              <AiFillPauseCircle />
            </IconContext.Provider>
          </button>
        )}
      </div>
      <div className='flex items-center space-x-2'>
        <span className='text-[6px] font-["Helvetica_Neue"]'>
          {currTime.min}:{currTime.sec}
        </span>

        <input
          type='range'
          min='0'
          max={duration / 1000}
          default='0'
          value={seconds}
          className='accent-[#28332B] flex-1'
          onChange={(e) => {
            sound.seek([e.target.value]);
          }}
        />
        <span className='text-[6px] font-["Helvetica_Neue"]'>
          {time.min}:{time.sec}
        </span>
      </div>
    </div>
  );
};

export default AudioPlayer;

BirdCard组件

import React from "react";
import { LazyLoadImage } from "react-lazy-load-image-component";
import "react-lazy-load-image-component/src/effects/blur.css";
import AudioPlayer from "./AudioPlayer";

const BirdCard = ({ birdImage, song, birdName }) => {
  return (
    <div className='w-full bg-[#f7f7f7] bg-opacity-60  backdrop-filter  h-[340px]'>
      <div>
        <LazyLoadImage
          effect='blur'
          className='z-50 bg-contain opacity-100 bg-inherit'
          src={birdImage}
          alt=''
          loading='lazy'
        />
      </div>
      <div>
        <h3 className="text-[20px] font-['Times_New_Roman']">{birdName}</h3>
      </div>
      <div className='px-2'>
        <AudioPlayer song={song} />
      </div>
    </div>
  );
};

export default BirdCard;

解决方案

方案1:组件卸载时自动暂停

beforeunload仅在页面刷新或关闭时触发,单页应用的路由切换不会触发该事件。直接在AudioPlayer组件的卸载回调中处理暂停逻辑:

修改AudioPlayer中的定时器useEffect,添加卸载时的暂停操作:

useEffect(() => {
  const interval = setInterval(() => {
    if (sound) {
      setSeconds(sound.seek([]));
      const min = Math.floor(sound.seek([]) / 60);
      const sec = Math.floor(sound.seek([]) % 60);
      setCurrTime({ min, sec });
    }
  }, 1000);

  // 组件卸载时暂停音频
  return () => {
    clearInterval(interval);
    if (sound && isPlaying) {
      pause();
      setIsPlaying(false);
    }
  };
}, [sound, isPlaying, pause]);

同时删除原有的beforeunload相关代码,该事件不适用路由切换场景。

方案2:监听路由变化(React Router场景)

如果项目使用React Router,可在根组件中全局监听路由切换,触发音频暂停:

import { useLocation } from 'react-router-dom';

function App() {
  const location = useLocation();

  useEffect(() => {
    // 路由切换时,模拟点击暂停按钮
    const activePlayButtons = document.querySelectorAll('.playButton svg[AIFILLPAUSECIRCLE]');
    activePlayButtons.forEach(btn => btn.parentElement.click());
  }, [location.pathname]);

  // ...其他根组件代码
}

方案3:全局状态管理(推荐)

用React Context管理全局播放状态,确保同一时间仅一个音频播放,且路由切换时自动暂停:

  1. 创建音频状态Context:
import { createContext, useContext, useState, useEffect } from 'react';
import { useLocation } from 'react-router-dom';

const AudioContext = createContext();

export function AudioProvider({ children }) {
  const [currentSound, setCurrentSound] = useState(null);
  const location = useLocation();

  const playAudio = (soundInstance) => {
    // 暂停正在播放的音频
    if (currentSound) {
      currentSound.pause();
    }
    setCurrentSound(soundInstance);
  };

  const pauseAudio = () => {
    if (currentSound) {
      currentSound.pause();
      setCurrentSound(null);
    }
  };

  // 路由切换时自动暂停
  useEffect(() => {
    pauseAudio();
  }, [location.pathname]);

  return (
    <AudioContext.Provider value={{ playAudio, pauseAudio }}>
      {children}
    </AudioContext.Provider>
  );
}

export function useAudio() {
  return useContext(AudioContext);
}
  1. 在AudioPlayer中使用Context:
const AudioPlayer = ({ song }) => {
  const { playAudio, pauseAudio } = useAudio();
  const [isPlaying, setIsPlaying] = useState(false);
  const [play, { pause, duration, sound }] = useSound(song);

  const playingButton = () => {
    if (isPlaying) {
      pause();
      pauseAudio();
      setIsPlaying(false);
    } else {
      play();
      playAudio(sound);
      setIsPlaying(true);
    }
  };

  // ...其余代码保持不变
};

内容的提问来源于stack exchange,提问作者Nishant Chaudhary

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 13:12:56