Audio Player

A compact player for a single audio clip — AI-generated speech or a recorded voice message — with play/pause, ±10s seek, scrub, timecodes, and volume.

0:00 / 0:00
<AudioPlayer src="/speech.mp3" />

Usage

import { AudioPlayer } from "@reva/ui";

AudioPlayer drives a single native <audio> element with Reva primitives — IconButton, Slider, and Text — so it themes off design tokens and adds no media dependency. It's a Reva-native rewrite of the shadcn.io / AI-Elements "AI Audio Player" (which wraps media-chrome), keeping the same controls but a batteries-included, single-component API.

Pass src (a URL, object URL, or data URL) or data (an AI SDK-style base64 speech result). The default variant shows the full transport — rewind, play, forward, scrub, timecodes, mute, and volume. The compact variant drops the seek and volume controls for tight surfaces like chat bubbles.

Recorded clips get a real duration

MediaRecorder webm/ogg blobs often report duration === Infinity until scanned. AudioPlayer handles this internally (it seeks to the end once on load to force the browser to compute the duration, then rewinds), so the scrubber and timecodes work for voice recordings without any caller effort.

Examples

Compact

Use variant="compact" inside message bubbles or list rows — play, scrub, and timecodes only.

–:–
<AudioPlayer variant="compact" src="/voice-note.webm" />

Lazy loading

When a clip lives behind a fetch (e.g. a chat attachment resolved on demand), leave src empty and pass onActivate. Pressing play calls it — show a busy state with loading while the bytes resolve, then set src; playback starts automatically because the press counts as the user gesture.

–:–
function LazyClip({ documentId }: { documentId: string }) {const [activated, setActivated] = React.useState(false);const preview = useDocumentPreview(documentId, activated);return (  <AudioPlayer    variant="compact"    src={preview.objectUrl}    loading={activated && preview.status === "loading"}    onActivate={() => setActivated(true)}  />);}

AI-generated speech

Pass a base64 speech result straight from the AI SDK via data — no object URL needed. src takes precedence when both are set.

import { experimental_generateSpeech as generateSpeech } from "ai";

const { audio } = await generateSpeech({ model, text });

<AudioPlayer data={{ base64: audio.base64, mediaType: audio.mediaType }} />;

Accessibility

  • The root is a role="group" named by label; every control is an IconButton with an explicit aria-label (Play / Pause, Rewind 10 seconds, Forward 10 seconds, Mute / Unmute).
  • The scrub and volume Sliders are keyboard-operable (arrow keys, Home/End) via Radix and carry aria-labels (Seek / Volume).
  • Controls are disabled until a source is available (unless onActivate is provided, so the play control can trigger the fetch).

Props

PropTypeDefaultDescription
srcstringAudio source URL — object URL, remote URL, or data URL. Takes precedence over data.
data{ base64: string; mediaType?: string }AI SDK-style base64 speech result, used when src is absent (default media type audio/mpeg).
variant"default" | "compact""default"compact drops the seek + volume controls for tight surfaces.
loadingbooleanfalseShow a busy state on the play control (e.g. while a lazy src fetches).
onActivate() => voidCalled when play is pressed with no src yet — the cue to lazily fetch. Playback auto-starts once src arrives.
autoPlaybooleanfalseBegin playback as soon as the clip is ready.
labelstring"Audio player"Accessible name for the player group.
classNamestringMerged onto the root via cn().

On this page