简体   繁体   中英

Play only one song at a time (React)

I am trying to create a music app over an API, where I am rendering a Song component for each song having its own Play, Pause and Stop button, however when I play another song while a song is playing, I want that the previous one will stop playing. Just like other music app.

This is the component to handle play, pause and stop

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];
};

Each time you call useAudio you are creating a new instance of the hook with it's independent state, hence you are unable to control from one hook to another.

In order to control all the songs with one hook, you should probably create a store.

This is a brief example of what I would be doing. Please make necessary changes to suit your needs.

//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>
}

You should then wrap your app with <AudioProvider>

Usage:

const { setSong, pauseSong } = useAudio()

const songSelected = (url) => setSong(url)

setSong will first pause the original song, then create a new Audio object with the new url, and then play.

Only one song can be played at a time.

My suggestions is write 2 hooks and consider the lifecycle of song plays:

  • song to pause
  • song to play

in code:

const [songIdToPlay, setSongIdToPlay] = useState();

Also if there were x songs - the code need to ability to identify which songs to play, so hook can take songId or something like identifier(or url) instead boolean.

In this idea there is probable need for 2 refs.

const audio = useRef(new Audio(song_url));


After triggering the play function(also try to simplify and use only 1 useEffect for better readability):

  useEffect(() => {
    setSongIdToPlay(song_url)
  }, [isPlaying]);


 const toggleAudio = () => {
    setPlaying(songToPlay);
  };

Well thanks for the suggestions, I really appreciate them, but this works absolutely fine for my case

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM