Components / Audio
Audio Player
A composable recording player for AGM audio, committee meetings, and voice notes. The pre-built bar matches the forest-green transcript strip: skip controls, lime play button, a real amplitude waveform decoded from the recording, duration, recording label, and download. Use position="inline" in detail panels or position="docked" fixed to the viewport bottom during transcript review.
Inline bar
Sits in normal document flow inside a detail panel, document preview, or task view. Rounded corners and a subtle shadow separate it from surrounding content.
Docked bar with transcript sync
Fixed to the bottom while managers read a verbatim transcript. Use Transcript Viewer in timeline mode with breaks that include speaker, role, and section labels. Click a segment to jump; the active segment highlights as playback progresses.
import { AudioPlayerBar, AudioPlayerProvider } from "@/components/ui/audio-player"
import {
TranscriptViewerContainer,
TranscriptViewerTimeline,
} from "@/components/ui/transcript-viewer"
const breaks = [
{
id: "opening",
label: "Call to order",
speaker: "David Okonkwo",
role: "Chair",
afterWordIndex: 7,
},
]
<AudioPlayerProvider item={recording}>
<main className="pb-24">
<TranscriptViewerContainer words={wordTimings} breaks={breaks} variant="timeline">
<TranscriptViewerTimeline />
</TranscriptViewerContainer>
</main>
<AudioPlayerBar position="docked" label="AGM recording" downloadHref={recording.src} />
</AudioPlayerProvider>// Each break can include speaker, role, and label metadata
{
"breaks": [
{
"id": "opening",
"label": "Call to order",
"speaker": "David Okonkwo",
"role": "Chair",
"afterWordIndex": 7
}
]
}With Transcript Viewer
Pair the docked bar with Transcript Viewer when you have word-level timings from transcription. The current word highlights in lime as the recording plays; click any word to seek.
Real waveform
When peaks are not supplied, AudioPlayerProvider fetches and decodes the audio file with the Web Audio API, then renders amplitudes on a canvas scrubber. Played bars turn lime and the white playhead shares the same currentTime / duration ratio so the highlight stays aligned during playback and scrubbing.
import { extractAudioPeaksFromSrc } from "@/components/ui/audio-player"
// Optional: pre-compute peaks server-side or at upload time
const peaks = await extractAudioPeaksFromSrc({ src: recording.src })
<AudioPlayerProvider item={recording} peaks={peaks}>
<AudioPlayerBar label="AGM recording" />
</AudioPlayerProvider>Installation
npx shadcn add https://raw.githubusercontent.com/levi-putna/elements/main/registry/audio-player/registry.jsonUsage
import {
AudioPlayerBar,
AudioPlayerProvider,
} from "@/components/ui/audio-player"
const recording = {
id: "agm-2026",
src: "/recordings/harbour-view-agm-2026.mp3",
}
<AudioPlayerProvider item={recording}>
<AudioPlayerBar
label="AGM recording"
downloadHref={recording.src}
downloadFilename="harbour-view-agm-2026.mp3"
/>
</AudioPlayerProvider>Reference
AudioPlayerProvider
| Prop | Type | Default | Description |
|---|---|---|---|
| item | AudioPlayerItem | - | Initial track with id, src, and optional data payload. |
| peaks | number[] | - | Optional pre-computed waveform bar heights (0–1). Decoded from the audio file via Web Audio when omitted. |
| onSeek | ({ time: number }) => void | - | Called when the user seeks via waveform or skip controls. |
AudioPlayerBar
| Prop | Type | Default | Description |
|---|---|---|---|
| position | "inline" | "docked" | "inline" | Inline in page flow, or fixed to the viewport bottom for transcript review. |
| label | string | - | Recording name shown on the right (e.g. "AGM recording"). |
| showRecordingDot | boolean | true | Red indicator dot before the label. |
| skipSeconds | number | 15 | Seconds to skip backward or forward. |
| downloadHref | string | - | URL for the download action. |
| downloadFilename | string | - | Suggested filename for the download attribute. |