wavesurf 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TheDecipherist
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,348 @@
1
+ # wavesurf
2
+
3
+ A React audio player with WaveSurfer.js waveform visualization, global state management, and mini-player support.
4
+
5
+ ## Features
6
+
7
+ - **WaveSurfer.js Integration** - Beautiful waveform visualization for audio playback
8
+ - **Global Audio State** - React Context for managing playback across your app
9
+ - **Mini Player** - A persistent bottom/top bar for controlling playback
10
+ - **Pre-computed Peaks Support** - Fast loading with pre-generated waveform data
11
+ - **Volume Fade-in Effect** - Smooth 3-second volume fade when starting playback
12
+ - **Volume Persistence** - Remember user's volume preference via localStorage
13
+ - **Lazy Loading** - Load waveforms only when visible using IntersectionObserver
14
+ - **Mobile Responsive** - Adapts to different screen sizes
15
+ - **CSS Variables** - Easy theming with CSS custom properties
16
+ - **TypeScript** - Full TypeScript support with exported types
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install wavesurf wavesurfer.js
22
+ # or
23
+ yarn add wavesurf wavesurfer.js
24
+ # or
25
+ pnpm add wavesurf wavesurfer.js
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ### 1. Wrap your app with the provider
31
+
32
+ ```tsx
33
+ import { AudioPlayerProvider } from 'wavesurf';
34
+
35
+ function App() {
36
+ return (
37
+ <AudioPlayerProvider>
38
+ <YourApp />
39
+ </AudioPlayerProvider>
40
+ );
41
+ }
42
+ ```
43
+
44
+ ### 2. Add the MiniPlayer component
45
+
46
+ ```tsx
47
+ import { MiniPlayer } from 'wavesurf';
48
+
49
+ function Layout({ children }) {
50
+ return (
51
+ <div>
52
+ {children}
53
+ <MiniPlayer />
54
+ </div>
55
+ );
56
+ }
57
+ ```
58
+
59
+ ### 3. Use the WaveformPlayer for songs
60
+
61
+ ```tsx
62
+ import { WaveformPlayer } from 'wavesurf';
63
+ import 'wavesurf/styles.css';
64
+
65
+ function SongList({ songs }) {
66
+ return (
67
+ <div>
68
+ {songs.map((song) => (
69
+ <WaveformPlayer
70
+ key={song.id}
71
+ song={{
72
+ id: song.id,
73
+ title: song.title,
74
+ artist: song.artist,
75
+ audioUrl: song.url,
76
+ duration: song.duration,
77
+ peaks: song.peaks, // Optional: pre-computed waveform data
78
+ }}
79
+ />
80
+ ))}
81
+ </div>
82
+ );
83
+ }
84
+ ```
85
+
86
+ ## API Reference
87
+
88
+ ### AudioPlayerProvider
89
+
90
+ Wrap your application with this provider to enable global audio state management.
91
+
92
+ ```tsx
93
+ <AudioPlayerProvider config={config}>
94
+ {children}
95
+ </AudioPlayerProvider>
96
+ ```
97
+
98
+ #### Config Options
99
+
100
+ | Property | Type | Default | Description |
101
+ |----------|------|---------|-------------|
102
+ | `fadeInEnabled` | `boolean` | `true` | Enable volume fade-in effect on play |
103
+ | `fadeInDuration` | `number` | `3000` | Duration of fade-in in milliseconds |
104
+ | `persistVolume` | `boolean` | `true` | Persist volume to localStorage |
105
+ | `storageKey` | `string` | `'audioPlayerVolume'` | localStorage key for volume |
106
+ | `defaultVolume` | `number` | `1` | Default volume level (0-1) |
107
+ | `onPlay` | `(song: Song) => void` | - | Callback when playback starts |
108
+ | `onPause` | `() => void` | - | Callback when playback pauses |
109
+ | `onEnd` | `() => void` | - | Callback when song ends |
110
+ | `onTimeUpdate` | `(time: number) => void` | - | Callback on time update |
111
+
112
+ ### useAudioPlayer Hook
113
+
114
+ Access the audio player state and controls from any component.
115
+
116
+ ```tsx
117
+ import { useAudioPlayer } from 'wavesurf';
118
+
119
+ function CustomControls() {
120
+ const {
121
+ // State
122
+ currentSong,
123
+ isPlaying,
124
+ currentTime,
125
+ duration,
126
+ volume,
127
+ displayVolume,
128
+ isFadingIn,
129
+ // Actions
130
+ play,
131
+ pause,
132
+ togglePlay,
133
+ seek,
134
+ setVolume,
135
+ stop,
136
+ } = useAudioPlayer();
137
+
138
+ return (
139
+ <button onClick={togglePlay}>
140
+ {isPlaying ? 'Pause' : 'Play'}
141
+ </button>
142
+ );
143
+ }
144
+ ```
145
+
146
+ ### WaveformPlayer
147
+
148
+ Displays a waveform visualization with play controls for a single song.
149
+
150
+ ```tsx
151
+ <WaveformPlayer
152
+ song={song}
153
+ waveformConfig={{
154
+ waveColor: '#666666',
155
+ progressColor: '#D4AF37',
156
+ height: 60,
157
+ }}
158
+ lazyLoad={true}
159
+ showTime={true}
160
+ className="my-player"
161
+ renderHeader={(song, isPlaying) => (
162
+ <div>{song.title} {isPlaying && '▶'}</div>
163
+ )}
164
+ renderControls={(song, isPlaying) => (
165
+ <button>Share</button>
166
+ )}
167
+ />
168
+ ```
169
+
170
+ #### Props
171
+
172
+ | Prop | Type | Default | Description |
173
+ |------|------|---------|-------------|
174
+ | `song` | `Song` | required | The song object to play |
175
+ | `waveformConfig` | `WaveformConfig` | - | Waveform styling options |
176
+ | `lazyLoad` | `boolean` | `true` | Enable lazy loading via IntersectionObserver |
177
+ | `showTime` | `boolean` | `true` | Show time display below waveform |
178
+ | `className` | `string` | `''` | Additional CSS class |
179
+ | `renderHeader` | `(song, isPlaying) => ReactNode` | - | Custom header renderer |
180
+ | `renderControls` | `(song, isPlaying) => ReactNode` | - | Custom controls renderer |
181
+
182
+ ### MiniPlayer
183
+
184
+ A fixed position player bar for persistent playback control.
185
+
186
+ ```tsx
187
+ <MiniPlayer
188
+ position="bottom"
189
+ showVolume={true}
190
+ showClose={true}
191
+ onClose={() => console.log('Player closed')}
192
+ className="my-mini-player"
193
+ waveformConfig={{
194
+ progressColor: '#FF0000',
195
+ }}
196
+ />
197
+ ```
198
+
199
+ #### Props
200
+
201
+ | Prop | Type | Default | Description |
202
+ |------|------|---------|-------------|
203
+ | `position` | `'top' \| 'bottom'` | `'bottom'` | Position on screen |
204
+ | `showVolume` | `boolean` | `true` | Show volume control (auto-hidden on mobile) |
205
+ | `showClose` | `boolean` | `true` | Show close button |
206
+ | `onClose` | `() => void` | - | Callback when close is clicked |
207
+ | `className` | `string` | `''` | Additional CSS class |
208
+ | `waveformConfig` | `WaveformConfig` | - | Waveform styling options |
209
+
210
+ ### Song Interface
211
+
212
+ ```typescript
213
+ interface Song {
214
+ id: string; // Unique identifier
215
+ title: string; // Display title
216
+ artist?: string; // Artist name (optional)
217
+ album?: string; // Album name (optional)
218
+ audioUrl: string; // URL to the audio file
219
+ duration?: number; // Duration in seconds (optional)
220
+ peaks?: number[]; // Pre-computed waveform peaks (optional)
221
+ }
222
+ ```
223
+
224
+ ### WaveformConfig Interface
225
+
226
+ ```typescript
227
+ interface WaveformConfig {
228
+ waveColor?: string; // Color of the waveform (default: '#666666')
229
+ progressColor?: string; // Color of played portion (default: '#D4AF37')
230
+ cursorColor?: string; // Color of playhead (default: '#D4AF37')
231
+ barWidth?: number; // Width of bars in px (default: 2)
232
+ barGap?: number; // Gap between bars in px (default: 1)
233
+ barRadius?: number; // Border radius of bars (default: 2)
234
+ height?: number; // Height in pixels (default: 60)
235
+ normalize?: boolean; // Normalize waveform (default: true)
236
+ }
237
+ ```
238
+
239
+ ## Styling & Theming
240
+
241
+ ### Using the default styles
242
+
243
+ Import the CSS file in your app:
244
+
245
+ ```tsx
246
+ import 'wavesurf/styles.css';
247
+ ```
248
+
249
+ ### Customizing with CSS Variables
250
+
251
+ Override the CSS variables to customize the appearance:
252
+
253
+ ```css
254
+ :root {
255
+ /* Colors */
256
+ --wsp-wave-color: #888888;
257
+ --wsp-progress-color: #FF5500;
258
+ --wsp-cursor-color: #FF5500;
259
+ --wsp-background: transparent;
260
+
261
+ /* Button Colors */
262
+ --wsp-button-bg: #FF5500;
263
+ --wsp-button-bg-hover: #FF7733;
264
+ --wsp-button-text: #ffffff;
265
+
266
+ /* Text Colors */
267
+ --wsp-text: #ffffff;
268
+ --wsp-text-muted: #999999;
269
+
270
+ /* Sizing */
271
+ --wsp-height: 60px;
272
+ --wsp-mini-height: 40px;
273
+ --wsp-button-size: 56px;
274
+
275
+ /* Mini Player */
276
+ --wsp-mini-bg: #1a1a1a;
277
+ --wsp-mini-border-color: #FF5500;
278
+ }
279
+ ```
280
+
281
+ ### Using your own styles
282
+
283
+ You can also write completely custom CSS targeting the class names:
284
+
285
+ - `.wsp-player` - Main player container
286
+ - `.wsp-play-button` - Play/pause button
287
+ - `.wsp-waveform` - Waveform container
288
+ - `.wsp-time-display` - Time display
289
+ - `.wsp-mini-player` - Mini player container
290
+ - `.wsp-mini-play-button` - Mini player play button
291
+ - `.wsp-mini-waveform` - Mini player waveform
292
+
293
+ ## Pre-computed Peaks
294
+
295
+ For optimal performance, especially with large audio files, you can pre-compute waveform peaks on your server. This eliminates the need to decode audio on the client.
296
+
297
+ ### Generating peaks with audiowaveform
298
+
299
+ ```bash
300
+ # Install audiowaveform (on Ubuntu/Debian)
301
+ sudo apt install audiowaveform
302
+
303
+ # Generate peaks JSON
304
+ audiowaveform -i audio.mp3 -o peaks.json --pixels-per-second 10
305
+ ```
306
+
307
+ ### Using peaks in your app
308
+
309
+ ```tsx
310
+ const song = {
311
+ id: '1',
312
+ title: 'My Song',
313
+ audioUrl: '/audio/song.mp3',
314
+ duration: 180, // 3 minutes
315
+ peaks: peaksData, // Array of numbers from audiowaveform
316
+ };
317
+
318
+ <WaveformPlayer song={song} />
319
+ ```
320
+
321
+ ## TypeScript
322
+
323
+ All types are exported from the package:
324
+
325
+ ```typescript
326
+ import type {
327
+ Song,
328
+ AudioPlayerState,
329
+ AudioPlayerActions,
330
+ AudioPlayerConfig,
331
+ WaveformConfig,
332
+ WaveformPlayerProps,
333
+ MiniPlayerProps,
334
+ } from 'wavesurf';
335
+ ```
336
+
337
+ ## Browser Support
338
+
339
+ This package requires browsers that support:
340
+ - Web Audio API
341
+ - CSS Custom Properties
342
+ - IntersectionObserver (for lazy loading)
343
+
344
+ All modern browsers (Chrome, Firefox, Safari, Edge) are supported.
345
+
346
+ ## License
347
+
348
+ MIT
@@ -0,0 +1,212 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ /**
5
+ * Represents a song/track that can be played by the audio player.
6
+ */
7
+ interface Song {
8
+ /** Unique identifier for the song */
9
+ id: string;
10
+ /** Display title of the song */
11
+ title: string;
12
+ /** Artist name (optional) */
13
+ artist?: string;
14
+ /** Album name (optional) */
15
+ album?: string;
16
+ /** URL to the audio file */
17
+ audioUrl: string;
18
+ /** Duration in seconds (optional, will be detected if not provided) */
19
+ duration?: number;
20
+ /** Pre-computed waveform peaks for fast visualization (optional) */
21
+ peaks?: number[];
22
+ }
23
+ /**
24
+ * Internal state of the audio player.
25
+ */
26
+ interface AudioPlayerState {
27
+ /** Currently loaded song (null if none) */
28
+ currentSong: Song | null;
29
+ /** Whether audio is currently playing */
30
+ isPlaying: boolean;
31
+ /** Current playback position in seconds */
32
+ currentTime: number;
33
+ /** Total duration in seconds */
34
+ duration: number;
35
+ /** User's target/saved volume (0-1) */
36
+ volume: number;
37
+ /** Actual current volume, follows fade-in animation (0-1) */
38
+ displayVolume: number;
39
+ /** Whether volume is currently fading in */
40
+ isFadingIn: boolean;
41
+ }
42
+ /**
43
+ * Actions available to control the audio player.
44
+ */
45
+ interface AudioPlayerActions {
46
+ /** Play a song (loads and starts playback with fade-in) */
47
+ play: (song: Song) => void;
48
+ /** Pause playback */
49
+ pause: () => void;
50
+ /** Toggle between play and pause */
51
+ togglePlay: () => void;
52
+ /** Seek to a specific time in seconds */
53
+ seek: (time: number) => void;
54
+ /** Set volume (0-1, persisted to localStorage if enabled) */
55
+ setVolume: (volume: number) => void;
56
+ /** Stop playback and clear current song */
57
+ stop: () => void;
58
+ }
59
+ /**
60
+ * Combined context value including state and actions.
61
+ */
62
+ interface AudioPlayerContextValue extends AudioPlayerState, AudioPlayerActions {
63
+ }
64
+ /**
65
+ * Configuration options for the AudioPlayerProvider.
66
+ */
67
+ interface AudioPlayerConfig {
68
+ /** Enable volume fade-in effect on play (default: true) */
69
+ fadeInEnabled?: boolean;
70
+ /** Duration of fade-in effect in milliseconds (default: 3000) */
71
+ fadeInDuration?: number;
72
+ /** Persist volume to localStorage (default: true) */
73
+ persistVolume?: boolean;
74
+ /** localStorage key for volume persistence (default: 'audioPlayerVolume') */
75
+ storageKey?: string;
76
+ /** Default volume level 0-1 (default: 1) */
77
+ defaultVolume?: number;
78
+ /** Callback when a song starts playing */
79
+ onPlay?: (song: Song) => void;
80
+ /** Callback when playback is paused */
81
+ onPause?: () => void;
82
+ /** Callback when a song ends */
83
+ onEnd?: () => void;
84
+ /** Callback when current time changes (called frequently) */
85
+ onTimeUpdate?: (time: number) => void;
86
+ }
87
+ /**
88
+ * Configuration options for waveform visualization.
89
+ */
90
+ interface WaveformConfig {
91
+ /** Color of the waveform (default: '#666666') */
92
+ waveColor?: string;
93
+ /** Color of the played/progress portion (default: '#D4AF37') */
94
+ progressColor?: string;
95
+ /** Color of the cursor/playhead (default: '#D4AF37') */
96
+ cursorColor?: string;
97
+ /** Width of each bar in pixels (default: 2) */
98
+ barWidth?: number;
99
+ /** Gap between bars in pixels (default: 1) */
100
+ barGap?: number;
101
+ /** Border radius of bars in pixels (default: 2) */
102
+ barRadius?: number;
103
+ /** Height of the waveform in pixels (default: 60) */
104
+ height?: number;
105
+ /** Normalize waveform to fill height (default: true) */
106
+ normalize?: boolean;
107
+ }
108
+ /**
109
+ * Props for the WaveformPlayer component.
110
+ */
111
+ interface WaveformPlayerProps {
112
+ /** The song to display/play */
113
+ song: Song;
114
+ /** Waveform styling configuration */
115
+ waveformConfig?: WaveformConfig;
116
+ /** Enable lazy loading via IntersectionObserver (default: true) */
117
+ lazyLoad?: boolean;
118
+ /** Show time display below waveform (default: true) */
119
+ showTime?: boolean;
120
+ /** Additional CSS class name */
121
+ className?: string;
122
+ /** Custom render function for the header area */
123
+ renderHeader?: (song: Song, isPlaying: boolean) => React.ReactNode;
124
+ /** Custom render function for additional controls */
125
+ renderControls?: (song: Song, isPlaying: boolean) => React.ReactNode;
126
+ }
127
+ /**
128
+ * Position of the mini player.
129
+ */
130
+ type MiniPlayerPosition = 'top' | 'bottom';
131
+ /**
132
+ * Props for the MiniPlayer component.
133
+ */
134
+ interface MiniPlayerProps {
135
+ /** Position on screen (default: 'bottom') */
136
+ position?: MiniPlayerPosition;
137
+ /** Show volume control (default: true on desktop, false on mobile) */
138
+ showVolume?: boolean;
139
+ /** Show close button (default: true) */
140
+ showClose?: boolean;
141
+ /** Callback when close button is clicked */
142
+ onClose?: () => void;
143
+ /** Additional CSS class name */
144
+ className?: string;
145
+ /** Waveform styling configuration for the mini waveform */
146
+ waveformConfig?: WaveformConfig;
147
+ }
148
+ /**
149
+ * Return type for the useLazyLoad hook.
150
+ */
151
+ interface UseLazyLoadResult {
152
+ /** Ref to attach to the element to observe */
153
+ ref: React.RefObject<HTMLDivElement>;
154
+ /** Whether the element is visible/intersecting */
155
+ isVisible: boolean;
156
+ }
157
+ /**
158
+ * Options for the useLazyLoad hook.
159
+ */
160
+ interface UseLazyLoadOptions {
161
+ /** Root margin for IntersectionObserver (default: '100px') */
162
+ rootMargin?: string;
163
+ /** Visibility threshold 0-1 (default: 0) */
164
+ threshold?: number;
165
+ /** Start as visible (skip lazy loading) */
166
+ forceVisible?: boolean;
167
+ }
168
+
169
+ declare const MINI_PLAYER_PLAY_EVENT = "wavesurfer-player-mini-play";
170
+ interface AudioPlayerProviderProps {
171
+ children: ReactNode;
172
+ config?: AudioPlayerConfig;
173
+ }
174
+ declare function AudioPlayerProvider({ children, config: userConfig, }: AudioPlayerProviderProps): react_jsx_runtime.JSX.Element;
175
+ declare function useAudioPlayer(): AudioPlayerContextValue;
176
+
177
+ declare function WaveformPlayer({ song, waveformConfig: userWaveformConfig, lazyLoad, showTime, className, renderHeader, renderControls, }: WaveformPlayerProps): react_jsx_runtime.JSX.Element;
178
+
179
+ declare function MiniPlayer({ position, showVolume, showClose, onClose, className, waveformConfig: userWaveformConfig, }: MiniPlayerProps): react_jsx_runtime.JSX.Element | null;
180
+
181
+ /**
182
+ * Hook for lazy loading elements using IntersectionObserver.
183
+ * Returns a ref to attach to the target element and a boolean indicating visibility.
184
+ *
185
+ * @param options - Configuration options for the IntersectionObserver
186
+ * @returns Object containing ref and isVisible state
187
+ *
188
+ * @example
189
+ * const { ref, isVisible } = useLazyLoad();
190
+ *
191
+ * return (
192
+ * <div ref={ref}>
193
+ * {isVisible && <ExpensiveComponent />}
194
+ * </div>
195
+ * );
196
+ */
197
+ declare function useLazyLoad(options?: UseLazyLoadOptions): UseLazyLoadResult;
198
+
199
+ /**
200
+ * Formats seconds into a human-readable time string (M:SS or MM:SS).
201
+ *
202
+ * @param seconds - The number of seconds to format
203
+ * @returns Formatted time string (e.g., "3:45" or "12:05")
204
+ *
205
+ * @example
206
+ * formatTime(125) // "2:05"
207
+ * formatTime(0) // "0:00"
208
+ * formatTime(3600) // "60:00"
209
+ */
210
+ declare function formatTime(seconds: number): string;
211
+
212
+ export { type AudioPlayerActions, type AudioPlayerConfig, type AudioPlayerContextValue, AudioPlayerProvider, type AudioPlayerState, MINI_PLAYER_PLAY_EVENT, MiniPlayer, type MiniPlayerPosition, type MiniPlayerProps, type Song, type UseLazyLoadOptions, type UseLazyLoadResult, type WaveformConfig, WaveformPlayer, type WaveformPlayerProps, formatTime, useAudioPlayer, useLazyLoad };