goldfire / howler.js

Javascript audio library for the modern web.
https://howlerjs.com
MIT License
24k stars 2.23k forks source link

Setting useState inside play function causes pause and stop function to stop working. #1536

Open codenamezeta opened 2 years ago

codenamezeta commented 2 years ago

Hello, I'm looking for some advice on an audio player I'm trying to build using Howler inside a Next.js site. The end-goal of this component is to have an audio player that will take an array of stems (individual instruments recordings that together make up the complete song) and synchronize the play/pause of all the stems yet allow for muting and volume changes of each stem independently. Here is my code so far...

import { useState } from 'react'
import { FaPlayCircle, FaPauseCircle, FaStopCircle } from 'react-icons/fa'
import { Howl, Howler } from 'howler'

export default function AudioPlayer() {
  const [isPlaying, setIsPlaying] = useState(false)

  const stems = [
    new Howl({
      src: ['/audio/piano.wav'],
      preload: true,
    }),
    new Howl({
      src: ['/audio/guitar.wav'],
      preload: true,
    }),
    new Howl({
      src: ['/audio/bass.wav'],
      preload: true,
    }),
    new Howl({
      src: ['/audio/drums.wav'],
      preload: true,
    }),
    new Howl({
      src: ['/audio/lead-vocals.wav'],
      preload: true,
    }),
    new Howl({
      src: ['/audio/backup-vocals.wav'],
      preload: true,
    }),
  ]

  //* Controls Functions
  function playStems() {
    // setIsPlaying(true) 
    stems.forEach((stem) => {
      stem.play()
    })
  }
  function pauseStems() {
    setIsPlaying(false)
    stems.forEach((stem) => {
      stem.pause()
    })
  }
  function stopStems() {
    setIsPlaying(false)
    stems.forEach((stem) => {
      stem.stop()
    })
  }

  return (
    <div>
      <FaPlayCircle onClick={playStems} />
      <FaPauseCircle onClick={pauseStems} />
      <FaStopCircle onClick={stopStems} />
    </div>
  )
}

I feel like there is probably a better way to get all the stems to sync together but I can't seem to get it to work any other way. If I'm missing something, please let me know. For now, this does work. As long as I allow a few seconds for the stems to load then the play, pause, and stop functions all work exactly as I was expecting. My issue comes up as soon as I try to update the state with the setIsPlaying function inside the playStems function. For some reason, calling setIsPlaying(true) causes all other controls functions (pause/stop) to stop working. I have no earthly idea why this is happening or how I solve this. Any help is greatly appreciated.

MCArth commented 2 years ago

This is because you are running the

const stems = [
    new Howl({
      src: ['/audio/piano.wav'],
      preload: true,
    }),
    new Howl({
      src: ['/audio/guitar.wav'],
      preload: true,
    }),
    new Howl({
      src: ['/audio/bass.wav'],
      preload: true,
    }),
    new Howl({
      src: ['/audio/drums.wav'],
      preload: true,
    }),
    new Howl({
      src: ['/audio/lead-vocals.wav'],
      preload: true,
    }),
    new Howl({
      src: ['/audio/backup-vocals.wav'],
      preload: true,
    }),
  ]

code every time the component is rendered, creating even more howls!

This could be fixed by simply moving the howl creation (the code snippet I pasted) outside the functional component

the react docs in general are really quite accessible and nicely ordered, I would recommend reading through them - https://reactjs.org/docs/components-and-props.html#function-and-class-components