我正在尝试通过 API 创建一个音乐应用程序,其中我为每首歌曲渲染一个歌曲组件,具有自己的播放、暂停和停止按钮,但是当我在播放歌曲时播放另一首歌曲时,我希望上一曲将停止播放。就像其他音乐应用程序一样。
这是处理播放、暂停和停止的组件
const useAudio = (song_url) => {
const audio = useRef(new Audio(song_url));
audio.current.preload = "metadata";
const [isPlaying, setPlaying] = useState(false);
const toggleAudio = () => {
setPlaying(!isPlaying);
};
const handleStop = () => {
audio.current.pause();
audio.current.currentTime = 0;
setPlaying(false);
};
useEffect(() => {
isPlaying ? audio.current.play() : audio.current.pause();
}, [isPlaying]);
useEffect(() => {
audio.current.addEventListener("ended", () => setPlaying(false));
return () => {
audio.current.removeEventListener("ended", () => setPlaying(false));
};
}, []);
return [isPlaying, toggleAudio, handleStop];
};
每次调用 useAudio 时,您都会创建一个具有独立状态的钩子的新实例,因此您无法控制从一个钩子到另一个钩子。
为了用一个钩子控制所有歌曲,您可能应该创建一个商店。
这是我将要做的事情的一个简短示例。请进行必要的更改以满足您的需求。
//We first create a store
export const AudioContext = createContext();
export const useAudio = () => {
const context = useContext(AudioContext);
if (!context && typeof window !== 'undefined') {
throw new Error(`useAudio must be used within a AudioContext `);
}
return context;
};
//Then we create the provider
export const AudioProvider = ({ children }) => {
const [ song, _setSong ] = useState()
const setSong = (url) => {
song.pause();
const newSong = new Audio(url)
newSong.play()
setSong(newSong)
}
const pauseSong = () => song.pause()
return <AudioContext.Provider value={{ setSong, pauseSong }}>{children}</AudioContext.Provider>
}
然后您应该用
<AudioProvider>
包装您的应用程序
用途:
const { setSong, pauseSong } = useAudio()
const songSelected = (url) => setSong(url)
setSong
将首先暂停原始歌曲,然后使用新的 url 创建一个新的 Audio 对象,然后播放。
一次只能播放一首歌曲。
非常感谢您的建议,我真的很感激, 但这对我的情况来说绝对没问题
// creating two states:
// (1) toggle Play-Pause
// (2) storing currently playing song ID
const [isPlaying, setPlaying] = useState(false);
const [currentSong, setCurrentSong] = useState(null);
const audio = useRef(null);
const togglePlay = (e) => {
const song = e.target.id;
if (currentSong === song) {
isPlaying ? audio.current.pause() : audio.current.play();
setPlaying(!isPlaying);
} else {
if (audio.current) {
audio.current.pause();
}
setCurrentSong(song);
setPlaying(true);
audio.current = new Audio(song);
audio.current.play();
}
};
const {useState, useRef} = React;
const AudioPlayer = ({ recording, id, activeId, setActiveId, audioRefs }) => {
const audioRef = useRef(null);
const handlePlay = () => {
if (activeId === id) {
audioRef.current.play();
} else {
// Clicked on a different recording, pause the current one (if any) and play the new one
if (activeId !== null) {
audioRefs[activeId].current.pause();
}
audioRef.current.play();
setActiveId(id);
}
};
const handlePause = () => {
if (activeId === id) {
audioRef.current.pause();
}
}
audioRefs[id] = audioRef;
return (
<div key={id}>
<audio ref={audioRef} controls={true} onPlay={handlePlay} onPause={handlePause}>
<source src={recording} type="audio/mpeg" />
</audio>
</div>
);
};
const RecordingList = () => {
const recordings = [
"https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3",
"https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3",
"https://www.soundhelix.com/examples/mp3/SoundHelix-Song-3.mp3"
];
const [activeId, setActiveId] = useState(null);
const audioRefs = useRef({});
return (
<div>
{recordings.map((recording, index) => (
<AudioPlayer
key={index}
recording={recording}
id={index}
activeId={activeId}
setActiveId={setActiveId}
audioRefs={audioRefs.current}
/>
))}
</div>
);
};
ReactDOM.createRoot(
document.getElementById("root")
).render(
<RecordingList />
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
这是一个演示,我利用音频标签和引用来一次仅播放一个音频。
我的建议是写 2 个 hook 并考虑歌曲播放的生命周期:
代码:
const [songIdToPlay, setSongIdToPlay] = useState();
此外,如果有 x 首歌曲 - 代码需要能够识别要播放哪些歌曲,因此 hook 可以采用 songId 或类似标识符(或 url)的东西来代替布尔值。
在这个想法中可能需要 2 个参考。
const audio = useRef(new Audio(song_url));
触发播放功能后(也尝试简化并仅使用1个useEffect以获得更好的可读性):
useEffect(() => {
setSongIdToPlay(song_url)
}, [isPlaying]);
const toggleAudio = () => {
setPlaying(songToPlay);
};