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管理全局播放状态,确保同一时间仅一个音频播放,且路由切换时自动暂停:
- 创建音频状态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); }
- 在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
相关产品推荐
相关产品推荐

