I am trying to make a simple component that when you press a button, will start playing a looping song which you could control the volume of using a range input.
This is my code so far:
import { useState, useEffect } from "react";
import music from "./assets/music.mp3";
function MusicPlay() {
const [volume, setVolume] = useState(20);
const testMusic = new Audio(music);
const playMusic = () => {
testMusic.play();
testMusic.volume = volume / 100;
testMusic.loop = true;
console.log("started music", testMusic, testMusic.volume);
};
useEffect(() => {
testMusic.volume = volume / 100;
console.log("changed volume", testMusic, testMusic.volume);
}, [volume]);
return (
<div className="App">
<button onClick={playMusic}>Play</button>
<input
type="range"
min="0"
max="100"
step="1"
value={volume}
onChange={(e) => {
setVolume(Number(e.target.value));
}}
/>
</div>
);
}
export default MusicPlay;
right now you can press the button to start music, and it will start at the volume you specified, but when you move the slider it doesnt update the volume
>Solution :
Every render of this component will create a new instance of Audio. Changing the volume will cause a rerender. This means that you lost the reference to your playing Audio quite a while ago. If you store the audio in a ref, you’ll always refer to the same audio and be able to change it’s volume:
import React, { useState, useEffect, useRef } from "react";
import music from "./assets/music.mp3";
function MusicPlay() {
const [volume, setVolume] = useState(20);
const testMusicRef = useRef();
React.useEffect(() => {
if (!testMusicRef.current) {
testMusicRef.current = new Audio(music);
}
}, []);
const playMusic = () => { // consider wrapping this in useCallback too
if (!testMusicRef.current) {
return;
}
testMusicRef.current.play();
testMusicRef.current.volume = volume / 100;
testMusicRef.current.loop = true;
console.log(
"started music",
testMusicRef.current,
testMusicRef.current.volume
);
};
useEffect(() => {
if (!testMusicRef.current) {
return;
}
testMusicRef.current.volume = volume / 100;
console.log(
"changed volume",
testMusicRef.current,
testMusicRef.current.volume
);
}, [volume]);
return (
<div className="App">
<button onClick={playMusic}>Play</button>
<input
type="range"
min="0"
max="100"
step="1"
value={volume}
onChange={(e) => {
setVolume(Number(e.target.value));
}}
/>
</div>
);
}
export default MusicPlay;