一次仅播放一首歌曲(React)

问题描述 投票:0回答:4

我正在尝试通过 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];
};

javascript reactjs react-hooks
4个回答
2
投票

每次调用 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 对象,然后播放。

一次只能播放一首歌曲。


1
投票

非常感谢您的建议,我真的很感激, 但这对我的情况来说绝对没问题

// 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();
    }
  };


1
投票

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>

这是一个演示,我利用音频标签和引用来一次仅播放一个音频。


0
投票

我的建议是写 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);
  };
© www.soinside.com 2019 - 2024. All rights reserved.