# Audio Player

> A React audio player with a waveform you can see and scrub, as a podcast card, a chat voice message or a thin call-log bar.

- Type: React component (Media)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.0.0
- Install: `npx wingo-ui@latest add audio-player`
- Docs: https://wingo-ui.com/components/audio-player

## Interaction

Press play: the icon turns into pause on a quick spring, the played bars fill in the tone as the recording runs and the time counts up. Tap or click anywhere on the waveform to jump there; drag along it to scrub (the bars and the time follow the finger, the audio seeks on release, and every 120ms with a mouse so you hear where you are). With a mouse, hovering shows a cursor line with the time above it. The card has skip back / forward buttons (15 seconds), a speed menu (a bottom sheet on phones), a volume button with a slider (hidden on touch screens, where the hardware keys do it) and an optional download. The compact voice-message player cycles 1×, 1,5×, 2× on each tap of its chip and shows the length until it starts, then the position. Starting one player pauses the others. While playing, the phone's lock screen and headphone buttons control it. Keyboard: Tab reaches the play button, the waveform and each control; on the waveform Left / Right (or Up / Down) seek 5 s, PageUp / PageDown 30 s, Home / End jump to the start or end, Space, k or Enter play and pause; anywhere in the player Space / k play, j / l seek 10 s, m mutes, Up / Down change the volume, 0 to 9 jump to 0% to 90%, < and > change the speed. A missing file shows a message with a retry. The file also exports Waveform (the bars alone for custom layouts: peaks, progress as a number or a motion value, barCount / barWidth / barGap / barRadius / height, tone / color, onSeek(fraction) makes it a slider with click, drag and arrow keys, onScrub(fraction | null), formatBubble(fraction) for the hover bubble, peaksKey to fade in new bars), computePeaks(buffer, count) (max absolute sample per bucket, normalized 0 to 1), decodePeaks(src, count, { signal }) (fetch, decodeAudioData and computePeaks, cached per src and count) and placeholderPeaks(seed, count) (stable speech-like bars, the same on the server and the client).

## Installation

```bash
npx wingo-ui@latest add audio-player
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/audio-player` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/ui/audio-player.tsx`

npm dependencies: `npm i motion lucide-react`

Wingo UI dependencies (installed automatically):

- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [Dropdown Menu](https://wingo-ui.com/components/dropdown-menu) ([Markdown](https://wingo-ui.com/components/dropdown-menu.md))
- [Popover](https://wingo-ui.com/components/popover) ([Markdown](https://wingo-ui.com/components/popover.md))
- [Slider](https://wingo-ui.com/components/slider) ([Markdown](https://wingo-ui.com/components/slider.md))
- [Spinner](https://wingo-ui.com/components/spinner) ([Markdown](https://wingo-ui.com/components/spinner.md))
- [Tooltip](https://wingo-ui.com/components/tooltip) ([Markdown](https://wingo-ui.com/components/tooltip.md))
- [useMediaControls](https://wingo-ui.com/components/use-media-controls) ([Markdown](https://wingo-ui.com/components/use-media-controls.md))
- [useLocale](https://wingo-ui.com/components/use-locale) ([Markdown](https://wingo-ui.com/components/use-locale.md))
- [File export](https://wingo-ui.com/components/file-export) ([Markdown](https://wingo-ui.com/components/file-export.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [Number format](https://wingo-ui.com/components/number-format) ([Markdown](https://wingo-ui.com/components/number-format.md))
- [Tones](https://wingo-ui.com/components/tones) ([Markdown](https://wingo-ui.com/components/tones.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
import { AudioPlayer } from "@/components/ui/audio-player"

export function Episode() {
  return (
    <AudioPlayer
      src="/demo/podcast.mp3"
      title="Sales tax for small businesses"
      subtitle="Summit Podcast · Ep. 12 · Sep 24, 2026"
      cover="/demo/mountains.jpg"
      downloadable
      labels={{ download: "Save episode" }}
    />
  )
}

export function VoiceNote() {
  return <AudioPlayer variant="compact" src="/demo/voice-message.mp3" labels={{ play: "Listen", pause: "Stop" }} />
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `src` | `string` |  | The audio file url. Decoding peaks needs the file on the same origin or served with CORS. |
| `title?` | `ReactNode` | `""` | The card's title, and the lock-screen title while playing (when it is a string). |
| `subtitle?` | `ReactNode` | `""` | A muted line under the title (show, author, date); the lock-screen artist while playing. |
| `cover?` | `string` |  | Square artwork url for the card. It sits at 94% while paused and grows to full size while playing. Also the lock-screen artwork. |
| `leading?` | `ReactNode` |  | Compact only: an avatar or a mic icon before the play button, the chat look. |
| `variant?` | `"card" \| "compact" \| "minimal"` | `"card"` | card: cover, title, waveform and a controls row on a raised panel. compact: the voice message (play, bars, time, speed chip) for chat bubbles. minimal: play, a thin scrubbable bar and the time for lists and call logs. |
| `tone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | Colors the played bars and the play button. neutral is black in light mode and white in dark mode. |
| `color?` | `string` | `""` | Any CSS color for the played bars and the play button; replaces the tone. The play icon turns black or white by lightness. |
| `size?` | `"sm" \| "md"` | `"md"` | Button sizes: md has a 40px play button in the card (32px compact and minimal), sm 32px (28px). Every button keeps a 44px touch target. |
| `radius?` | `"none" \| "sm" \| "md" \| "lg"` | `"md"` | Corner radius of the card surface (12 / 16 / 24px) and its cover. |
| `barCount?` | `number` | `0` | How many bars. 0 fits as many as the width allows (barWidth + barGap each, remeasured on resize); a fixed count spreads over the width. |
| `barWidth?` | `number` | `3` | Width of each bar in px. |
| `barGap?` | `number` | `2` | Space between bars in px. |
| `barRadius?` | `number` | `2` | Corner radius of each bar in px. |
| `waveformHeight?` | `number` | `0` | Height of the waveform in px. 0 is automatic: 40px in the card, 28px in the compact player. |
| `peaks?` | `number[]` |  | Precomputed bar heights from 0 to 1, any length (resampled to the bar count, max per bucket). Skips decoding. |
| `decodePeaks?` | `boolean` | `true` | Without peaks, fetch and decode the file with Web Audio once the player is on screen (one decode per file, cached), fading from placeholder bars to the real waveform. A CORS-blocked file stays on the placeholder bars. The file also exports a decodePeaks(src, count, { signal }) function that returns the same peaks as a Promise<number[]>, for precomputing them yourself. |
| `skipSeconds?` | `number` | `15` | Seconds for the card's skip back / forward buttons and the lock-screen skip. 0 hides the buttons. |
| `rates?` | `number[]` |  | Playback speeds. Compact cycles through them on tap (default [1, 1.5, 2]); the card opens a menu (default MEDIA_RATES, 0.5 to 2). One entry hides the speed control. |
| `showVolume?` | `"auto" \| "always" \| "never"` | `"auto"` | The card's volume button and slider. auto hides it on touch screens, where people use the hardware keys. |
| `downloadable?` | `boolean` | `false` | A download button (card and minimal) that saves the file through file-export's downloadUrl and announces it. |
| `fileName?` | `string` | `""` | The downloaded file's name, sanitized, keeping the source's extension. Empty keeps the name from the url. |
| `exclusive?` | `boolean` | `true` | Starting this player pauses every other exclusive player on the page, so two voice notes never talk over each other. |
| `mediaSession?` | `boolean` | `true` | While playing, sets the lock-screen metadata (title, subtitle, cover) and handles play, pause, seek back / forward and seek-to from the lock screen and headphones. |
| `preload?` | `"none" \| "metadata" \| "auto"` | `"metadata"` | The audio element's preload. metadata loads the duration only; none shows --:-- until the first play. |
| `loop?` | `boolean` | `false` | Starts over at the end. |
| `locale?` | `string` | `""` | Formats the speed labels (1.5× in en-US, 1,5× in de-DE). Empty uses UIProvider, then the page language. |
| `onPlay?` | `() => void` |  | Playback started (passed to useMediaControls). |
| `onPause?` | `() => void` |  | Playback paused. |
| `onEnded?` | `() => void` |  | The recording reached its end (the play icon turns into replay). |
| `onError?` | `(error: MediaError \| null) => void` |  | The file could not be loaded or played; the player shows the error row with a retry. |
| `onTimeUpdate?` | `(seconds: number) => void` |  | Whole seconds while playing and after seeks: resume positions, listening analytics. |
| `setApi?` | `(api: AudioPlayerApi) => void` |  | The media controls without the ref: playing, second, duration, progress (a motion value), play(), pause(), seek(seconds), seekBy, setRate... Called after mount and again when the state changes (at most once a second while playing), so it can go straight into state. |
| `labels?` | `Partial<AudioPlayerLabels>` |  | Every string: play "Play", pause "Pause", replay "Replay", loading "Loading", seek "Seek", skipBack(s) "Back {s} seconds", skipForward(s), speed "Speed", cancel "Cancel" (the speed sheet on phones), volume "Volume", mute "Mute", unmute "Unmute", download "Download", downloaded(name), error "The recording could not be played", retry "Try again", live "Live", position(current, total) "{current} of {total}". DEFAULT_AUDIO_PLAYER_LABELS holds the English set. |
| `classNames?` | `Partial<Record<AudioPlayerSlot, string>>` |  | Classes for the parts: audio, header, cover, meta, title, subtitle, leading, play, waveform, bar, time, controls, skipBack, skipForward, rate, volume, download, error, live. |

## Parts

### Waveform (component)

The bar waveform on its own: draws peaks with a played share, and with onSeek becomes a slider you click, tap, drag or move with the arrow keys. Use it for a custom player layout.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `peaks` | `readonly number[]` |  | Bar heights from 0 to 1, any length; resampled to the bar count. |
| `progress?` | `number \| MotionValue<number>` | `0` | The played share from 0 to 1; a motion value moves without re-rendering. |
| `barCount?` | `number` | `0` | 0 fits as many bars as the width allows. |
| `barWidth?` | `number` | `3` | Bar width in px. |
| `barGap?` | `number` | `2` | Gap between bars in px. |
| `barRadius?` | `number` | `2` | Bar corner radius in px. |
| `height?` | `number` | `40` | Height in px. |
| `tone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | Color of the played bars. |
| `color?` | `string` |  | Any CSS color for the played bars; replaces the tone. |
| `onSeek?` | `(fraction: number) => void` |  | Makes the bars a slider: a click or tap seeks, a drag scrubs. |
| `onScrub?` | `(fraction: number \| null) => void` |  | The fraction under the pointer while dragging, then null. |
| `seekInterval?` | `number` | `120` | ms between seeks while dragging with a mouse; 0 seeks only on release. |
| `formatBubble?` | `(fraction: number) => string` |  | The text of the bubble above the cursor, such as 1:12. |
| `keyStep?` | `number` | `0.05` | Arrow key step as a fraction; PageUp and PageDown move four steps. |
| `disabled?` | `boolean` | `false` | Turns off seeking. |
| `peaksKey?` | `string \| number` | `"peaks"` | Change it to fade in new bars, from placeholder to decoded peaks. |

### computePeaks (function)

Turns a decoded AudioBuffer into bar heights: the loudest sample per bucket over the first two channels, normalized so the loudest bar is 1. Signature: (buffer: AudioBuffer, count: number) => number[].

### placeholderPeaks (function)

Stable pseudo-random bars from any string (usually the src), shaped like speech with short pauses, shown until the real peaks arrive. Signature: (seed: string, count: number) => number[].

### DEFAULT_AUDIO_PLAYER_LABELS (constant)

The English strings of the buttons, the slider and the error state. Spread it to translate a few. Type: AudioPlayerLabels.

### AudioPlayerApi (type)

What setApi receives: the useMediaControls result without its ref (playing, duration, second, progress, play(), pause(), toggle(), seek(seconds), seekBy(delta), setRate and the volume controls).

## Examples

- **Voice messages in a chat**: Compact players with an avatar. Exclusive: starting one pauses the other. The chip cycles 1×, 1.5×, 2×.
- **Decoded peaks**: No peaks prop: the file is decoded with Web Audio when the card scrolls into view.
- **Call log**: The minimal variant in CRM rows: play, a thin scrubbable bar, the time and a download.
- **Custom color, dense bars**: color="#7C3AED" with barWidth 2 and barGap 1, a taller waveform and a download button.
- **Error and retry**: A missing file shows the message and a retry that reloads the element.
- **Transcript with setApi**: The controls object drives a transcript: the current sentence highlights, a tap seeks there.
- **Sizes and tones**: md and sm buttons for the compact and minimal players, with a tone on the played part.

## See also

- [useMediaControls](https://wingo-ui.com/components/use-media-controls) ([Markdown](https://wingo-ui.com/components/use-media-controls.md))
- [Video Player](https://wingo-ui.com/components/video-player) ([Markdown](https://wingo-ui.com/components/video-player.md))
- [Voice Input](https://wingo-ui.com/components/voice-input) ([Markdown](https://wingo-ui.com/components/voice-input.md))
- [Chat Message](https://wingo-ui.com/components/chat-message) ([Markdown](https://wingo-ui.com/components/chat-message.md))
- [File export](https://wingo-ui.com/components/file-export) ([Markdown](https://wingo-ui.com/components/file-export.md))

---

Canonical: https://wingo-ui.com/components/audio-player
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
