React+DRF音乐直播站点:播放状态切换与点击延迟问题排查
问题解决方案
问题1:切换歌曲时同步更新"play"类
当前通过直接操作DOM添加/移除类的方式,无法跨元素同步状态。改用React状态跟踪当前播放歌曲ID,渲染时动态绑定类名即可解决:
修改步骤:
- 添加状态存储当前播放的歌曲ID:
const [currentPlayingId, setCurrentPlayingId] = useState(null);
- 移除
playSong/pauseSong中手动操作class的代码,改为更新状态:
function playSong(songId) { setCurrentPlayingId(songId); setIcon(faPause); setPlaying(true); } function pauseSong() { setCurrentPlayingId(null); setIcon(faPlay); setPlaying(false); }
- 渲染
music_overlay时,根据currentPlayingId动态添加类:
<div className={`music_overlay ${currentPlayingId === post.id ? 'play' : ''}`} id={post.id} onClick={(e) => setAudioSrc(e)} ></div>
- 调整
playPause逻辑,基于状态判断而非DOM类:
function playPause(songId) { if (currentPlayingId === songId) { pauseSong(); audioRef.current?.pause(); } else { playSong(songId); } }
问题2:首次点击仅加载资源不播放的延迟问题
当前fetch完成后立即调用playPause,但音频资源尚未加载完成,导致play()调用失败。改用useRef获取音频元素,并监听资源加载完成事件再触发播放:
修改步骤:
- 用
useRef替代document.querySelector获取音频元素:
const audioRef = useRef(null);
- 渲染audio标签时绑定ref并监听加载事件:
<audio ref={audioRef} src={music.audio} onTimeUpdate={UpdateProgress} onCanPlay={() => { // 资源加载完成后自动播放 if (playing) audioRef.current.play(); }} />
- 修改
setAudioSrc函数,更新音频源后统一管理播放状态:
const setAudioSrc = (e) => { const songId = e.target.id; fetch(`http://127.0.0.1:8000/${songId}/`) .then(response => response.json()) .then(data => { setMusic(data); if (currentPlayingId === songId) { // 切换当前歌曲的播放状态 playing ? (pauseSong(), audioRef.current?.pause()) : (playSong(songId), audioRef.current?.play()); } else { // 播放新歌曲,重置进度 playSong(songId); SetCurrentProgress(0); } }) }
- 移除原代码中手动修改音频元素ID的逻辑(
e.target.nextSibling.id='audio'),完全通过ref控制音频元素。
完整优化后的核心代码片段
import { useState, useEffect, useRef } from 'react'; import { faPlay, faPause } from '@fortawesome/free-solid-svg-icons'; import Navbar from './Navbar'; export default function MusicPlayer() { const audioRef = useRef(null); const [posts, setPosts] = useState([]); const [icon, setIcon] = useState(faPlay); const [playing, setPlaying] = useState(false); const [music, setMusic] = useState({}); const [progress, SetCurrentProgress] = useState(0); const [currentPlayingId, setCurrentPlayingId] = useState(null); function getAllPosts() { fetch(`http://127.0.0.1:8000/`) .then(response => response.json()) .then(data => setPosts(data)); } useEffect(() => { getAllPosts(); }, []); function playSong(songId) { setCurrentPlayingId(songId); setIcon(faPause); setPlaying(true); } function pauseSong() { setCurrentPlayingId(null); setIcon(faPlay); setPlaying(false); } const UpdateProgress = (e) => { const { duration, currentTime } = e.target; if (duration) { const ProgressPercent = (currentTime / duration) * 100; SetCurrentProgress(`${ProgressPercent}`); } }; const setAudioSrc = (e) => { const songId = e.target.id; fetch(`http://127.0.0.1:8000/${songId}/`) .then(response => response.json()) .then(data => { setMusic(data); if (currentPlayingId === songId) { playing ? (pauseSong(), audioRef.current?.pause()) : (playSong(songId), audioRef.current?.play()); } else { playSong(songId); SetCurrentProgress(0); } }); }; const setProgress = (e) => { if (!audioRef.current) return; const width = e.target.clientWidth; const clickX = e.nativeEvent.offsetX; const duration = audioRef.current.duration; if (duration) { audioRef.current.currentTime = (clickX / width) * duration; } }; return ( <div> <Navbar /> <main className="landing"> {posts.map((post, i) => ( <div className="music_holder" key={i}> <div className="music_container"> <img className="music_image" src={`http://127.0.0.1:8000${post.image}`} onClick={() => { if (currentPlayingId === post.id) { playing ? (pauseSong(), audioRef.current?.pause()) : (playSong(post.id), audioRef.current?.play()); } else { document.getElementById(post.id).click(); } }} /> <div className={`music_overlay ${currentPlayingId === post.id ? 'play' : ''}`} id={post.id} onClick={setAudioSrc} ></div> <audio ref={audioRef} src={music.audio} onTimeUpdate={UpdateProgress} onCanPlay={() => { if (playing && currentPlayingId === post.id) audioRef.current.play(); }} /> </div> <div className="caption_holder"> <p className="caption">{post.title}</p> </div> </div> ))} </main> </div> ); }
关键优化说明:
- 用
currentPlayingId统一控制播放状态和类名,避免DOM操作不一致 - 音频元素改用
useRef获取,符合React组件生命周期规范 - 监听
onCanPlay事件,确保资源加载完成后再播放,解决首次点击无响应问题 - 统一播放/暂停逻辑,消除分散的DOM操作
内容的提问来源于stack exchange,提问作者miracle godwin
相关产品推荐
相关产品推荐

