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

React+DRF音乐直播站点:播放状态切换与点击延迟问题排查

问题解决方案

问题1:切换歌曲时同步更新"play"类

当前通过直接操作DOM添加/移除类的方式,无法跨元素同步状态。改用React状态跟踪当前播放歌曲ID,渲染时动态绑定类名即可解决:

修改步骤:

  1. 添加状态存储当前播放的歌曲ID:
const [currentPlayingId, setCurrentPlayingId] = useState(null);
  1. 移除playSong/pauseSong中手动操作class的代码,改为更新状态:
function playSong(songId) {
  setCurrentPlayingId(songId);
  setIcon(faPause);
  setPlaying(true);
}

function pauseSong() {
  setCurrentPlayingId(null);
  setIcon(faPlay);
  setPlaying(false);
}
  1. 渲染music_overlay时,根据currentPlayingId动态添加类:
<div 
  className={`music_overlay ${currentPlayingId === post.id ? 'play' : ''}`}
  id={post.id}
  onClick={(e) => setAudioSrc(e)}
></div>
  1. 调整playPause逻辑,基于状态判断而非DOM类:
function playPause(songId) {
  if (currentPlayingId === songId) {
    pauseSong();
    audioRef.current?.pause();
  } else {
    playSong(songId);
  }
}

问题2:首次点击仅加载资源不播放的延迟问题

当前fetch完成后立即调用playPause,但音频资源尚未加载完成,导致play()调用失败。改用useRef获取音频元素,并监听资源加载完成事件再触发播放:

修改步骤:

  1. 用useRef替代document.querySelector获取音频元素:
const audioRef = useRef(null);
  1. 渲染audio标签时绑定ref并监听加载事件:
<audio 
  ref={audioRef}
  src={music.audio} 
  onTimeUpdate={UpdateProgress}
  onCanPlay={() => {
    // 资源加载完成后自动播放
    if (playing) audioRef.current.play();
  }}
/>
  1. 修改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);
      }
    })
}
  1. 移除原代码中手动修改音频元素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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 17:55:25