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