Drop-in React audio player components with playlist support, waveform visualization, and audio-reactive ambient glow effects. Built with Tailwind CSS.
This is a copy-paste component library (like shadcn/ui) — you copy the component files into your project and own them directly. No npm package, no build step.
| Component | Description |
|---|---|
| PlaylistPlayer | Full-featured audio player with playlist tabs, track list, progress bar, and volume control |
| WaveformPlayer | Single-track player with interactive waveform visualization powered by WaveSurfer.js |
| AmbientGlow | Canvas-based audio-reactive background glow effect (used internally by PlaylistPlayer) |
| useAudioVisualization | React hook that provides real-time frequency data from the Web Audio API |
# Required for all components
npm install react react-dom
# Required only if using WaveformPlayer
npm install wavesurfer.js@^7.4.0Copy the entire components/ directory into your project:
your-project/
├── components/
│ ├── types.ts
│ ├── PlaylistPlayer.tsx
│ ├── WaveformPlayer.tsx
│ ├── useAudioVisualization.ts
│ └── AmbientGlow.tsx
Update import paths if you place them somewhere other than the project root.
Add these colors to your tailwind.config.ts (or see examples/tailwind-colors.ts):
// tailwind.config.ts
export default {
theme: {
extend: {
colors: {
'code': '#22C55E', // Accent color (buttons, progress, active states)
'bg-card': '#141414', // Card background
'text-primary': '#ffffff', // Primary text
'text-secondary': '#c0c0cc', // Secondary/muted text
},
},
},
};import PlaylistPlayer from './components/PlaylistPlayer';
import type { Playlist } from './components/types';
const playlists: Playlist[] = [
{
name: 'Original Soundtrack',
tracks: [
{ url: '/audio/track-1.mp3', title: 'Opening Theme', artist: 'Composer', duration: '3:24' },
{ url: '/audio/track-2.mp3', title: 'Battle Music', artist: 'Composer', duration: '4:10' },
],
},
{
name: 'Bonus Tracks',
tracks: [
{ url: '/audio/bonus-1.mp3', title: 'Bonus Track', duration: '2:58' },
],
},
];
<PlaylistPlayer playlists={playlists} />When two or more playlists are provided, tabs appear automatically for switching between them.
import PlaylistPlayer from './components/PlaylistPlayer';
import type { AudioTrack } from './components/types';
const tracks: AudioTrack[] = [
{ url: '/audio/song-1.mp3', title: 'Song One', duration: '3:12' },
{ url: '/audio/song-2.mp3', title: 'Song Two', duration: '4:05' },
];
<PlaylistPlayer tracks={tracks} />import WaveformPlayer from './components/WaveformPlayer';
<WaveformPlayer src="/audio/demo.mp3" title="Demo Track" />Both players use browser-only APIs (Web Audio, WaveSurfer.js). In Next.js App Router, use dynamic() with ssr: false:
import dynamic from 'next/dynamic';
const PlaylistPlayer = dynamic(() => import('./components/PlaylistPlayer'), {
ssr: false,
});
const WaveformPlayer = dynamic(() => import('./components/WaveformPlayer'), {
ssr: false,
});| Prop | Type | Description |
|---|---|---|
tracks |
AudioTrack[] |
List of tracks for a single playlist (no tabs) |
playlists |
Playlist[] |
Multiple named playlists (tabs shown when >= 2) |
onPlay |
() => void |
Callback fired when playback starts |
onPause |
() => void |
Callback fired when playback pauses |
Provide either tracks or playlists, not both. If playlists is provided, it takes precedence.
| Prop | Type | Description |
|---|---|---|
src |
string |
(required) URL of the audio file |
title |
string |
Optional title displayed above the waveform |
onPlay |
() => void |
Callback fired when playback starts |
onPause |
() => void |
Callback fired when playback pauses |
| Prop | Type | Description |
|---|---|---|
audioData |
AudioVisualizationData | null |
Frequency data from useAudioVisualization |
isPlaying |
boolean |
Whether audio is currently playing |
function useAudioVisualization(
audioRef: RefObject<HTMLAudioElement>,
isPlaying: boolean
): AudioVisualizationData | null;Returns null when audio is not playing or the Web Audio API hasn't been initialized yet.
interface AudioTrack {
url: string; // Audio file URL
title: string; // Track display name
duration?: string; // Display duration (e.g. "3:24") — cosmetic only
artist?: string; // Artist name
}
interface Playlist {
name: string; // Tab label
tracks: AudioTrack[]; // Tracks in this playlist
}
interface AudioVisualizationData {
frequencyData: Uint8Array; // Raw frequency bin values (0-255)
averageFrequency: number; // Mean across all bins
bassLevel: number; // Low frequency average (0-30%)
midLevel: number; // Mid frequency average (30-75%)
trebleLevel: number; // High frequency average (75-100%)
}Since you own the files, you can modify anything directly.
Change the accent color: Replace #22C55E (green) in your Tailwind config's code color and in WaveformPlayer.tsx (the progressColor and cursorColor passed to WaveSurfer).
Change the card background: Update the bg-card value in your Tailwind config.
Change the visualization hue range: In AmbientGlow.tsx, modify the targetHue calculation on line 70 — the default range is 130–250 (green to blue). Shift this to match your accent color.
Change waveform appearance: In WaveformPlayer.tsx, adjust the WaveSurfer config options (barWidth, barGap, barRadius, height, waveColor, etc.).
'use client'directives are included on all components. These are required for Next.js App Router and are harmlessly ignored in Vite, CRA, and other non-RSC setups.- Volume persistence: PlaylistPlayer saves/restores volume via
localStorage(playlistPlayerVolumekey). - Tailwind v3.1+ is required for the
border-white/5opacity syntax. - Web Audio autoplay policy: The audio visualization initializes on the first user-triggered play event to comply with browser autoplay policies.
- Circular playback: PlaylistPlayer wraps around to the first track after the last track ends.
MIT