useAudio

Manage an HTML5 audio element with an easy-to-use control interface.

🎵

SoundHelix Song 1

Experimental Synth Wave

0:000:00

Interactive Audio Controller

Usage

import { useAudio } from 'usehooks-ts'
 
export default function MyAudioPlayer() {
  const [state, controls] = useAudio('https://example.com/audio.mp3')
 
  return (
    <div>
      <p>Playing: {state.playing ? 'Yes' : 'No'}</p>
      <p>Time: {Math.floor(state.currentTime)}s</p>
      
      <button onClick={controls.toggle}>
        {state.playing ? 'Pause' : 'Play'}
      </button>
      
      <button onClick={() => controls.seek(state.currentTime + 10)}>
        Forward 10s
      </button>
    </div>
  )
}

API

const [state, controls] = useAudio(src)

State Values

NameTypeDescription
playingbooleanCurrent playback status
durationnumberTotal audio length in seconds
currentTimenumberCurrent playback position
volumenumberVolume level (0 to 1)
mutedbooleanWhether audio is muted

Controls

NameTypeDescription
play() => PromiseStart playback
pause() => voidPause playback
toggle() => voidToggle play/pause
seek(time) => voidJump to specific time
setVolume(v) => voidUpdate volume
mute() => voidMute audio
unmute() => voidUnmute audio

Hook

import { useCallback, useEffect, useRef, useState } from 'react'
 
export interface AudioState {
  playing: boolean
  duration: number
  currentTime: number
  volume: number
  muted: boolean
}
 
export interface AudioControls {
  play: () => Promise<void>
  pause: () => void
  toggle: () => void
  seek: (time: number) => void
  setVolume: (volume: number) => void
  mute: () => void
  unmute: () => void
}
 
export function useAudio(src: string): [AudioState, AudioControls] {
  const audioRef = useRef<HTMLAudioElement | null>(null)
  const [state, setState] = useState<AudioState>({
    playing: false,
    duration: 0,
    currentTime: 0,
    volume: 1,
    muted: false,
  })
 
  useEffect(() => {
    const audio = new Audio(src)
    audioRef.current = audio
 
    const updateState = () => {
      setState({
        playing: !audio.paused,
        duration: audio.duration || 0,
        currentTime: audio.currentTime,
        volume: audio.volume,
        muted: audio.muted,
      })
    }
 
    audio.addEventListener('play', updateState)
    audio.addEventListener('pause', updateState)
    audio.addEventListener('timeupdate', updateState)
    audio.addEventListener('volumechange', updateState)
    audio.addEventListener('loadedmetadata', updateState)
 
    return () => {
      audio.pause()
      audio.removeEventListener('play', updateState)
      audio.removeEventListener('pause', updateState)
      audio.removeEventListener('timeupdate', updateState)
      audio.removeEventListener('volumechange', updateState)
      audio.removeEventListener('loadedmetadata', updateState)
      audioRef.current = null
    }
  }, [src])
 
  const controls: AudioControls = {
    play: useCallback(() => audioRef.current?.play() || Promise.resolve(), []),
    pause: useCallback(() => audioRef.current?.pause(), []),
    toggle: useCallback(() => {
      if (audioRef.current?.paused) {
        audioRef.current.play()
      } else {
        audioRef.current?.pause()
      }
    }, []),
    seek: useCallback((time: number) => {
      if (audioRef.current) {
        audioRef.current.currentTime = time
      }
    }, []),
    setVolume: useCallback((volume: number) => {
      if (audioRef.current) {
        audioRef.current.volume = volume
      }
    }, []),
    mute: useCallback(() => {
      if (audioRef.current) {
        audioRef.current.muted = true
      }
    }, []),
    unmute: useCallback(() => {
      if (audioRef.current) {
        audioRef.current.muted = false
      }
    }, []),
  }
 
  return [state, controls]
}