No track selected
"use client"
import { PauseIcon, PlayIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import {
AudioPlayerButton,
AudioPlayerDuration,
AudioPlayerProgress,
AudioPlayerProvider,
AudioPlayerSpeed,
AudioPlayerTime,
exampleTracks,
useAudioPlayer,
} from "@/components/ui/audio-player"
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import { ScrollArea } from "@/components/ui/scroll-area"
interface Track {
id: string
name: string
url: string
}
export function AudioPlayer() {
return (
<AudioPlayerProvider<Track>>
<AudioPlayerDemo />
</AudioPlayerProvider>
)
}
const AudioPlayerDemo = () => {
return (
<Card className="w-full overflow-hidden p-0">
<div className="flex flex-col lg:h-[180px] lg:flex-row">
<div className="bg-muted/50 flex flex-col overflow-hidden lg:h-full lg:w-64">
<ScrollArea className="h-48 w-full lg:h-full">
<div className="space-y-1 p-3">
{exampleTracks.map((song, index) => (
<SongListItem
key={song.id}
song={song}
trackNumber={index + 1}
/>
))}
</div>
</ScrollArea>
</div>
<Player />
</div>
</Card>
)
}
const Player = () => {
const player = useAudioPlayer<Track>()
return (
<div className="flex flex-1 items-center p-4 sm:p-6">
<div className="mx-auto w-full max-w-2xl">
<div className="mb-4">
<h3 className="text-base font-semibold sm:text-lg">
{player.activeItem?.data?.name ?? "No track selected"}
</h3>
</div>
<div className="flex items-center gap-3 sm:gap-4">
<AudioPlayerButton
variant="outline"
size="default"
className="h-12 w-12 shrink-0 sm:h-10 sm:w-10"
disabled={!player.activeItem}
/>
<div className="flex flex-1 items-center gap-2 sm:gap-3">
<AudioPlayerTime className="text-xs tabular-nums" />
<AudioPlayerProgress className="flex-1" />
<AudioPlayerDuration className="text-xs tabular-nums" />
<AudioPlayerSpeed variant="ghost" size="icon" />
</div>
</div>
</div>
</div>
)
}
const SongListItem = ({
song,
trackNumber,
}: {
song: Track
trackNumber: number
}) => {
const player = useAudioPlayer<Track>()
const isActive = player.isItemActive(song.id)
const isCurrentlyPlaying = isActive && player.isPlaying
return (
<div className="group/song relative">
<Button
variant={isActive ? "secondary" : "ghost"}
size="sm"
className={cn(
"h-10 w-full justify-start px-3 font-normal sm:h-9 sm:px-2",
isActive && "bg-secondary"
)}
onClick={() => {
if (isCurrentlyPlaying) {
player.pause()
} else {
player.play({
id: song.id,
src: song.url,
data: song,
})
}
}}
>
<div className="flex w-full items-center gap-3">
<div className="flex w-5 shrink-0 items-center justify-center">
{isCurrentlyPlaying ? (
<PauseIcon className="h-4 w-4 sm:h-3.5 sm:w-3.5" />
) : (
<>
<span className="text-muted-foreground/60 text-sm tabular-nums group-hover/song:invisible">
{trackNumber}
</span>
<PlayIcon className="invisible absolute h-4 w-4 group-hover/song:visible sm:h-3.5 sm:w-3.5" />
</>
)}
</div>
<span className="truncate text-left text-sm">{song.name}</span>
</div>
</Button>
</div>
)
}
Installation
pnpm dlx @elevenlabs/cli@latest components add audio-player
Usage
import {
AudioPlayerButton,
AudioPlayerDuration,
AudioPlayerProgress,
AudioPlayerProvider,
AudioPlayerSpeed,
AudioPlayerSpeedButtonGroup,
AudioPlayerTime,
useAudioPlayer,
useAudioPlayerTime,
} from "@/components/ui/audio-player"Basic Player
<AudioPlayerProvider>
<div className="flex items-center gap-4">
<AudioPlayerButton />
<AudioPlayerProgress className="flex-1" />
<AudioPlayerTime />
<span>/</span>
<AudioPlayerDuration />
</div>
</AudioPlayerProvider>Playing a Specific Track
const track = {
id: "track-1",
src: "/audio/song.mp3",
data: { title: "My Song", artist: "Artist Name" }
}
<AudioPlayerProvider>
<AudioPlayerButton item={track} />
<AudioPlayerProgress />
</AudioPlayerProvider>Multiple Tracks
const tracks = [
{ id: "1", src: "/audio/track1.mp3", data: { title: "Track 1" } },
{ id: "2", src: "/audio/track2.mp3", data: { title: "Track 2" } },
{ id: "3", src: "/audio/track3.mp3", data: { title: "Track 3" } },
]
<AudioPlayerProvider>
<div className="space-y-4">
{tracks.map((track) => (
<div key={track.id} className="flex items-center gap-4">
<AudioPlayerButton item={track} />
<span className="text-sm">{track.data.title}</span>
</div>
))}
<AudioPlayerProgress className="w-full" />
<div className="flex gap-2 text-sm">
<AudioPlayerTime />
<span>/</span>
<AudioPlayerDuration />
</div>
</div>
</AudioPlayerProvider>API Reference
AudioPlayerProvider
The provider component that manages audio state and playback. Must wrap all audio player components.
<AudioPlayerProvider>{children}</AudioPlayerProvider>AudioPlayerButton
A play/pause button that controls playback. Shows a loading spinner when buffering.
Props
| Prop | Type | Description |
|---|---|---|
| item | AudioPlayerItem<TData> | Optional. The audio item to play. If not provided, controls the current track |
| ...props | ButtonProps | All standard Button component props |
AudioPlayerItem Type
interface AudioPlayerItem<TData = unknown> {
id: string | number
src: string
data?: TData
}AudioPlayerProgress
A slider that shows playback progress and allows seeking. Pauses during seeking and resumes after.
Props
| Prop | Type | Description |
|---|---|---|
| ...props | SliderProps | All Radix UI Slider props except min, max, and value |
AudioPlayerTime
Displays the current playback time in formatted time (e.g., "1:30").
Props
| Prop | Type | Description |
|---|---|---|
| className | string | Optional CSS classes |
| ...props | HTMLSpanElement | All standard span element props |
AudioPlayerDuration
Displays the total duration of the current track or "--:--" when unavailable.
Props
| Prop | Type | Description |
|---|---|---|
| className | string | Optional CSS classes |
| ...props | HTMLSpanElement | All standard span element props |
AudioPlayerSpeed
A dropdown menu button (with settings icon) for controlling playback speed. Displays "Normal" for 1x speed and shows other speeds with "x" suffix (e.g., "0.5x", "1.5x").
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| speeds | readonly number[] | [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2] | Available playback speeds |
| variant | ButtonVariant | "ghost" | Button variant |
| size | ButtonSize | "icon" | Button size |
| ...props | ButtonProps | - | All standard Button component props |
Example
<AudioPlayerSpeed variant="ghost" size="icon" />AudioPlayerSpeedButtonGroup
A button group component for quick playback speed selection.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| speeds | readonly number[] | [0.5, 1, 1.5, 2] | Available playback speeds |
| className | string | - | Optional CSS classes |
| ...props | HTMLDivElement | - | All standard div element props |
Example
<AudioPlayerSpeedButtonGroup speeds={[0.5, 0.75, 1, 1.5, 2]} />useAudioPlayer Hook
Access the audio player context to control playback programmatically.
const {
ref, // RefObject<HTMLAudioElement>
activeItem, // Current playing item
duration, // Track duration in seconds
error, // MediaError if any
isPlaying, // Playing state
isBuffering, // Buffering state
playbackRate, // Current playback speed
isItemActive, // Check if an item is active
setActiveItem, // Set the active item
play, // Play audio
pause, // Pause audio
seek, // Seek to time
setPlaybackRate, // Change playback speed
} = useAudioPlayer<TData>()Example Usage
function PlaylistController() {
const { play, pause, isPlaying, activeItem } = useAudioPlayer()
const handlePlayNext = () => {
const nextTrack = getNextTrack(activeItem?.id)
if (nextTrack) {
play(nextTrack)
}
}
return <button onClick={handlePlayNext}>Next Track</button>
}useAudioPlayerTime Hook
Get the current playback time (updates every frame using requestAnimationFrame).
const time = useAudioPlayerTime() // Current time in secondsExample Usage
function CustomTimeDisplay() {
const time = useAudioPlayerTime()
const { duration } = useAudioPlayer()
const percentage = duration ? (time / duration) * 100 : 0
return <div>Progress: {percentage.toFixed(1)}%</div>
}Advanced Examples
Player with Speed Control
function AudioPlayerWithSpeed() {
return (
<AudioPlayerProvider>
<div className="flex items-center gap-4">
<AudioPlayerButton />
<AudioPlayerTime />
<AudioPlayerProgress className="flex-1" />
<AudioPlayerDuration />
<AudioPlayerSpeed />
</div>
</AudioPlayerProvider>
)
}Custom Controls
function CustomAudioPlayer() {
const { play, pause, isPlaying, seek, duration, setPlaybackRate } =
useAudioPlayer()
return (
<div className="space-y-4">
<button onClick={() => (isPlaying ? pause() : play())}>
{isPlaying ? "Pause" : "Play"}
</button>
<button onClick={() => seek(0)}>Restart</button>
<button onClick={() => duration && seek(duration * 0.5)}>
Jump to 50%
</button>
<button onClick={() => setPlaybackRate(1.5)}>Speed 1.5x</button>
</div>
)
}Error Handling
function AudioPlayerWithError() {
const { error, activeItem } = useAudioPlayer()
if (error) {
return (
<div className="text-red-500">
Failed to load: {activeItem?.src}
<br />
Error: {error.message}
</div>
)
}
return <AudioPlayerButton />
}Notes
- The audio player uses the HTML5 audio element under the hood
- Progress updates are synchronized using
requestAnimationFramefor smooth UI updates - The player handles buffering states and network errors automatically
- Space bar triggers play/pause when the progress slider is focused
- The component includes TypeScript support with generic data types
- Audio state is managed globally within the provider context
- Playback speed displays "Normal" for 1x speed and numerical values (e.g., "0.5x", "1.5x") for other speeds
- Playback speed is preserved when switching between tracks
On This Page
InstallationUsageBasic PlayerPlaying a Specific TrackMultiple TracksAPI ReferenceAudioPlayerProviderAudioPlayerButtonPropsAudioPlayerItem TypeAudioPlayerProgressPropsAudioPlayerTimePropsAudioPlayerDurationPropsAudioPlayerSpeedPropsExampleAudioPlayerSpeedButtonGroupPropsExampleuseAudioPlayer HookExample UsageuseAudioPlayerTime HookExample UsageAdvanced ExamplesPlayer with Speed ControlCustom ControlsError HandlingNotes