Skip to content

How to autoplay the next song onClick on a btn

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>
        )
    }
}

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>
  );
}