Reputation: 3556
I have a React component that plays/pauses audio when you click on a button. It works great and I render about 5 of these on a page at once. However, if you click play on one, and then click play on another, both audio's are playing, which isn't great. Here's my code for the component:
import React from 'react';
import playIcon from './images/play.png';
import pauseIcon from './images/pause.png';
class Music extends React.Component {
constructor(props) {
super(props);
this.state = { 'play': false };
this.url = props.src;
this.audio = new Audio(this.url);
this.audio.preload = 'none';
this.togglePlay = this.togglePlay.bind(this);
}
togglePlay() {
this.setState({'play': !this.state.play}, () => {
this.state.play ? this.audio.play() : this.audio.pause();
});
}
componentWillUnmount () {
this.audio.pause();
}
render() {
return (
<div style={this.props.style} className={this.props.className}>
{this.state.play
? <button className="audio-button" aria-label="Pause" onClick={this.togglePlay}><img src={pauseIcon} width="34" height="34" alt="Pause"></img></button>
: <button className="audio-button" aria-label="Play" onClick={this.togglePlay}><img src={playIcon} width="34" height="34" alt="Play"></img></button>}
</div>
);
}
}
export default Music;
I've done some looking around and a potential solution is to use redux or some other state management library. I'd like to avoid this as I otherwise have no need for that in this site. I've looked into event listeners (namely the solution proposed here) but when I do a document.getElementsByTagName('audio')
I get back an empty HTMLCollection
. This solution is closer, but I can't bridge the gap between it's implementation in jQuery to the one I'm using in React.
Is there a way to identify the playing audio and pause it from a React Component before playing new audio? Any and all suggestions are appreciated.
Upvotes: 5
Views: 6410
Reputation: 3556
Got some big hints from Xuezheng Ma and TheIncorrigible1. Basically moved audio into it's own module. play
and pause
methods are exported from that, and currentlyPlaying
is tracked in that module.
I adjusted my Music
component to look like this
import React from 'react';
import playIcon from './images/play.png';
import pauseIcon from './images/pause.png';
import globalAudio from './globalAudio';
class Music extends React.Component {
constructor(props) {
super(props);
this.state = { 'play': false };
this.name = props.src;
this.togglePlay = this.togglePlay.bind(this);
}
togglePlay() {
this.setState({'play': !this.state.play}, () => {
this.state.play ? globalAudio.play(this.name) : globalAudio.pause(this.name);
});
}
componentWillUnmount () {
globalAudio.pause(this.name);
}
render() {
return (
<div style={this.props.style} className={this.props.className}>
{this.state.play
? <button className="audio-button" aria-label="Pause" onClick={this.togglePlay}><img src={pauseIcon} width="34" height="34" alt="Pause"></img></button>
: <button className="audio-button" aria-label="Play" onClick={this.togglePlay}><img src={playIcon} width="34" height="34" alt="Play"></img></button>}
</div>
);
}
}
export default Music;
And my new module globalAudio
:
import audio1 from './audio1.mp3';
import audio2 from './audio2.mp3';
import audio3 from './audio3.mp3';
import audio4 from './audio4.mp3';
import audio5 from './audio5.mp3';
const audios = {
'audio1': new Audio(audio1),
'audio2': new Audio(audio2),
'audio3': new Audio(audio3),
'audio4': new Audio(audio4),
'audio5': new Audio(audio5)
};
let currentlyPlaying = null;
const play = (name) => {
if (currentlyPlaying) {
audios[currentlyPlaying].pause();
}
audios[name].play();
currentlyPlaying = name;
};
const pause = (name) => {
currentlyPlaying = null;
audios[name].pause();
};
export default {
pause,
play
};
Thanks for all of the suggestions, they were all very helpful.
Upvotes: 2
Reputation: 436
import React from 'react';
import audio1 from './audio1.mp3';
import audio2 from './audio2.mp3';
class AudioList extends React.Component {
constructor (props) {
super(props);
this.audios = props.list.map(audio => new Audio(audio));
}
getCurrentAudio () {
return this.audios.find(audio => false === audio.paused);
}
toggle (nextAudio) {
const currentAudio = this.getCurrentAudio();
if (currentAudio && currentAudio !== nextAudio) {
currentAudio.pause();
}
nextAudio.paused ? nextAudio.play() : nextAudio.pause();
}
render () {
return (
<div>
{ this.audios.map((audio, index) =>
<button onClick={() => this.toggle(audio) }>
PLAY AUDIO { index }
</button>
) }
</div>
)
}
}
export default () => <AudioList list={[ audio1, audio2 ]} />;
PS: new Audio(url)
returns a HTMLAudioElement.
Upvotes: 2
Reputation: 377
Create a module to handle audio. Expose methods like play, pause, resume, etc, to Music
, and keep track of different audios from all five Music
instance you have. Whenever a new audio is played, paused the old one.
Upvotes: 0