Cannot stop player if use useState React Tonejs

200 views Asked by At

I'm trying to implement a start/stop button in my React project with tonejs library, but after I use a setState to change my Icon, my player.stop() functionality doesn't work. If I press the button 2 more times, a second player sounds.

Here is my code

    const [play, setPlay] = useState(false);
    const buffer = new Tone.Buffer("audio.mp3");
    const player = new Tone.Player(buffer).toDestination();

    const startMusic = () => {
        if (buffer.loaded) {
            player.start();
            setPlay(true);
        }
    }

    const muteMusic = () => {
        player.stop();
        console.log(player.buffer)
        setPlay(false);
    }

<Avatar className="hover" style={{ position: 'absolute', top: '10px', left: '10px' }}>
     {play == true ? <VolumeUpRounded onClick={() => { muteMusic() }} /> : <VolumeOff onClick={() => { startMusic() }} />}
</Avatar>

UPDATE: Now I'm using a ContextProvider to only control the audio and I call the functions in the children's

import { createContext } from "react";
import * as Tone from "tone";
import chopin from "../assets/songs/audio.mp3"

const AudioContext = createContext({
    start: null,
    stop: null,

})

export default AudioContext;

export const AudioContextProvider = (props) => {

    const buffer = new Tone.Buffer(chopin);
    const player = new Tone.Player(buffer).toDestination();

    const start = () => {
        player.start();
        console.log('started!');

    }
    const stop = () => {
        player.stop(); 
        console.log('stopped');
    }


    return (
        <AudioContext.Provider value={{
            start: start,
            stop: stop,
        }}>
            {props.children}
        </AudioContext.Provider>
    )
}

and used like this:

import AudioController from "../context/audio-context-controller";

const audioController = React.useContext(AudioController);
const [started, setStarted] = useState(false);


const start = () => {
    audioController.start();
    setStarted(true);
}
    
const stop = () => {
    audioController.stop();
    setStarted(false);
}

1

There are 1 answers

0
Yassine CHABLI On BEST ANSWER

As mentioned above, the setState method generates a render that always allows you to have a new player instance and suddenly you lose access to the current state of the object.

The idea behind and to have a single instance outside the component and like that even if the component is re-rendered, you will always have the same Player instance.

const player = new Tone.Player("https://www.soundhelix.com/examples/mp3/SoundHelix-Song-5.mp3").toDestination();
  
function Example() {
  const [play, setPlay] = useState(false);
  
 
    const startMusic = () => {
            player.start();
            setPlay(true);
        
    }
    const muteMusic = () => {
        player.stop();
        setPlay(false);
    }

  return (
    <div>
      <button onClick={play===true ? muteMusic : startMusic}>
        {play ===true ? "stop" : "start"}
      </button>
    </div>
  );
}

Think of creating a service so you will always get a singleton over the app.