Core audio helper and singleton for loading and controlling HTML5 audio.
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
| Method | Description |
|---|---|
init() | Initialize on the client. Safe to call multiple times. |
cleanup() | Reset and release the audio element (pause, clear src). |
Playback
| Method | Description |
|---|---|
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
| Method | Description |
|---|---|
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
| Method | Description |
|---|---|
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:
| Prop | Type | Default | Description |
|---|---|---|---|
id | string | number | - | Unique identifier for the track. |
url | string | - | URL of the audio file or stream. |
title | string | - | Track title. |
artist | string | - | Artist name. |
artwork | string | - | Album artwork URL. |
images | string[] | - | Array of image URLs. |
duration | number | - | Track duration in seconds. |
album | string | - | Album name. |
genre | string | - | Genre. |
live | boolean | - | 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 volumeReact 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);
});Related
- Audio Store — Zustand store for queue and playback state management
- Audio Player — Composable player UI components
Notes
- Singleton pattern: All methods access the same
$htmlAudioinstance - 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
requestAnimationFramefor smooth transitions - Live streams: Disable seeking and use extended timeout for reliability
formatDurationhandles edge cases (NaN, Infinity, negative values)
Changelog
2025-12-24 useAudio integration and live stream improvements
- Changed:
isLive()is now a method on theHtmlAudioclass instead of a standalone function - Removed:
LIVE_STREAM_PATTERNSURL pattern matching (now uses duration-based detection) - Improved:
isLive()now correctly returnsfalseforduration === 0 - Added: Integration with
useAudio()hook for React components - Improved:
setPlaybackRate()usesisLive()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_PATTERNSmoved to top-level scope for better performance - Improved: Live stream detection uses consistent patterns across the library
Last updated 12/24/2025
On This Page