Audio

PreviousNext

Core audio helper and singleton for loading and controlling HTML5 audio.

The $audio singleton manages playback of HTML5 audio with automatic retry logic, event handling, and volume fading. Use it alongside the Audio Store for full player functionality.

Installation

Import the singleton and helpers from the audio library:

import { $audio, formatDuration, isLive, type Track } from "@/lib/audio"

Core API

$audio (Singleton)

Manages the underlying HTMLAudioElement, playback state, retries, and events. Initialize on client start — the instance is server-safe.

import { $audio } from "@/lib/audio"
 
// Initialize on the client
$audio.init()
 
// Load and play
await $audio.load("https://example.com/audio.mp3", 0)
await $audio.play()

Client initialization: The $audio singleton must be initialized on the client. Call $audio.init() from a client component or inside useEffect() so the underlying HTMLAudioElement is created only in the browser environment.

Lifecycle

MethodDescription
init()Initialize on the client. Safe to call multiple times.
cleanup()Reset and release the audio element (pause, clear src).

Playback

MethodDescription
load(url, startTime?, isLive?)Load an audio source and wait for readiness. Pass isLive for live streams (longer timeout, no seek). Returns Promise<void>.
play()Start or resume playback. Returns promise that resolves when browser allows playback.

Browser autoplay restrictions: The play() call returns a promise which may be rejected by browser autoplay policies if there was no user gesture. Wrap calls to play() in a try/catch and provide a fallback UI so your app handles playback interruptions gracefully.

| pause() | Pause playback immediately. | | seek(time) | Seek when metadata is available. Ignored for live streams. |

Live streams: For live streams (when isLive is true) seeking is disabled and long timeouts are used. Live streams are best handled with a separate code-path because the playback semantics differ from on-demand audio files.

Volume

MethodDescription
setVolume(volume, fadeTime?)Set or fade volume (0–1). If fadeTime > 0, animates smoothly.
getVolume()Return current volume (0–1).
setMuted(muted)Mute or unmute. Restores previous volume when unmuting.

State

MethodDescription
getDuration()Return loaded source duration (seconds) or 0 if unavailable.
getCurrentTime()Return current playback position (seconds).
isPaused()Return boolean — is playback paused.
getBufferedRanges()Return underlying TimeRanges or null.
getSource()Return current source URL string.
getAudioElement()Return raw HTMLAudioElement or null on server.

Events

The library emits custom events via an internal EventTarget:

$audio.addEventListener("bufferingStart", () => console.log("Buffering..."))
$audio.addEventListener("bufferingEnd", () => console.log("Ready to play"))
$audio.addEventListener("playbackStarted", () => console.log("Playing"))
$audio.addEventListener("audioError", () => console.error("Error"))
$audio.addEventListener("bufferUpdate", (e) => {
  if (e instanceof CustomEvent) console.log("Buffered:", e.detail.bufferedTime)
})

Utilities

formatDuration(seconds)

Format seconds to MM:SS string. Handles invalid input by returning "0:00".

import { formatDuration } from "@/lib/audio"
 
formatDuration(125) // "2:05"
formatDuration(3661) // "61:01"

isLive(track)

Heuristic check for live streams based on URL patterns or track.live flag.

import { isLive } from "@/lib/audio"
 
if (isLive(track)) {
  // Handle live stream (no seeking)
}

Types

Track

Common audio track object with optional fields:

PropTypeDefaultDescription
idstring | number-Unique identifier for the track.
urlstring-URL of the audio file or stream.
titlestring-Track title.
artiststring-Artist name.
artworkstring-Album artwork URL.
imagesstring[]-Array of image URLs.
durationnumber-Track duration in seconds.
albumstring-Album name.
genrestring-Genre.
liveboolean-Whether this is a live stream.
[key: string]unknown-Additional properties.

Examples

Basic Playback

import { $audio } from "@/lib/audio"
 
$audio.init()
 
async function playTrack(url: string) {
  try {
    await $audio.load(url, 0)
    await $audio.play()
  } catch (error) {
    console.error("Playback failed:", error)
  }
}

Volume Management

import { $audio } from "@/lib/audio"
 
// Immediate change
$audio.setVolume(0.5)
 
// Smooth fade over 1 second
$audio.setVolume(0.8, 1000)
 
// Mute with memory
$audio.setMuted(true)
$audio.setMuted(false) // Restores previous volume

React Component

import { $audio, formatDuration } from "@/lib/audio"
import { useEffect, useState } from "react"
 
function TimeDisplay() {
  const [time, setTime] = useState(0)
 
  useEffect(() => {
    const updateTime = () => setTime($audio.getCurrentTime())
    const interval = setInterval(updateTime, 100)
    return () => clearInterval(interval)
  }, [])
 
  return <span>{formatDuration(time)}</span>
}

Event Monitoring

import { $audio } from "@/lib/audio"
 
$audio.addEventListener("bufferingStart", () => {
  console.log("Loading audio...")
})
 
$audio.addEventListener("bufferingEnd", () => {
  console.log("Ready to play")
})
 
$audio.addEventListener("audioError", (event) => {
  console.error("Audio error:", event)
})
  • Audio Store — Zustand store for queue and playback state management
  • Audio Player — Composable player UI components

Notes

  • Singleton pattern: All methods access the same $audio instance
  • Server-safe: Methods check for client-side availability before executing
  • Automatic retries: Handles load/play errors with exponential backoff (max 3 attempts)
  • Volume fading: Animations use requestAnimationFrame for smooth transitions
  • Live streams: Disable seeking and use extended timeout for reliability
  • formatDuration handles edge cases (NaN, Infinity, negative values)

Last updated 11/18/2025