Search code examples
reactjsaudio

Play same audio twice, then move to the next audio file in React


I'm trying to make an autoplay of audio files from an array, using the react-audio-player. I want to play each audio twice, and then automatically move to the next one.

I found a way to automatically play all the audios, but when I want to play the same audio second time, the player stops. So it plays the first audio, stops, then I click play again and it plays the first audio again and advances to the second, and then stops again.

I think the problem is that since the audio URL doesn't change, the player doesn't recognize it as a new audio.

I can't find almost any threads related to this issue, as it's quite specific. I will appreciate any help.

const userItems = [{ "MP3": "https://www2.cs.uic.edu/~i101/SoundFiles/CantinaBand3.wav" }, { "MP3": "https://www2.cs.uic.edu/~i101/SoundFiles/CantinaBand3.wav" }, { "MP3": "https://www2.cs.uic.edu/~i101/SoundFiles/Fanfare60.wav" }]

const [repeatAudio, setRepeatAudio] = useState(true); // Determine if the audio has been played already

const [currentAudioIndex, setCurrentAudioIndex] = useState(0);

const audioSrc = userItems[currentAudioIndex].MP3

function setNextAudio() {
    if (repeatAudio) { // If the audio hasn't been played, keep same audio index
        setCurrentAudioIndex(i => i + 0)
        setRepeatAudio(false) 
    } else { // If it has been played, jump to the next index
        setCurrentAudioIndex(i => i + 1)
        setRepeatAudio(true)
    }
}

return (
    <div>
        <ReactAudioPlayer
            src={audioSrc}
            autoPlay
            controls
            onEnded={() => setNextAudio()}
        />
    </div>
)

}


Solution

  • I've cleaned this up a bit to make it easier to understand, but yes the key bit is the "replay" doesn't involve a URL change, and so autoPlay doesn't kick in. You have to manage that yourself by getting a reference to the audio element and prodding it directly.

    import React, { useState, useRef } from "react";
    import ReactAudioPlayer from "react-audio-player";
    
    const userItems = [
      { MP3: "https://www2.cs.uic.edu/~i101/SoundFiles/CantinaBand3.wav" },
      { MP3: "https://www2.cs.uic.edu/~i101/SoundFiles/Fanfare60.wav" }
    ];
    
    const PlayTwicePlayer = () => {
      const [audioPointer, setAudioPointer] = useState({
        index: 0,
        playedOnce: false
      });
    
      const audioPlayerRef = useRef(null);
    
      const audioSrc = userItems[audioPointer.index].MP3;
    
      function handleAudioEnded() {
        // Dont proceed to next track if already at the end of the array
        if (audioPointer.index >= userItems.length - 1) return;
    
        if (audioPointer.playedOnce) {
          // If they already played it, that means we just finished playing it a second time, and need to move to the next track.
          setAudioPointer((prev) => ({
            index: prev.index + 1,
            playedOnce: false
          }));
          return;
        } else {
          // If they hadn't already played it, we just finished the first run so we can set playedOnce to true now.
          // We must additionally seek back to 0 and play manually using a reference to the player because autoPlay does not
          // trigger since the URL didn't change.
          setAudioPointer((prev) => ({ ...prev, playedOnce: true }));
          audioPlayerRef.current.currentTime = 0;
          audioPlayerRef.current.play();
          return;
        }
      }
    
      return (
        <div>
          <ReactAudioPlayer
            src={audioSrc}
            autoPlay
            controls
            onEnded={handleAudioEnded}
            ref={(ref) => {
              if (!ref) return;
              audioPlayerRef.current = ref.audioEl.current;
            }}
          />
        </div>
      );
    };
    

    Note this won't give you seamless transitions. To do that you'd need the WebAudio API. Easy library wrappers for it exist like https://github.com/t-mullen/video-stream-merger and https://github.com/jaggad/crunker.