Elements

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.

Document attachment preview

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.

Meeting transcript review
Loading transcript...
tsx
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>
json
// 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.

Word-by-word sync
Loading word timings...

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.

tsx
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

bash
npx shadcn add https://raw.githubusercontent.com/levi-putna/elements/main/registry/audio-player/registry.json

Usage

tsx
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

PropTypeDefaultDescription
itemAudioPlayerItem-Initial track with id, src, and optional data payload.
peaksnumber[]-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

PropTypeDefaultDescription
position"inline" | "docked""inline"Inline in page flow, or fixed to the viewport bottom for transcript review.
labelstring-Recording name shown on the right (e.g. "AGM recording").
showRecordingDotbooleantrueRed indicator dot before the label.
skipSecondsnumber15Seconds to skip backward or forward.
downloadHrefstring-URL for the download action.
downloadFilenamestring-Suggested filename for the download attribute.