@arraypress/waveform-player 1.7.2 → 1.8.1

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/README.md CHANGED
@@ -1,173 +1,122 @@
1
- # WaveformPlayer
2
-
3
- A lightweight, customizable audio player with waveform visualization. Under 8KB gzipped.
1
+ <div align="center">
4
2
 
5
- **[Live Demo](https://waveformplayer.com)** | **[Documentation](https://waveformplayer.com/#docs)** | *
6
- *[NPM Package](https://www.npmjs.com/package/@arraypress/waveform-player)**
3
+ # WaveformPlayer
7
4
 
8
- ![Version](https://img.shields.io/npm/v/@arraypress/waveform-player)
9
- ![Size](https://img.shields.io/bundlephobia/minzip/@arraypress/waveform-player)
10
- ![License](https://img.shields.io/npm/l/@arraypress/waveform-player)
11
- ![Downloads](https://img.shields.io/npm/dm/@arraypress/waveform-player)
5
+ **Zero-config audio waveforms for the web.**
6
+ Add a `data-` attribute to a `<div>` — get a real, interactive waveform player. No build step, no dependencies, ~10KB.
12
7
 
13
- ![WaveformPlayer Demo](https://waveformplayer.com/assets/img/og-image.png)
8
+ [![npm](https://img.shields.io/npm/v/@arraypress/waveform-player?style=flat-square&labelColor=09090b&color=3f3f46)](https://www.npmjs.com/package/@arraypress/waveform-player)
9
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/@arraypress/waveform-player?style=flat-square&label=gzip&labelColor=09090b&color=3f3f46)](https://bundlephobia.com/package/@arraypress/waveform-player)
10
+ [![downloads](https://img.shields.io/npm/dm/@arraypress/waveform-player?style=flat-square&labelColor=09090b&color=3f3f46)](https://www.npmjs.com/package/@arraypress/waveform-player)
11
+ [![types](https://img.shields.io/npm/types/@arraypress/waveform-player?style=flat-square&labelColor=09090b&color=3f3f46)](./index.d.ts)
12
+ [![license](https://img.shields.io/npm/l/@arraypress/waveform-player?style=flat-square&labelColor=09090b&color=3f3f46)](./LICENSE)
14
13
 
15
- ## Why WaveformPlayer?
14
+ [Live demo](https://waveformplayer.com) · [Docs](https://waveformplayer.com/#docs) · [npm](https://www.npmjs.com/package/@arraypress/waveform-player)
16
15
 
17
- - **Zero Config** - Just add `data-waveform-player` to any div. No JavaScript required.
18
- - **Tiny** - ~8KB gzipped vs 40KB+ for alternatives
19
- - **Real Waveforms** - Actual audio analysis, not fake waves
20
- - **No Dependencies** - No jQuery, no bloat, pure vanilla JS
21
- - **Works Everywhere** - WordPress, Shopify, React, Vue, or plain HTML
22
- - **Ecosystem** - Optional [WaveformBar](https://github.com/arraypress/waveform-bar) persistent player addon available
16
+ ![WaveformPlayer](./assets/og-image.svg)
23
17
 
24
- ## What's New
18
+ </div>
25
19
 
26
- For detailed release notes, see the [Changelog](CHANGELOG.md).
20
+ ---
27
21
 
28
- ## Quick Start
22
+ ## Quick start
29
23
 
30
- Simplest possible usage:
24
+ No build tools. No initialization. Drop in two files and add a `<div>`.
31
25
 
32
26
  ```html
33
- <!-- Just this. That's it. -->
34
- <div data-waveform-player data-url="song.mp3"></div>
27
+ <link rel="stylesheet" href="https://unpkg.com/@arraypress/waveform-player/dist/waveform-player.css">
28
+ <script src="https://unpkg.com/@arraypress/waveform-player/dist/waveform-player.min.js"></script>
29
+
30
+ <div data-waveform-player data-url="track.mp3" data-title="My Song"></div>
35
31
  ```
36
32
 
37
- ## Installation
33
+ It auto-initializes every `[data-waveform-player]` on the page when the DOM is ready. That's it.
38
34
 
39
- ### NPM
35
+ ## Install
40
36
 
41
37
  ```bash
42
38
  npm install @arraypress/waveform-player
43
39
  ```
44
40
 
45
- ### CDN
46
-
47
- ```html
41
+ ```js
42
+ import WaveformPlayer from '@arraypress/waveform-player';
43
+ import '@arraypress/waveform-player/styles.css';
48
44
 
49
- <link rel="stylesheet" href="https://unpkg.com/@arraypress/waveform-player@latest/dist/waveform-player.css">
50
- <script src="https://unpkg.com/@arraypress/waveform-player@latest/dist/waveform-player.min.js"></script>
45
+ const player = new WaveformPlayer('#player', { url: 'track.mp3' });
51
46
  ```
52
47
 
53
- ### Download
48
+ Ships ESM + CommonJS + a standalone IIFE for `<script>` tags, with bundled TypeScript definitions.
54
49
 
55
- ```html
50
+ ## Why
56
51
 
57
- <link rel="stylesheet" href="waveform-player.css">
58
- <script src="waveform-player.js"></script>
59
- ```
52
+ | | WaveformPlayer | WaveSurfer.js | Amplitude.js |
53
+ | ------------------- | :------------: | :-----------: | :----------: |
54
+ | Size (gzipped) | **~10KB** | 40KB+ | 35KB+ |
55
+ | Zero-config (HTML) | ✓ | — | — |
56
+ | Dependencies | None | None | None |
57
+ | Real waveforms | ✓ | ✓ | — |
58
+ | TypeScript types | ✓ | ✓ | — |
59
+ | Keyboard + ARIA | ✓ | partial | — |
60
+ | Media Session API | ✓ | — | — |
60
61
 
61
62
  ## Features
62
63
 
63
- - 🎨 **6 Visual Styles** - Bars, mirror, line, blocks, dots, seekbar
64
- - 🎯 **Tiny Footprint** - Under 8KB gzipped
65
- - **Zero Dependencies** - Pure JavaScript
66
- - 🎭 **Fully Customizable** - Colors, sizes, styles
67
- - 🎛️ **Show/Hide Controls** - Hide button and info bar for custom UIs
68
- - 📱 **Responsive** - Works on all devices
69
- - 🎵 **BPM Detection** - Automatic tempo detection (optional)
70
- - 💾 **Waveform Caching** - Pre-generate waveforms for performance
71
- - 🌐 **Framework Agnostic** - Works with React, Vue, Angular, or vanilla JS
72
- - ⌨️ **Keyboard Controls** - Full keyboard navigation support
73
- - 📱 **Media Session API** - System media controls, lock screen integration
74
- - ⏩ **Speed Control** - Adjustable playback rate for podcasts/audiobooks
75
- - 📍 **Chapter Markers** - Add clickable markers for navigation
76
- - 🔄 **Dynamic Loading** - Load new tracks without page refresh
77
- - 🎨 **Auto Theme Detection** - Adapts to light/dark themes automatically
64
+ - **Zero-config** works from plain HTML via `data-` attributes; no JS required.
65
+ - **6 visual styles** bars, mirror, line, blocks, dots, seekbar — plus rounded caps and gradient fills.
66
+ - **Real audio analysis** — decodes peaks with the Web Audio API, or uses pre-generated data.
67
+ - **Accessible** — the waveform is a keyboard-operable ARIA slider out of the box.
68
+ - **Framework-ready** — first-party [React](https://github.com/arraypress/waveform-player-react) and [Astro](https://github.com/arraypress/waveform-player-astro) wrappers; works anywhere otherwise.
69
+ - **Batteries included** chapter markers, BPM detection, Media Session, speed control, auto theme detection.
70
+ - **Tiny** ~10KB gzipped, zero dependencies, TypeScript types bundled.
78
71
 
79
- ## Ecosystem
72
+ ## Visual styles
80
73
 
81
- ### WaveformBar (Optional Addon)
74
+ Set with `data-style` (or the longer `data-waveform-style`), or the `style` / `waveformStyle` option.
82
75
 
83
- A persistent bottom-bar audio player with queue, favorites, cart, volume popup, DJ mode with markers, and cross-page
84
- session persistence:
76
+ | Style | Look |
77
+ | --------- | ----------------------------------- |
78
+ | `mirror` | Symmetrical, SoundCloud-style (default) |
79
+ | `bars` | Classic bottom-anchored bars |
80
+ | `line` | Smooth oscilloscope line |
81
+ | `blocks` | Segmented LED meter |
82
+ | `dots` | Circular points |
83
+ | `seekbar` | Minimal progress bar, no peaks |
85
84
 
86
- ```html
87
-
88
- <div data-wb-play
89
- data-url="song.mp3"
90
- data-title="My Track"
91
- data-artist="Artist"
92
- data-bpm="128"
93
- data-key="Am">
94
- </div>
95
-
96
- <script>
97
- WaveformBar.init();
98
- </script>
99
- ```
100
-
101
- [Learn more →](https://github.com/arraypress/waveform-bar)
102
-
103
- ### WaveformPlaylist (Optional Addon)
104
-
105
- Add playlist and chapter support with zero JavaScript:
106
-
107
- ```html
108
-
109
- <div data-waveform-playlist data-continuous="true">
110
- <div data-track data-url="song1.mp3" data-title="Track 1">
111
- <div data-chapter data-time="0:00">Intro</div>
112
- <div data-chapter data-time="2:30">Verse</div>
113
- </div>
114
- <div data-track data-url="song2.mp3" data-title="Track 2"></div>
115
- </div>
116
- ```
85
+ **Modern caps & gradients** (bundle-neutral):
117
86
 
118
- [Learn more →](https://github.com/arraypress/waveform-playlist)
119
-
120
- ### WaveformTracker (Optional Addon)
121
-
122
- Track meaningful audio engagement:
123
-
124
- ```javascript
125
- WaveformTracker.init({
126
- endpoint: '/api/analytics',
127
- events: {
128
- listen: 30 // Track after 30 seconds of listening
129
- }
87
+ ```js
88
+ new WaveformPlayer('#player', {
89
+ url: 'track.mp3',
90
+ waveformStyle: 'mirror',
91
+ barRadius: 3, // rounded bar caps
92
+ waveformColor: ['#fafafa', '#71717a'], // vertical gradient (array of stops)
93
+ progressColor: ['#ffffff', '#a1a1aa'],
130
94
  });
131
95
  ```
132
96
 
133
- [Learn more →](https://github.com/arraypress/waveform-tracker)
134
-
135
- ## Comparison
136
-
137
- | Feature | WaveformPlayer | WaveSurfer.js | Amplitude.js |
138
- |-------------------|----------------|---------------|--------------|
139
- | Size (gzipped) | ~8KB | 40KB+ | 35KB+ |
140
- | Zero Config | ✅ | ❌ | ❌ |
141
- | Dependencies | None | None | None |
142
- | Waveform Styles | 6 | 3 | N/A |
143
- | Setup Time | 30 seconds | 5+ minutes | 5+ minutes |
144
- | Real Waveforms | ✅ | ✅ | ❌ |
145
- | Keyboard Controls | ✅ | ✅ | ❌ |
146
- | Media Session API | ✅ | ❌ | ❌ |
147
- | Speed Control | ✅ | ✅ | ❌ |
97
+ In zero-config markup, pass a gradient as a JSON array: `data-waveform-color='["#fafafa","#71717a"]'`.
148
98
 
149
99
  ## Usage
150
100
 
151
- ### HTML (Zero JavaScript)
101
+ ### HTML (zero JavaScript)
152
102
 
153
103
  ```html
154
-
155
104
  <div data-waveform-player
156
- data-url="audio.mp3"
105
+ data-url="track.mp3"
157
106
  data-title="My Song"
158
107
  data-subtitle="Artist Name"
159
108
  data-waveform-style="mirror"
109
+ data-bar-radius="3"
160
110
  data-show-playback-speed="true"
161
111
  data-markers='[{"time": 30, "label": "Chorus"}]'>
162
112
  </div>
163
113
  ```
164
114
 
165
- ### Waveform Only (Custom UI)
115
+ ### Waveform only (custom UI)
166
116
 
167
117
  ```html
168
- <!-- Hide built-in controls for custom UI integration -->
169
118
  <div data-waveform-player
170
- data-url="audio.mp3"
119
+ data-url="track.mp3"
171
120
  data-show-controls="false"
172
121
  data-show-info="false">
173
122
  </div>
@@ -175,315 +124,165 @@ WaveformTracker.init({
175
124
 
176
125
  ### JavaScript API
177
126
 
178
- ```javascript
127
+ ```js
179
128
  import WaveformPlayer from '@arraypress/waveform-player';
180
129
 
181
130
  const player = new WaveformPlayer('#player', {
182
- url: 'audio.mp3',
183
- waveformStyle: 'mirror',
184
- height: 80,
185
- barWidth: 2,
186
- barSpacing: 1,
187
- markers: [
188
- {time: 30, label: 'Verse', color: '#4ade80'},
189
- {time: 60, label: 'Chorus', color: '#f59e0b'}
190
- ]
131
+ url: 'track.mp3',
132
+ waveformStyle: 'mirror',
133
+ height: 80,
134
+ markers: [
135
+ { time: 30, label: 'Verse', color: '#4ade80' },
136
+ { time: 60, label: 'Chorus', color: '#f59e0b' },
137
+ ],
191
138
  });
192
139
  ```
193
140
 
194
- ## Visual Styles
195
-
196
- Choose from 6 built-in styles:
197
-
198
- - **bars** - Classic waveform bars
199
- - **mirror** - SoundCloud-style mirrored waveform
200
- - **line** - Smooth oscilloscope line
201
- - **blocks** - LED meter blocks
202
- - **dots** - Circular dots
203
- - **seekbar** - Minimal progress bar
204
-
205
141
  ## Options
206
142
 
207
- | Option | Type | Default | Description |
208
- |----------------------|---------|---------------------------|---------------------------------------------------------|
209
- | `url` | string | `''` | Audio file URL |
210
- | `waveformStyle` | string | `'bars'` | Visual style: bars, mirror, line, blocks, dots, seekbar |
211
- | `height` | number | `60` | Waveform height in pixels |
212
- | `barWidth` | number | `3` | Width of waveform bars |
213
- | `barSpacing` | number | `1` | Space between bars |
214
- | `samples` | number | `200` | Number of waveform samples |
215
- | `waveformColor` | string | `'rgba(255,255,255,0.3)'` | Waveform color |
216
- | `progressColor` | string | `'rgba(255,255,255,0.9)'` | Progress color |
217
- | `buttonColor` | string | `'rgba(255,255,255,0.9)'` | Play button color |
218
- | `showControls` | boolean | `true` | Show play/pause button |
219
- | `showInfo` | boolean | `true` | Show title, subtitle, time, and metadata |
220
- | `showTime` | boolean | `true` | Show time display |
221
- | `showBPM` | boolean | `false` | Enable BPM detection |
222
- | `showPlaybackSpeed` | boolean | `false` | Show speed control menu |
223
- | `playbackRate` | number | `1` | Initial playback speed (0.5-2) |
224
- | `autoplay` | boolean | `false` | Autoplay on load |
225
- | `title` | string | `''` | Track title |
226
- | `subtitle` | string | `''` | Track subtitle |
227
- | `artwork` | string | `''` | Album artwork URL |
228
- | `markers` | array | `[]` | Chapter markers array |
229
- | `waveform` | array | `null` | Pre-generated waveform data |
230
- | `enableMediaSession` | boolean | `true` | Enable system media controls |
231
- | `audioMode` | string | `'self'` | `'self'` (own `<audio>`) or `'external'` (delegate) |
232
- | `accessibleSeek` | boolean | `true` | Expose the waveform as a keyboard-operable ARIA slider |
233
- | `seekLabel` | string | `null` | Accessible name for the seek slider (falls back to title) |
234
-
235
- ### Accessibility
236
-
237
- By default the waveform is exposed as a keyboard-operable [ARIA slider](https://www.w3.org/WAI/ARIA/apg/patterns/slider/): the `.waveform-container` gets `role="slider"`, is focusable in the tab order, and reports `aria-valuemin`/`aria-valuemax`/`aria-valuenow` plus a readable `aria-valuetext` (e.g. `"0:30 of 2:00"`). When focused it responds to the standard slider keys:
238
-
239
- | Key | Action |
240
- |---|---|
143
+ | Option | Type | Default | Description |
144
+ | -------------------- | --------------------- | ----------- | ------------------------------------------------------- |
145
+ | `url` | `string` | `''` | Audio file URL (alias: `src` / `data-src`) |
146
+ | `waveformStyle` | `string` | `'mirror'` | `bars` · `mirror` · `line` · `blocks` · `dots` · `seekbar` |
147
+ | `height` | `number` | `60` | Waveform height (px) |
148
+ | `barWidth` | `number` | style-based | Bar width (px) |
149
+ | `barSpacing` | `number` | style-based | Gap between bars (px) |
150
+ | `barRadius` | `number` | `0` | Rounded bar-cap radius (px) — bars/mirror |
151
+ | `waveformColor` | `string \| string[]` | preset | Unplayed colour; array = vertical gradient |
152
+ | `progressColor` | `string \| string[]` | preset | Played colour; array = vertical gradient |
153
+ | `colorPreset` | `'dark' \| 'light' \| null` | `null` | Force a theme, or `null` to auto-detect |
154
+ | `samples` | `number` | `200` | Peak samples to extract |
155
+ | `waveform` | `number[] \| string` | `null` | Pre-generated peaks (array, `.json` URL, or JSON) |
156
+ | `audioMode` | `'self' \| 'external'`| `'self'` | Own the `<audio>`, or delegate (see below) |
157
+ | `markers` | `WaveformMarker[]` | `[]` | Chapter markers |
158
+ | `showControls` | `boolean` | `true` | Show the play/pause button |
159
+ | `showInfo` | `boolean` | `true` | Show title/subtitle/time |
160
+ | `showPlaybackSpeed` | `boolean` | `false` | Show the speed menu |
161
+ | `showBPM` | `boolean` | `false` | Detect + display BPM |
162
+ | `autoplay` | `boolean` | `false` | Play on load |
163
+ | `enableMediaSession` | `boolean` | `true` | System media controls (self mode) |
164
+ | `accessibleSeek` | `boolean` | `true` | Expose the waveform as a keyboard ARIA slider |
165
+ | `seekLabel` | `string` | `null` | Accessible name for the slider (falls back to title) |
166
+
167
+ Full option types ship in [`index.d.ts`](./index.d.ts).
168
+
169
+ ## Accessibility
170
+
171
+ By default the waveform is a keyboard-operable [ARIA slider](https://www.w3.org/WAI/ARIA/apg/patterns/slider/): `.waveform-container` gets `role="slider"`, joins the tab order, and reports `aria-valuemin`/`max`/`now` plus a readable `aria-valuetext` (e.g. `"0:30 of 2:00"`). When focused:
172
+
173
+ | Key | Action |
174
+ | -------------------------------------------- | ------------ |
241
175
  | <kbd>←</kbd> / <kbd>↓</kbd> · <kbd>→</kbd> / <kbd>↑</kbd> | Seek ∓5s |
242
- | <kbd>Page Down</kbd> / <kbd>Page Up</kbd> | Seek ∓10s |
243
- | <kbd>Home</kbd> / <kbd>End</kbd> | Start / end |
176
+ | <kbd>Page Down</kbd> / <kbd>Page Up</kbd> | Seek ∓10s |
177
+ | <kbd>Home</kbd> / <kbd>End</kbd> | Start / end |
244
178
 
245
- This works in both `self` and `external` audio modes (in external mode it dispatches `waveformplayer:request-seek`, exactly like click-to-seek). Set `accessibleSeek: false` to opt out and keep the prior markup, or `seekLabel` to localize the control's accessible name.
179
+ Works in both audio modes, respects `prefers-reduced-motion`, and announces load errors to screen readers. Opt out with `accessibleSeek: false`; localize with `seekLabel`.
246
180
 
247
- ### External audio mode
248
-
249
- When `audioMode: 'external'` the player skips creating its own `<audio>` element and becomes a visualization-only surface. Play / pause / seek interactions dispatch cancelable events on the container, and an external controller (e.g. [`@arraypress/waveform-bar`](https://github.com/arraypress/waveform-bar) 1.3+) handles audio + pumps state back via `setPlayingState()` / `setProgress()`.
250
-
251
- ```html
252
- <!-- The bar discovers this on init, listens for request-play events,
253
- and mirrors its own playback progress back into the canvas. -->
254
- <div data-waveform-player
255
- data-audio-mode="external"
256
- data-url="song.mp3"
257
- data-waveform-style="bars"
258
- data-wb-play
259
- data-wb-url="song.mp3"
260
- data-wb-title="..."></div>
261
- ```
181
+ ## API
262
182
 
263
183
  ```js
264
- // Or construct directly:
265
- const player = new WaveformPlayer(el, { audioMode: 'external' });
266
-
267
- // Listen for the play action and route to your own audio source:
268
- el.addEventListener('waveformplayer:request-play', (e) => {
269
- // e.detail = { url, title, subtitle, artist, artwork, id, player }
270
- yourAudio.src = e.detail.url;
271
- yourAudio.play();
272
- });
273
-
274
- // Drive the visualization from your audio's timeupdate:
275
- yourAudio.addEventListener('timeupdate', () => {
276
- player.setProgress(yourAudio.currentTime, yourAudio.duration);
277
- });
278
- yourAudio.addEventListener('play', () => player.setPlayingState(true));
279
- yourAudio.addEventListener('pause', () => player.setPlayingState(false));
280
- ```
281
-
282
- Events dispatched in external mode (all bubble; all cancelable):
283
-
284
- | Event | Detail |
285
- |------------------------------------|-----------------------------------------------------|
286
- | `waveformplayer:request-play` | `{ url, title, subtitle, artist, artwork, id, player }` |
287
- | `waveformplayer:request-pause` | Same shape as request-play |
288
- | `waveformplayer:request-seek` | Same shape + `{ percent: 0..1 }` |
289
-
290
- ## API Methods
291
-
292
- ```javascript
293
- // Control playback
294
- player.play();
184
+ player.play(); // returns the <audio>.play() promise in self mode
295
185
  player.pause();
296
186
  player.togglePlay();
297
187
 
298
- // Seek
299
- player.seekTo(30); // Seek to 30 seconds
300
- player.seekToPercent(0.5); // Seek to 50%
188
+ player.seekTo(30); // seconds
189
+ player.seekToPercent(0.5);
190
+ player.setVolume(0.8);
191
+ player.setPlaybackRate(1.5);
301
192
 
302
- // Volume
303
- player.setVolume(0.8); // 80% volume
193
+ await player.loadTrack('next.mp3', 'Title', 'Artist', { artwork: 'cover.jpg' });
194
+ player.destroy(); // removes all listeners + DOM
304
195
 
305
- // Speed
306
- player.setPlaybackRate(1.5); // 1.5x speed
307
-
308
- // External-mode state pumps (only useful when audioMode === 'external')
309
- player.setPlayingState(true); // toggle play/pause UI state
310
- player.setProgress(currentTime, duration); // sync canvas + time displays
311
-
312
- // Dynamic loading
313
- await player.loadTrack('new-song.mp3', 'New Title', 'New Artist', {
314
- markers: [{time: 30, label: 'Chorus'}],
315
- artwork: 'cover.jpg',
316
- waveform: [0.2, 0.5, 0.8, 0.3] // Optional pre-generated data
317
- });
318
-
319
- // Destroy
320
- player.destroy();
321
- ```
322
-
323
- ## Events
324
-
325
- ```javascript
326
- new WaveformPlayer('#player', {
327
- url: 'audio.mp3',
328
- onLoad: (player) => console.log('Loaded'),
329
- onPlay: (player) => console.log('Playing'),
330
- onPause: (player) => console.log('Paused'),
331
- onEnd: (player) => console.log('Ended'),
332
- onTimeUpdate: (current, total, player) => {
333
- console.log(`${current}/${total}`);
334
- }
335
- });
196
+ // Statics
197
+ WaveformPlayer.getInstance('id');
198
+ WaveformPlayer.getAllInstances();
199
+ WaveformPlayer.destroyAll();
200
+ const peaks = await WaveformPlayer.generateWaveformData('track.mp3');
201
+ const jsonUrl = WaveformPlayer.getPeaksUrl('track.mp3'); // -> 'track.json'
336
202
  ```
337
203
 
338
- ## Keyboard Controls
339
-
340
- When a player is focused (click on it):
341
-
342
- - `Space` - Play/pause
343
- - `←/→` - Seek backward/forward 5 seconds
344
- - `↑/↓` - Volume up/down
345
- - `M` - Mute/unmute
346
- - `0-9` - Jump to 0%-90% of track
347
-
348
- ## Advanced Usage
349
-
350
- ### Pre-generated Waveforms
351
-
352
- For better performance, generate waveform data server-side:
204
+ ### Events
353
205
 
354
- ```javascript
355
- // Generate waveform data once
356
- const waveformData = await WaveformPlayer.generateWaveformData('audio.mp3');
206
+ Lifecycle callbacks via options, or DOM events on the container (all bubble, `e.detail` is typed):
357
207
 
358
- // Use pre-generated data for instant display
208
+ ```js
359
209
  new WaveformPlayer('#player', {
360
- url: 'audio.mp3',
361
- waveform: waveformData // Bypass client-side processing
210
+ url: 'track.mp3',
211
+ onPlay: (player) => {},
212
+ onPause: (player) => {},
213
+ onEnd: (player) => {},
214
+ onTimeUpdate: (currentTime, duration, player) => {},
362
215
  });
363
- ```
364
-
365
- **Build-time pipeline:** when you generate peaks ahead of time with [`@arraypress/waveform-gen`](https://github.com/arraypress/waveform-gen) and store the JSON alongside each MP3 (`/audio/track.mp3` ↔ `/audio/track.json`), `WaveformPlayer.getPeaksUrl()` derives the JSON URL by swapping the extension:
366
216
 
367
- ```javascript
368
- new WaveformPlayer('#player', {
369
- url: track.audioUrl,
370
- waveform: WaveformPlayer.getPeaksUrl(track.audioUrl),
217
+ el.addEventListener('waveformplayer:timeupdate', (e) => {
218
+ const { currentTime, duration, progress } = e.detail;
371
219
  });
372
220
  ```
373
221
 
374
- Recognised extensions: `mp3`, `wav`, `ogg`, `flac`, `m4a`, `aac`. Query strings and URL fragments are preserved. Returns `undefined` for unrecognised inputs so the player falls back to live decoding.
375
-
376
- ### Multiple Players
377
-
378
- ```javascript
379
- // Get all instances
380
- const players = WaveformPlayer.getAllInstances();
222
+ ## External audio mode
381
223
 
382
- // Find specific player
383
- const player = WaveformPlayer.getInstance('my-player');
224
+ Set `audioMode: 'external'` and the player becomes a visualization-only surface: play/pause/seek dispatch cancelable events, and you drive the canvas back with `setPlayingState()` / `setProgress()`. This is how [`@arraypress/waveform-bar`](https://github.com/arraypress/waveform-bar) turns many inline players into one persistent bar.
384
225
 
385
- // Destroy all players
386
- WaveformPlayer.destroyAll();
387
- ```
388
-
389
- ### Custom Styling
390
-
391
- ```css
392
- .waveform-player {
393
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
394
- border-radius: 12px;
395
- }
226
+ ```js
227
+ const player = new WaveformPlayer(el, { audioMode: 'external' });
396
228
 
397
- .waveform-btn {
398
- border-color: #fff;
399
- }
229
+ el.addEventListener('waveformplayer:request-play', (e) => {
230
+ audio.src = e.detail.url; // e.detail = { url, title, subtitle, artist, artwork, id, player }
231
+ audio.play();
232
+ });
233
+ audio.addEventListener('timeupdate', () => player.setProgress(audio.currentTime, audio.duration));
234
+ audio.addEventListener('play', () => player.setPlayingState(true));
235
+ audio.addEventListener('pause', () => player.setPlayingState(false));
400
236
  ```
401
237
 
402
- ## Framework Integration
403
-
404
- ### React
405
-
406
- ```jsx
407
- import {useEffect, useRef} from 'react';
408
- import WaveformPlayer from '@arraypress/waveform-player';
409
-
410
- function AudioPlayer({url}) {
411
- const playerRef = useRef();
238
+ | Event | Detail |
239
+ | ------------------------------ | --------------------------------------------------- |
240
+ | `waveformplayer:request-play` | `{ url, title, subtitle, artist, artwork, id, player }` |
241
+ | `waveformplayer:request-pause` | Same shape |
242
+ | `waveformplayer:request-seek` | Same shape + `{ percent: 0..1 }` |
412
243
 
413
- useEffect(() => {
414
- const player = new WaveformPlayer(playerRef.current, {url});
415
- return () => player.destroy();
416
- }, [url]);
244
+ ## Frameworks
417
245
 
418
- return <div ref={playerRef}/>;
419
- }
420
- ```
246
+ | Environment | Package |
247
+ | ----------- | ------- |
248
+ | React | [`@arraypress/waveform-player-react`](https://github.com/arraypress/waveform-player-react) |
249
+ | Astro | [`@arraypress/waveform-player-astro`](https://github.com/arraypress/waveform-player-astro) |
250
+ | Vanilla / anything else | this package — `new WaveformPlayer(el, opts)` and `player.destroy()` on teardown |
421
251
 
422
- ### Vue
252
+ ## Ecosystem
423
253
 
424
- ```vue
254
+ - **[WaveformBar](https://github.com/arraypress/waveform-bar)** — persistent bottom-bar player with queue, favorites, and cross-page session.
255
+ - **[WaveformPlaylist](https://github.com/arraypress/waveform-playlist)** — zero-JS playlists and chapters.
256
+ - **[WaveformGen](https://github.com/arraypress/waveform-gen)** — pre-generate peak JSON at build time.
257
+ - **[WaveformTracker](https://github.com/arraypress/waveform-tracker)** — audio engagement analytics.
425
258
 
426
- <template>
427
- <div ref="player"></div>
428
- </template>
259
+ ## Pre-generated waveforms
429
260
 
430
- <script>
431
- import WaveformPlayer from '@arraypress/waveform-player';
261
+ Skip client-side decoding by storing peaks next to each file (`track.mp3` ↔ `track.json`):
432
262
 
433
- export default {
434
- mounted() {
435
- this.player = new WaveformPlayer(this.$refs.player, {
436
- url: this.audioUrl
437
- });
438
- },
439
- beforeDestroy() {
440
- this.player?.destroy();
441
- }
442
- }
443
- </script>
263
+ ```js
264
+ new WaveformPlayer('#player', {
265
+ url: track.audioUrl,
266
+ waveform: WaveformPlayer.getPeaksUrl(track.audioUrl),
267
+ });
444
268
  ```
445
269
 
446
- ## Browser Support
447
-
448
- - Chrome/Edge 90+
449
- - Firefox 88+
450
- - Safari 14+
451
- - Mobile browsers (iOS Safari, Chrome Android)
270
+ `getPeaksUrl()` swaps a known audio extension (`mp3`, `wav`, `ogg`, `flac`, `m4a`, `aac`) for `.json`, preserving query/hash, and returns `undefined` for anything else (so the player falls back to live decoding).
452
271
 
453
- ## Examples
272
+ ## Browser support
454
273
 
455
- See the [live demo](https://waveformplayer.com) for:
456
-
457
- - All visual styles
458
- - Custom styling examples
459
- - Event handling
460
- - Player builder
461
- - BPM detection
462
- - Pre-generated waveforms
463
- - Keyboard navigation
464
- - Speed controls
465
- - Chapter markers
274
+ Chrome/Edge 90+, Firefox 88+, Safari 14+, and modern mobile browsers. Rounded bar caps use `roundRect` where available (Safari 16+) and fall back to square bars elsewhere.
466
275
 
467
276
  ## Development
468
277
 
469
278
  ```bash
470
- # Install dependencies
471
279
  npm install
472
-
473
- # Development mode
474
- npm run dev
475
-
476
- # Build
477
- npm run build
478
-
479
- # Check size
480
- npm run size
280
+ npm run dev # watch build
281
+ npm test # vitest
282
+ npm run build # all bundles
283
+ npm run size # gzipped size
481
284
  ```
482
285
 
483
286
  ## License
484
287
 
485
- MIT © [ArrayPress](https://github.com/arraypress)
486
-
487
- ## Credits
488
-
489
- Created by [David Sherlock](https://github.com/arraypress)
288
+ MIT © [ArrayPress](https://github.com/arraypress) · created by [David Sherlock](https://github.com/arraypress)