useAudio

React hook to manage an audio.

Add the hook via the CLI:

npx @novajslabs/cli add useAudio

Or copy and paste the code into your project:

import { useEffect, useState, RefObject } from 'react'

export const useAudio = (ref: RefObject<HTMLAudioElement>) => {
  const audio = ref.current

  const [audioState, setAudioState] = useState({
    isPaused: audio ? audio?.paused : true,
    isMuted: audio ? audio?.muted : false,
    currentVolume: audio ? audio?.volume : 100,
    currentTime: audio ? audio?.currentTime : 0,
  })

  const play = () => {
    audio?.play()
    setAudioState((prev) => {
      return {
        ...prev,
        isPaused: false,
        isMuted: audio ? audio.muted : prev.isMuted,
      }
    })
  }

  const pause = () => {
    audio?.pause()
    setAudioState((prev) => {
      return {
        ...prev,
        isPaused: true,
      }
    })
  }

  const handlePlayPauseControl = (e: Event) => {
    setAudioState((prev) => {
      return {
        ...prev,
        isPaused: (e.target as HTMLAudioElement).paused,
      }
    })
  }

  const togglePause = () => (audio?.paused ? play() : pause())

  const handleVolume = (delta: number) => {
    const deltaDecimal = delta / 100

    if (audio) {
      let newVolume = audio?.volume + deltaDecimal

      if (newVolume >= 1) {
        newVolume = 1
      } else if (newVolume <= 0) {
        newVolume = 0
      }

      audio.volume = newVolume
      setAudioState((prev) => {
        return {
          ...prev,
          currentVolume: newVolume * 100,
        }
      })
    }
  }

  const handleVolumeControl = (e: Event) => {
    if (e.target && audio) {
      const newVolume = (e.target as HTMLAudioElement).volume * 100

      handleMute(audio.muted)
      setAudioState((prev) => ({
        ...prev,
        currentVolume: newVolume,
      }))
    }
  }

  const handleMute = (mute: boolean) => {
    if (audio) {
      audio.muted = mute
      setAudioState((prev) => {
        return {
          ...prev,
          isMuted: mute,
        }
      })
    }
  }

  const handleTime = (delta: number = 5) => {
    if (audio) {
      let newTime = audio.currentTime + delta

      if (newTime >= audio.duration) {
        newTime = audio.duration
      } else if (newTime <= 0) {
        newTime = 0
      }

      audio.currentTime = newTime
      setAudioState((prev) => {
        return {
          ...prev,
          currentTime: newTime,
        }
      })
    }
  }

  const handleTimeControl = (e: Event) => {
    setAudioState((prev) => {
      return {
        ...prev,
        currentTime: (e.target as HTMLAudioElement).currentTime,
      }
    })
  }

  useEffect(() => {
    return () => {
      pause()
    }
  }, [])

  useEffect(() => {
    if (audio) {
      audio.addEventListener('volumechange', handleVolumeControl)
      audio.addEventListener('play', handlePlayPauseControl)
      audio.addEventListener('pause', handlePlayPauseControl)
      audio.addEventListener('timeupdate', handleTimeControl)

      return () => {
        audio.removeEventListener('volumechange', handleVolumeControl)
        audio.addEventListener('play', handlePlayPauseControl)
        audio.addEventListener('pause', handlePlayPauseControl)
        audio.addEventListener('timeupdate', handleTimeControl)
      }
    }
  }, [audio])

  return {
    ...audioState,
    play,
    pause,
    togglePause,
    increaseVolume: (increase: number = 5) => handleVolume(increase),
    decreaseVolume: (decrease: number = 5) => handleVolume(decrease * -1),
    mute: () => handleMute(true),
    unmute: () => handleMute(false),
    toggleMute: () => handleMute(!audio?.muted),
    forward: (increase: number = 5) => handleTime(increase),
    back: (decrease: number = 5) => handleTime(decrease * -1),
  }
}

React 16.8 or higher

  • Name
    ref
    Type
    HTMLAudioElement
    Required
    required
    Description

    The HTML audio element to be manage.

  • Name
    isPaused
    Type
    boolean
    Description

    Indicates whether the audio is currently paused (true) or playing (false).

  • Name
    isMuted
    Type
    boolean
    Description

    Indicates whether the audio is currently muted (true) or unmuted (false).

  • Name
    currentVolume
    Type
    number
    Description

    The current volume level of the audio, ranging from 0 to 100.

  • Name
    currentTime
    Type
    number
    Description

    The current playback position (in seconds) of the audio.

  • Name
    play
    Type
    function
    Description

    Play the audio.

  • Name
    pause
    Type
    function
    Description

    Pause the audio.

  • Name
    togglePause
    Type
    function
    Description

    Toggle between playing and pausing the audio.

  • Name
    increaseVolume
    Type
    function
    Description

    Increase the volume by a specified amount.

  • Name
    decreaseVolume
    Type
    function
    Description

    Decrease the volume by a specified amount.

  • Name
    mute
    Type
    function
    Description

    Mute the audio.

  • Name
    unmute
    Type
    function
    Description

    Unmute the audio.

  • Name
    toggleMute
    Type
    function
    Description

    Toggle between muting and unmuting the audio.

  • Name
    forward
    Type
    function
    Description

    Fast forward the audio by a specified number of seconds.

  • Name
    back
    Type
    function
    Description

    Rewind the audio by a specified number of seconds.

import { useRef } from 'react'
import { useAudio } from './hooks/useAudio'

const App = () => {
  const audioRef = useRef<HTMLAudioElement>(null)
  const {
    isPaused,
    isMuted,
    currentVolume,
    currentTime,
    play,
    pause,
    togglePause,
    increaseVolume,
    decreaseVolume,
    mute,
    unmute,
    toggleMute,
    forward,
    back,
  } = useAudio(audioRef)

  return (
    <div>
      <audio ref={audioRef} src="audio.mp3" />
      <div>
        <button onClick={togglePause}>{isPaused ? 'Play' : 'Pause'}</button>
        <button onClick={toggleMute}>{isMuted ? 'Unmute' : 'Mute'}</button>
        <input
          type="range"
          min={0}
          max={100}
          value={currentVolume}
          onChange={(e) => increaseVolume(Number(e.target.value))}
        />
        <input
          type="range"
          min={0}
          max={videoRef.current?.duration || 0}
          value={currentTime}
          onChange={(e) =>
            videoRef.current &&
            (videoRef.current.currentTime = Number(e.target.value))
          }
        />
        <button onClick={() => forward(10)}>Forward</button>
        <button onClick={() => back(10)}>Back</button>
      </div>
    </div>
  )
}

export default App

Here are some use cases where this React hook is useful:

  • Controlling audio playback in a custom media player
  • Implementing a podcast player with seek functionality
  • Building a music streaming app with volume control
  • Creating an audio preview component for an e-commerce site
  • Developing an educational platform with audio lectures