Audio player
Play audio, seek through it, and inspect its transcript.
Preview
Design guidance
Use compact controls beside generated speech, voice notes, or a transcript. Add a waveform when the shape of the clip helps review. One Slider owns seeking; the waveform adds no duplicate keyboard stop. The player starts only after a user action.
WaveSurfer owns media playback and rendering. Ghost supplies Button, Slider, Select, and Loader. Each root owns one clip; an application can coordinate multiple players through useAudioPlayer().getMediaElement(). Upload, speech generation, transport, and SDK policy remain caller-owned.
Installation
Package consumers can install wavesurfer.js, @fix-webm-duration/fix and @fix-webm-duration/parser, then import from @ghost-ui/core/audio. Basic component imports keep the audio dependencies optional.
The bundled example clip is a local speech sample. Replace its URL with your own asset.
Download example files — JSX recipes, demo styles and any sample assets. Install the component first.
Basic usage
Use a real media URL. Cross-origin audio needs the server's CORS support. WaveSurfer fetches and decodes audio for its waveform, including compact mode. For large clips, supply real precomputed peaks together with the measured duration to avoid full client decoding. Keep those arrays stable between renders.
Structures
AudioPlayer and AudioPlayer.Root use the same owner. Omit children for the default PlayPause, Seek, and Time row. ControlBar arranges PlayPause, Seek and Time with optional Mute, Download and Speed. Seek explicitly selects Slider’s 16px rail; it does not inherit the 32px field size. useAudioPlayer() exposes state, playPause, seek(seconds), setRate(rate), toggleMute, decoded waveform, retry, and native media access for optional integrations.
Examples
Voice message
Playback controls
Mute follows native volumechange events, including changes made through the media element. Speed uses a compact menu. Its value follows the native playback rate, including external changes. Changing speed preserves pitch and keeps playback running.
Compact seek sizes
Choose a 16px or 20px contained seek rail. Playback buttons and time stay in ControlBar.
Selected audio interval
Selection takes a controlled tuple in seconds. Its default step is 0.01 seconds. Set step for a different precision. Fractional time labels show the selected bounds, and opposing handles stay separate for short intervals. Its waveform spans the full decoded source. Audio outside the selected interval stays visible in a muted color. The 40px rail highlights the selected interval; transparent grip areas keep both edges clear. Silence has a thin baseline instead of a blank gap. Pass the same tuple to Root’s playbackRange to preview that interval. Play stays within its bounds, stops at the end and can replay it. Changing bounds pauses playback. The playhead and small seek control show playback position; they do not move the selection handles. Time shows elapsed time and length within the preview interval. Download retrieves the original source. Selecting bounds does not encode a shorter file.
Use size="xl" for the 44px panel.
Source replacement and error recovery
The first URL is unavailable. Retry reloads that URL; the example action replaces it with the bundled clip. A replacement resets playback and ignores events from the old engine.
A media error keeps custom child content, such as a supplied transcript, visible. Transport controls remain disabled until Retry succeeds.
Loading disables playback and seeking and shows an unknown duration as --:--. A load or playback failure shows Retry. Ended clips can be replayed. Unmount destroys the engine and its listeners.
Compare takes
Choose a take without changing another player's transport. Selection remains application state.
Timed transcript
The transcript uses the same media clock and seek command. This speech sample has a supplied whole-turn interval. Word highlighting requires separate word timing; it is not inferred from the text.
API reference
AudioPlayer.Rootsrcrequired | string | — | Audio URL. Changing it replaces the owned media instance. |
|---|
labelrequired | string | — | Accessible clip name used by the group and controls. |
|---|
variant | 'compact' | 'waveform' | 'compact' | Controls whether the real waveform is visible. |
|---|
peaks | number[][] | — | Real precomputed channel amplitudes, supplied with duration. |
|---|
duration | number | — | Initial measured duration hint in seconds. Required with peaks; playback refines it when native timing is confirmed. |
|---|
playbackRange | readonly [number, number] | — | Optional preview bounds in source seconds. Play and Seek stay within the interval. Changing bounds pauses playback; completion replays from its start. Omit for full-source playback. Time reports interval-relative elapsed time; state currentTime and duration remain source seconds. |
|---|
onStateChange | (state: AudioPlayerState) => void | — | Reports loading, ready, playing, paused, ended, or error; currentTime and duration use seconds. |
|---|
onError | (error: Error) => void | — | Reports media loading or playback failure. |
|---|
children | React.ReactNode | — | Optional composition of the shared parts. |
|---|
Root accepts native div attributes and forwards its ref. PlayPause accepts IconButton appearance and disabled props; its default size is "icon-xs". Seek accepts Slider layout and disabled props; its value, bounds, and change handler remain media-owned. Time accepts native span props. Download accepts native anchor props plus filename and size (default "icon-xs"); cross-origin download behavior follows the browser. Speed offers 0.75×, 1×, 1.25×, 1.5×, and 2× through Select.
ControlBar forwards native div attributes and its ref. Mute accepts IconButton appearance and disabled props (default size "icon-xs"); its action and pressed state are media-owned. AudioPlayerState includes muted and volume from the native media element. Selection forwards native div attributes/ref and takes value: readonly [number, number], onValueChange(value: [number, number]), size: "lg" | "xl" (default "lg"), and step: number in seconds (default 0.01). It owns selection controls. Root’s optional playbackRange controls which interval Play previews. Without that prop, playback covers the full source. Waveform forwards SVG attributes/ref, accepts optional normalized samples, and otherwise reads the source’s decoded peaks. Invalid amplitudes become zero. It is decorative and has no input semantics.
Supply a measured duration when timing is known. It keeps the displayed bounds stable. AudioRecorder measures the completed file from decoded samples, repairs missing WebM duration metadata and supplies that file length to Preview. A genuine native playback end can refine the final endpoint. Without a supplied duration, the player uses decoded sample length after decoding.
Ordinary sources wait for media metadata before becoming ready. Supplying duration alone keeps that wait. Real precomputed peaks use their paired measured duration for the optimized loading path.