I am in front of a problem. I am trying to create a music player. But now, I have a problem with my loading of the next music at the click of the next button. The new data is replaced in the state and in the render, but the music does not play by itself after the button is clicked. I think I should used the load () function of an audio or video element to reload the media but it doesn’t work. I put my code for you so you can look. It is certainly not perfect, I am aware that I could improve some point of my code.
What i’m trying to do is when the next function is launch. I take the current song then add +1 for the new information then i update the state with the new information. When the new informations are in the state i pause the music then load the new song and after i launch the playing function who check if he need to pause or play the music. If anyone could give me a lead on how to solve the problem it would be great !
Thx a lot !
import React, { Component } from 'react' import data from '../data/list-music.json' export default class audioPlayer extends Component { constructor(props) { super(props); this.audio = React.createRef(); this.state = { musicList: data, playing: false, currentMusic: 0, nextMusic: 1, repeat: false, volume: 0.1, currentTitle: data[0].name, currentImage: data[0].img, currentSongSrc: data[0].src }; } playing = (e) => { this.state.playing ? this.pause() : this.play(); } play() { const audio = this.audio.current this.setState({ playing: true }) audio.play() audio.volume = this.state.volume } pause() { const audio = this.audio.current this.setState({ playing: false }) audio.pause() } // GET THE PREVIOUS MUSIC previous = (e) => { let currentSongPlaying = this.state.currentMusic let nextSongPlaying = this.state.nextMusic currentSongPlaying = currentSongPlaying - 1 nextSongPlaying = currentSongPlaying + 1 this.setState({ currentMusic: currentSongPlaying, nextMusic: nextSongPlaying, currentTitle: data[currentSongPlaying].name, currentImage: data[currentSongPlaying].img, currentSongSrc: data[currentSongPlaying].src }) } // GET THE NEXT MUSIC next = (e) => { let currentSongPlaying = this.state.currentMusic let nextSongPlaying = this.state.nextMusic currentSongPlaying = currentSongPlaying + 1 nextSongPlaying = currentSongPlaying + 1 this.setState({ currentMusic: currentSongPlaying, nextMusic: nextSongPlaying, currentTitle: data[currentSongPlaying].name, currentImage: data[currentSongPlaying].img, currentSongSrc: data[currentSongPlaying].src }) const audio = this.audio.current this.pause() audio.load() this.playing() } render() { const isPlaying = this.state.playing const poster = this.state.currentImage const titre = this.state.currentTitle const song = this.state.currentSongSrc const music = require("../music/" + song + ".mp3") return ( <div> <h1>Audio Player</h1> <audio id="main-audio" src={music.default} ref={this.audio} ></audio> <p>{titre}</p> <button onClick={this.previous}> previous </button> <button onClick={this.playing}> {isPlaying ? 'Pause' : 'Play'} </button> <button onClick={this.next}> next </button> </div> ) } }
Advertisement
Answer
You are working with stale state when you enqueue the state update and then try to call pause
, load the audio, and play
. The component hasn’t rerendered for the this.audio
ref to have updated yet either.
After the state update is enqueued you will want to pause the currently playing track and use the componentDidUpdate
lifecycle hook to start the next.
componentDidUpdate(prevProps, prevState) { if (prevState.currentMusic !== this.state.currentMusic) { this.audio.current.load(); this.play(); } } next = (e) => { this.setState(prevState => { const { currentMusic, nextMusic } = prevState; return { currentMusic: currentMusic + 1, nextMusic: nextMusic + 1, } }); this.pause(); }
You will need to apply a similar fix to prev
. In fact, since these two functions are basically the same, the only difference is incrementing/decrementing the current/next value, you can combine these into a curried function and close over in scope the incrementing/decrementing value.
incrementTrack = (val) => (e) => { this.setState(prevState => { const { currentMusic, nextMusic } = prevState; return { currentMusic: currentMusic + val, nextMusic: nextMusic + val, } }); this.pause(); }
The title, image, and song source all considered “derived” state, meaning that from the actual currentMusic
state and the data
you can derive this “state”.
render() { const { currentMusic, playing: isPlaying } = this.state const { name: title, img: poster, src: song, } = data[currentMusic]; const music = require("../music/" + song + ".mp3"); return ( <div> <h1>Audio Player</h1> <audio id="main-audio" src={music.default} ref={this.audio} ></audio> <p>{title}</p> <button onClick={this.incrementTrack(-1)}> previous </button> <button onClick={this.playing}> {isPlaying ? 'Pause' : 'Play'} </button> <button onClick={this.incrementTrack(1)}> next </button> </div> ); }