React音频组件:如何实现点击下一曲时停止当前音频
解决多音频同时播放的问题
你的问题出在每个SetAudios组件都维护自己的currentAudio状态——因为TranslationText里每段经文都会渲染一个独立的SetAudios,这些组件的状态互相隔离,所以点击新音频时,没法暂停其他组件里正在播放的音频。
解决思路是把音频控制的状态和逻辑提升到父组件TranslationText,让所有音频组件共享同一个当前播放实例,这样就能统一控制暂停和播放。
修改步骤:
1. 改造TranslationText.tsx
在父组件中维护当前播放的音频实例,并提供统一的播放控制方法:
import React, { useEffect, useState } from 'react' import { useSignedContext } from '../contexts/SignedContext' import SetAudios from './SetAudios' import { useParams } from 'react-router' import Loader from './Loader' interface SurahProps { name: string englishName: string englishNameTranslation: string number: number ayahs: { number: number text: string numberInSurah: number }[] } const TranslationText: React.FC = () => { const { surah } = useParams<{ surah: string }>() const { translatedText } = useSignedContext() const [data, setData] = useState<SurahProps[]>([]) const [loading, setLoading] = useState<boolean>(false) // 把currentAudio状态移到父组件统一管理 const [currentAudio, setCurrentAudio] = useState<HTMLAudioElement | null>(null) const BASE_URL = 'https://api.alquran.cloud/v1/quran' useEffect(() => { async function fetchScholarData() { try { setLoading(true) if (translatedText) { const response = await fetch(`${BASE_URL}/${translatedText}`).then( (response) => response.json() ) setData(response.data.surahs) } setLoading(false) } catch (error) { console.error('Error fetching scholar data:', error) setLoading(false) } } fetchScholarData() }, [translatedText]) // 统一的音频播放控制方法 const handlePlayAudio = (audioElement: HTMLAudioElement) => { if (currentAudio && currentAudio !== audioElement) { currentAudio.pause() // 可选:将暂停的音频进度重置到开头 currentAudio.currentTime = 0 } setCurrentAudio(audioElement) audioElement.play() } if (loading) { return <Loader /> } if (!translatedText) { return <p className="text-3xl">No translated text available 🙅♂️</p> } const filteredData = data.find((surahData) => surahData.englishName === surah) return ( <> {filteredData?.ayahs.map((ayah, i) => ( <div key={ayah.number} className="bg-white shadow-md rounded-lg overflow-hidden" > <div className="px-6 py-4 bg-gray-200"> <div className="flex gap-5"> <div className="flex flex-col justify-between"> <p className="text-3xl font-black">{i + 1}</p> <p> <i className="fa-solid fa-share text-3xl"></i> </p> <p> <i className="fa-solid fa-person-praying text-3xl"></i> </p> </div> <div> <p className="text-2xl">{ayah.text}</p> </div> </div> {/* 把控制方法传给子组件 */} <SetAudios ayahNumber={ayah.number} onPlay={handlePlayAudio} /> </div> <hr className="border-t-4 my-10 border-emerald-700" /> </div> ))} </> ) } export default TranslationText
2. 改造SetAudios.tsx
移除子组件内部的状态和控制逻辑,改用父组件传来的回调:
import React, { useEffect, useState } from 'react' import { useSignedContext } from '../contexts/SignedContext' import { useParams } from 'react-router' interface SurahProps { name: string englishName: string englishNameTranslation: string number: number ayahs: { number: number text: string numberInSurah: number audio: string }[] } interface SetAudiosProps { ayahNumber: number // 接收父组件的播放回调 onPlay: (audioElement: HTMLAudioElement) => void } const SetAudios: React.FC<SetAudiosProps> = ({ ayahNumber, onPlay }) => { const { surah } = useParams<{ surah: string }>() const { selectedAudioFormat } = useSignedContext() const [data, setData] = useState<SurahProps[]>([]) const [loading, setLoading] = useState<boolean>(false) const BASE_URL = 'https://api.alquran.cloud/v1/quran/' useEffect(() => { async function fetchScholarData() { try { setLoading(true) if (selectedAudioFormat) { const response = await fetch( `${BASE_URL}/${selectedAudioFormat}` ).then((response) => response.json()) setData(response.data.surahs) } setLoading(false) } catch (error) { console.error('Error fetching scholar data:', error) setLoading(false) } } fetchScholarData() }, [selectedAudioFormat]) const filteredData = data.find((surahData) => surahData.englishName === surah) if (loading) { return <p>Loading...</p> } if (!selectedAudioFormat) { return <p>No translated text available</p> } return ( <div className="w-full my-5"> {filteredData?.ayahs.map((ayah) => { if(ayah.number === ayahNumber){ return ( <audio key={ayah.number} className="w-full" controls // 调用父组件传来的播放方法 onPlay={(e) => onPlay(e.target as HTMLAudioElement)} > <source src={ayah.audio} /> </audio> ) } })} </div> ) } export default SetAudios
额外优化(可选)
如果希望音频播放完毕后自动清空当前状态,可以在父组件的handlePlayAudio里添加结束监听:
const handlePlayAudio = (audioElement: HTMLAudioElement) => { if (currentAudio && currentAudio !== audioElement) { currentAudio.pause() currentAudio.currentTime = 0 } // 添加播放结束监听 audioElement.onended = () => { setCurrentAudio(null) } setCurrentAudio(audioElement) audioElement.play() }
内容的提问来源于stack exchange,提问作者Uktam Rakhmatullayev
相关产品推荐
相关产品推荐

