HTML Audio

PreviousNext

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

References

The $htmlAudio 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

pnpm dlx shadcn@latest add @audio/html

Import

Import the singleton and helpers from the audio library:

import { $htmlAudio, formatDuration, type Track } from "@/lib/html-audio";

Or use the React hook for better integration:

import { useAudio } from "@/hooks/use-audio";
 
function MyComponent() {
  const { htmlAudio } = useAudio();
  // Use htmlAudio instead of $audio
}

Core API

$htmlAudio (Singleton)

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

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

Client initialization: The $htmlAudio singleton must be initialized on the client. Call $htmlAudio.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.
pause()Pause playback immediately.
seek(time)Seek when metadata is available. Ignored for live streams.

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.

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:

$htmlAudio.addEventListener("bufferingStart", () => console.log("Buffering..."));
$htmlAudio.addEventListener("bufferingEnd", () => console.log("Ready to play"));
$htmlAudio.addEventListener("playbackStarted", () => console.log("Playing"));
$htmlAudio.addEventListener("audioError", () => console.error("Error"));
$htmlAudio.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/html-audio";
 
formatDuration(125); // "2:05"
formatDuration(3661); // "61:01"

isLive(duration)

Check if a duration value indicates a live stream. This is a method on the HtmlAudio class.

import { useAudio } from "@/hooks/use-audio";
 
function MyComponent() {
  const { htmlAudio } = useAudio();
  const duration = htmlAudio.getDuration();
  
  if (htmlAudio.isLive(duration)) {
    // Handle live stream (no seeking)
  }
}

Live Stream Detection: The isLive() method checks if a duration is NaN, Infinity, or -Infinity. A duration of 0 is not considered a live stream (it just means the duration hasn't loaded yet).

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 { $htmlAudio } from "@/lib/html-audio";
 
$htmlAudio.init();
 
async function playTrack(url: string) {
  try {
    await $htmlAudio.load(url, 0);
    await $htmlAudio.play();
  } catch (error) {
    console.error("Playback failed:", error);
  }
}

Volume Management

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

React Component

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

Event Monitoring

import { $htmlAudio } from "@/lib/html-audio";
 
$htmlAudio.addEventListener("bufferingStart", () => {
  console.log("Loading audio...");
});
 
$htmlAudio.addEventListener("bufferingEnd", () => {
  console.log("Ready to play");
});
 
$htmlAudio.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 $htmlAudio 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)

Changelog

2025-12-24 useAudio integration and live stream improvements

  • Changed: isLive() is now a method on the HtmlAudio class instead of a standalone function
  • Removed: LIVE_STREAM_PATTERNS URL pattern matching (now uses duration-based detection)
  • Improved: isLive() now correctly returns false for duration === 0
  • Added: Integration with useAudio() hook for React components
  • Improved: setPlaybackRate() uses isLive() method for live stream detection

2025-12-02 Live stream playback rate protection

  • Added: setPlaybackRate() now prevents rate changes for live streams
  • Fixed: LIVE_STREAM_PATTERNS moved to top-level scope for better performance
  • Improved: Live stream detection uses consistent patterns across the library

Last updated 12/24/2025