reactjstonejs

Cannot stop player if use useState React Tonejs


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


Solution

  • 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.