I’m trying to figure out a way of controlling HTML Audio elements that are in components in React.
I’m making a music viewer which has buttons that allow you to pause or play all individual audio elements at once.
I’ve tried using props to pass the state about whether or not all the samples should play, but this causes the page to refresh in a way that resets the audio elements to their default position(not playing).
Any suggestions or pointers as to how I could approach this would be much appreciated, thanks.
Home Page:
'use client';
import Track from './components/track.tsx';
import {useState, useRef} from 'react';
export default function Home() {
var musicDir = "/assets/music"
var currentSong = "sweep-me-off-my-feet";
const [playingStatus, updatePlaying] = useState(null);
const musicFolder = "./assets/music"
const currentSongFolder = "/sweep-me-off-my-feet/"
return (
<div id="viewer-widget" className="flex-row h-min w-min bg-gray-500 m-0 mt-20 ml-5 pt-1 pb-6 px-10 rounded-lg">
<div id="track-container">
</div>
<h1 className="text-3xl text-center text-white my-5 underline">Music Viewer</h1>
<button onClick={() => playingStatus.current = true}>Play All</button>
<button onClick={() => playingStatus.current = false}>Stop All</button>
<Track source={musicFolder + currentSongFolder + "pond-sweepme-lead.mp3"} isPlaying={playingStatus.current} />
<Track source={musicFolder + currentSongFolder + "pond-sweepme-strings.mp3"} isPlaying={playingStatus.current} />
<Track source={musicFolder + currentSongFolder + "pond-sweepme-bass.mp3"} isPlaying={playingStatus.current} />
<Track source={musicFolder + currentSongFolder + "pond-sweepme-brass.mp3"} isPlaying={playingStatus.current} />
</div>
)
}
All</button>
Audio Component File:
const Track = ({source, isPlaying}) => {
const myaudio = useRef(null); //useRef(null);
const [val, setVal] = useState(84);
const inputRef = useRef(null);
const MyAudio = React.forwardRef((props, ref) => {
return (
<audio controls controlsList="nodownload noplaybackrate" ref={ref} src={source} type="audio/mp3" className="">
Your browser does not support the audio element.
{props.children}
</audio>
);
});
const [audio1, setAudio1] = useState(null)
useEffect(() => {
setAudio1(new Audio(source));
}, [])
ReactDOM.render(audio1, document.getElementById("track-container"));
function updateVolume(event) {
myaudio.current.volume = event.target.value / 100;
}
useEffect(() => {
inputRef.current.addEventListener("input", updateVolume);
}, []);
if (isPlaying == false) {
console.log("current time through track: " + myaudio.current.currentTime)
myaudio.current.pause();
} else if (isPlaying == true) {
myaudio.current.play();
}
return (
<div className="track flex">
<MyAudio ref={myaudio}/>
<div className="flex audio-controls w-32 h-15 bg-white rounded-lg ml-2">
<input onChange={updateVolume} ref={inputRef} type="range" id="volume-knob" min="0" max="100" value={val} data-diameter='50' data-bgcolor="#a8a7b5" data-fgcolor="#4a4a4a" className="input-knob volume-knob outline-none focus:outline-none outline-transparent border-transparent focus:ring-0"/>
</div>
</div>
);
};
export default Track;
2
Answers
A simple example doing what I understand you want: https://codepen.io/aSH-uncover/pen/ZEVaOBa
IMHO, You dont really need to use the <audio> markup and you can stick with the Audio object.
This is what the ‘Track’ component looks like on this codepen:
You are recreating the
MyAudio
component on each render. When re-create the component itself, the previous component is unmounted, and the new one us mounted. Move it’s definition out ofTrack
:If you want to render a component into another DOM element, use createPortal. However, it’s not clear why you’re trying to render
audio1
, which isnew Audio()
into the DOM, and why you do it on each render: