npx shadcn@latest add https://sdk-components.thesqd.com/r/audio-player.jsonimport { AudioPlayer, type AudioPlayerHandle } from "@/components/ui/audio-player";import { AudioPlayer } from "@/components/ui/audio-player";
export function Example() {
return <AudioPlayer src="/audio/voice-sample.wav" />;
}AudioPlayer (single self-contained client component)
├── src (audio URL — fetched, decoded, cached per URL)
├── peaks (optional number[] 0…1 — skips the decode pass)
├── height / barWidth (waveform geometry)
├── barGap / barRadius (…)
├── minBarHeight (floor so silence still reads as a waveform)
├── playOnSeek (click/drag also starts playback — default true)
├── showHandle (playhead line + drag handle — default true)
├── showTime (elapsed / total time row — default true)
├── scroll / pixelsPerSecond (fixed-scale rail — on by default, 60px/s)
├── showSpeed (speed toggle in the time row — default false)
├── speeds / defaultSpeed (cycle steps, default [0.5…2] starting at 1)
├── onPlayingChange / onSeeked (playback + seek callbacks)
└── ref → AudioPlayerHandle ({ play, pause, toggle, seek, audio })
┌──────────────────────────────────────────┐
│ ▂▅█▃▁▇▄ waveform canvas ╎ ▁▃▂▅▁▂▄ │ ← scrub surface (role="slider")
│ ● │ ← playhead + handle
└──────────────────────────────────────────┘
0:07 1× 1:24 ← time + speed row
Renders a <canvas> waveform over a hidden <audio> element. The waveform IS
the control — there is no play button, no volume, no speed menu. While
dragging, the elapsed label tracks the handle rather than playback. Keyboard:
←/→ nudge 5s (10s with Shift), Space/Enter toggles playback.| Prop | Type | Default | Description |
|---|---|---|---|
src | string | — | Audio file URL. Fetched once and decoded to peaks; the result is cached per URL across mounts. |
peaks | number[] | — | Pre-computed waveform peaks in the 0–1 range. Supply these to skip the fetch + decode pass entirely — useful when the server already stores them. |
height | number | string | 64 | Waveform height. A number is treated as pixels. |
barWidth | number | 3 | Width of each bar in px. |
barGap | number | 2 | Gap between bars in px. Bar count is `floor(width / (barWidth + barGap))`. |
barRadius | number | 2 | Corner radius of each bar in px. |
minBarHeight | number | 2 | Minimum bar height in px, so silent stretches still read as a waveform rather than a gap. |
playOnSeek | boolean | true | Start playback when the user clicks or finishes a drag. Turn off when an external transport owns play/pause. |
showHandle | boolean | true | Draw the playhead line and its circular drag handle over the waveform. |
showTime | boolean | true | Show the elapsed / total time row under the waveform. Elapsed follows the handle while dragging; total reads the clip duration once metadata loads. |
scroll | boolean | true | Lay the waveform out at a fixed pixels-per-second scale inside a horizontal `scroll-fade-x` scroller, following the playhead during playback. Set `false` to squeeze the whole clip into the container width instead. |
pixelsPerSecond | number | 60 | Horizontal scale of the rail. Ignored when `scroll` is `false`, where the waveform always spans the container. |
showSpeed | boolean | false | Show the playback-speed toggle in the time row. Clicking it cycles through `speeds` and sets `playbackRate` on the underlying element. |
speeds | number[] | [0.5, 0.75, 1, 1.25, 1.5, 2] | The speeds the toggle cycles through, in order. |
defaultSpeed | number | 1 | Speed to start at. Include it in `speeds` or the cycle can never return to it. |
onSpeedChange | (speed: number) => void | — | Fires with the new rate each time the toggle advances. |
onPlayingChange | (playing: boolean) => void | — | Fires on play, pause, and end — use it to drive your own transport button. |
onSeeked | (time: number) => void | — | Fires with the new time (seconds) after a seek commits. |
className | string | — | Merged onto the root element via `cn`. |
| Prop | Type | Default | Description |
|---|---|---|---|
play() | () => Promise<void> | void | — | Start playback. |
pause() | () => void | — | Pause playback. |
toggle() | () => void | — | Play if paused, pause if playing. |
seek(time) | (time: number) => void | — | Jump to an absolute time in seconds. |
audio | HTMLAudioElement | null | — | The underlying media element, for anything the handle doesn't cover (volume, playbackRate, …). |