I am making an audio player. It has pause, rewind and time seek features. How and who should handle the audio element?
So what is the most redux way to handle audio with progress display?
To play an mp3 clip on click in React, we can use the Audio constructor to create an audio element with the MP3 file URL. Then we call play on the created object to play the audio clip. We create the audio object with the Audio constructor with the MP3 file URL as its argument. Then we call audio.
To use the addEventListener method in function components in React: Set the ref prop on the element. Use the current property on the ref to get access to the element. Add the event listener in the useEffect hook.
Redux - it's all about the state and consistency.
Your goal is to keep in sync the song time and the progress bar.
I see two possible aproaches:
So you have to keep the song's current time (in seconds for instance) in the Store, because of there are a few dependend components and its hard to sync them without the Store.
You have few events those change the current time:
On a time change you will dispatch an action and will update the Store with the new time. Thereby keeping current song's time all components will be in sync.
Managing the state in unidirectional data flow with actions dispatching, reducers and stores is the Redux way of implementing any component.
Here is a pseudo code of the #1 aproach:
class AudioPlayer extends React.Component {
onPlay(second) {
// Store song current time in the Store on each one second
store.dispatch({ type: 'SET_CURRENT_SECOND', second });
}
onRewind(seconds) {
// Rewind song current time
store.dispatch({ type: 'REWIND_CURRENT_SECOND', seconds });
}
onSeek(seconds) {
// Seek song current time
store.dispatch({ type: 'SEEK_CURRENT_SECOND', seconds });
}
render() {
const { currentTime, songLength } = this.state;
return <div>
<audio onPlay={this.onPlay} onRewind={this.onRewind} onSeek={this.onSeek} />
<AudioProgressBar currentTime songLength />
</div>
}
}
If the above aproach doesn't fit your needs, for example you may have a lot of Audio players on a same screen - there may be a performance gap.
In that case you can access your HTML5 audio tag and components via refs in the componentDidMount lifecycle method.
The HTML5 audio tag has DOM events and you can keep the both components in sync without touching the Store. If there is a need to save something in the Store - you can do it anytime.
Please take a look at react-audio-player source code and check how it handles the refs and what API the plugin exposes. For sure you can take inspiration from there. Also you can reuse it for your use case.
Here are some of the API methods those are related to your questions:
It depends to your use case specifics. However generally speaking in the both aproaches it's a good idea to implement a presentational component with the necessary API methods and it's up to you to decide how much data to manage in the Store.
So I created a starting component for you to illustrate how to handle the refs to the audio and slider. Start / Stop / Seeking features included. For sure it has drawbacks, but as I already mentioned it's a good starting point.
You can evolve it to a presentational component with good API methods, that suits your needs.
class Audio extends React.Component {
constructor(props) {
super(props);
this.state = {
duration: null
}
};
handlePlay() {
this.audio.play();
}
handleStop() {
this.audio.currentTime = 0;
this.slider.value = 0;
this.audio.pause();
}
componentDidMount() {
this.slider.value = 0;
this.currentTimeInterval = null;
// Get duration of the song and set it as max slider value
this.audio.onloadedmetadata = function() {
this.setState({duration: this.audio.duration});
}.bind(this);
// Sync slider position with song current time
this.audio.onplay = () => {
this.currentTimeInterval = setInterval( () => {
this.slider.value = this.audio.currentTime;
}, 500);
};
this.audio.onpause = () => {
clearInterval(this.currentTimeInterval);
};
// Seek functionality
this.slider.onchange = (e) => {
clearInterval(this.currentTimeInterval);
this.audio.currentTime = e.target.value;
};
}
render() {
const src = "https://mp3.gisher.org/download/1000/preview/true";
return <div>
<audio ref={(audio) => { this.audio = audio }} src={src} />
<input type="button" value="Play"
onClick={ this.handlePlay.bind(this) } />
<input type="button"
value="Stop"
onClick={ this.handleStop.bind(this) } />
<p><input ref={(slider) => { this.slider = slider }}
type="range"
name="points"
min="0" max={this.state.duration} /> </p>
</div>
}
}
ReactDOM.render(<Audio />, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
If you have any questions feel free to comment below! :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With