@arraypress/waveform-player 1.7.1 → 1.8.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/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,301 +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
-
233
- ### External audio mode
234
-
235
- 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()`.
236
-
237
- ```html
238
- <!-- The bar discovers this on init, listens for request-play events,
239
- and mirrors its own playback progress back into the canvas. -->
240
- <div data-waveform-player
241
- data-audio-mode="external"
242
- data-url="song.mp3"
243
- data-waveform-style="bars"
244
- data-wb-play
245
- data-wb-url="song.mp3"
246
- data-wb-title="..."></div>
247
- ```
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
+ | -------------------------------------------- | ------------ |
175
+ | <kbd>←</kbd> / <kbd>↓</kbd> · <kbd>→</kbd> / <kbd>↑</kbd> | Seek ∓5s |
176
+ | <kbd>Page Down</kbd> / <kbd>Page Up</kbd> | Seek ∓10s |
177
+ | <kbd>Home</kbd> / <kbd>End</kbd> | Start / end |
178
+
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`.
180
+
181
+ ## API
248
182
 
249
183
  ```js
250
- // Or construct directly:
251
- const player = new WaveformPlayer(el, { audioMode: 'external' });
252
-
253
- // Listen for the play action and route to your own audio source:
254
- el.addEventListener('waveformplayer:request-play', (e) => {
255
- // e.detail = { url, title, subtitle, artist, artwork, id, player }
256
- yourAudio.src = e.detail.url;
257
- yourAudio.play();
258
- });
259
-
260
- // Drive the visualization from your audio's timeupdate:
261
- yourAudio.addEventListener('timeupdate', () => {
262
- player.setProgress(yourAudio.currentTime, yourAudio.duration);
263
- });
264
- yourAudio.addEventListener('play', () => player.setPlayingState(true));
265
- yourAudio.addEventListener('pause', () => player.setPlayingState(false));
266
- ```
267
-
268
- Events dispatched in external mode (all bubble; all cancelable):
269
-
270
- | Event | Detail |
271
- |------------------------------------|-----------------------------------------------------|
272
- | `waveformplayer:request-play` | `{ url, title, subtitle, artist, artwork, id, player }` |
273
- | `waveformplayer:request-pause` | Same shape as request-play |
274
- | `waveformplayer:request-seek` | Same shape + `{ percent: 0..1 }` |
275
-
276
- ## API Methods
277
-
278
- ```javascript
279
- // Control playback
280
- player.play();
184
+ player.play(); // returns the <audio>.play() promise in self mode
281
185
  player.pause();
282
186
  player.togglePlay();
283
187
 
284
- // Seek
285
- player.seekTo(30); // Seek to 30 seconds
286
- player.seekToPercent(0.5); // Seek to 50%
287
-
288
- // Volume
289
- player.setVolume(0.8); // 80% volume
290
-
291
- // Speed
292
- player.setPlaybackRate(1.5); // 1.5x speed
293
-
294
- // External-mode state pumps (only useful when audioMode === 'external')
295
- player.setPlayingState(true); // toggle play/pause UI state
296
- player.setProgress(currentTime, duration); // sync canvas + time displays
188
+ player.seekTo(30); // seconds
189
+ player.seekToPercent(0.5);
190
+ player.setVolume(0.8);
191
+ player.setPlaybackRate(1.5);
297
192
 
298
- // Dynamic loading
299
- await player.loadTrack('new-song.mp3', 'New Title', 'New Artist', {
300
- markers: [{time: 30, label: 'Chorus'}],
301
- artwork: 'cover.jpg',
302
- waveform: [0.2, 0.5, 0.8, 0.3] // Optional pre-generated data
303
- });
304
-
305
- // Destroy
306
- player.destroy();
307
- ```
193
+ await player.loadTrack('next.mp3', 'Title', 'Artist', { artwork: 'cover.jpg' });
194
+ player.destroy(); // removes all listeners + DOM
308
195
 
309
- ## Events
310
-
311
- ```javascript
312
- new WaveformPlayer('#player', {
313
- url: 'audio.mp3',
314
- onLoad: (player) => console.log('Loaded'),
315
- onPlay: (player) => console.log('Playing'),
316
- onPause: (player) => console.log('Paused'),
317
- onEnd: (player) => console.log('Ended'),
318
- onTimeUpdate: (current, total, player) => {
319
- console.log(`${current}/${total}`);
320
- }
321
- });
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'
322
202
  ```
323
203
 
324
- ## Keyboard Controls
325
-
326
- When a player is focused (click on it):
327
-
328
- - `Space` - Play/pause
329
- - `←/→` - Seek backward/forward 5 seconds
330
- - `↑/↓` - Volume up/down
331
- - `M` - Mute/unmute
332
- - `0-9` - Jump to 0%-90% of track
333
-
334
- ## Advanced Usage
335
-
336
- ### Pre-generated Waveforms
337
-
338
- For better performance, generate waveform data server-side:
204
+ ### Events
339
205
 
340
- ```javascript
341
- // Generate waveform data once
342
- const waveformData = await WaveformPlayer.generateWaveformData('audio.mp3');
206
+ Lifecycle callbacks via options, or DOM events on the container (all bubble, `e.detail` is typed):
343
207
 
344
- // Use pre-generated data for instant display
208
+ ```js
345
209
  new WaveformPlayer('#player', {
346
- url: 'audio.mp3',
347
- waveform: waveformData // Bypass client-side processing
210
+ url: 'track.mp3',
211
+ onPlay: (player) => {},
212
+ onPause: (player) => {},
213
+ onEnd: (player) => {},
214
+ onTimeUpdate: (currentTime, duration, player) => {},
348
215
  });
349
- ```
350
-
351
- **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:
352
216
 
353
- ```javascript
354
- new WaveformPlayer('#player', {
355
- url: track.audioUrl,
356
- waveform: WaveformPlayer.getPeaksUrl(track.audioUrl),
217
+ el.addEventListener('waveformplayer:timeupdate', (e) => {
218
+ const { currentTime, duration, progress } = e.detail;
357
219
  });
358
220
  ```
359
221
 
360
- 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.
361
-
362
- ### Multiple Players
363
-
364
- ```javascript
365
- // Get all instances
366
- const players = WaveformPlayer.getAllInstances();
222
+ ## External audio mode
367
223
 
368
- // Find specific player
369
- 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.
370
225
 
371
- // Destroy all players
372
- WaveformPlayer.destroyAll();
373
- ```
374
-
375
- ### Custom Styling
376
-
377
- ```css
378
- .waveform-player {
379
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
380
- border-radius: 12px;
381
- }
226
+ ```js
227
+ const player = new WaveformPlayer(el, { audioMode: 'external' });
382
228
 
383
- .waveform-btn {
384
- border-color: #fff;
385
- }
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));
386
236
  ```
387
237
 
388
- ## Framework Integration
389
-
390
- ### React
391
-
392
- ```jsx
393
- import {useEffect, useRef} from 'react';
394
- import WaveformPlayer from '@arraypress/waveform-player';
395
-
396
- function AudioPlayer({url}) {
397
- 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 }` |
398
243
 
399
- useEffect(() => {
400
- const player = new WaveformPlayer(playerRef.current, {url});
401
- return () => player.destroy();
402
- }, [url]);
244
+ ## Frameworks
403
245
 
404
- return <div ref={playerRef}/>;
405
- }
406
- ```
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 |
407
251
 
408
- ### Vue
252
+ ## Ecosystem
409
253
 
410
- ```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.
411
258
 
412
- <template>
413
- <div ref="player"></div>
414
- </template>
259
+ ## Pre-generated waveforms
415
260
 
416
- <script>
417
- import WaveformPlayer from '@arraypress/waveform-player';
261
+ Skip client-side decoding by storing peaks next to each file (`track.mp3` ↔ `track.json`):
418
262
 
419
- export default {
420
- mounted() {
421
- this.player = new WaveformPlayer(this.$refs.player, {
422
- url: this.audioUrl
423
- });
424
- },
425
- beforeDestroy() {
426
- this.player?.destroy();
427
- }
428
- }
429
- </script>
263
+ ```js
264
+ new WaveformPlayer('#player', {
265
+ url: track.audioUrl,
266
+ waveform: WaveformPlayer.getPeaksUrl(track.audioUrl),
267
+ });
430
268
  ```
431
269
 
432
- ## Browser Support
433
-
434
- - Chrome/Edge 90+
435
- - Firefox 88+
436
- - Safari 14+
437
- - 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).
438
271
 
439
- ## Examples
272
+ ## Browser support
440
273
 
441
- See the [live demo](https://waveformplayer.com) for:
442
-
443
- - All visual styles
444
- - Custom styling examples
445
- - Event handling
446
- - Player builder
447
- - BPM detection
448
- - Pre-generated waveforms
449
- - Keyboard navigation
450
- - Speed controls
451
- - 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.
452
275
 
453
276
  ## Development
454
277
 
455
278
  ```bash
456
- # Install dependencies
457
279
  npm install
458
-
459
- # Development mode
460
- npm run dev
461
-
462
- # Build
463
- npm run build
464
-
465
- # Check size
466
- 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
467
284
  ```
468
285
 
469
286
  ## License
470
287
 
471
- MIT © [ArrayPress](https://github.com/arraypress)
472
-
473
- ## Credits
474
-
475
- Created by [David Sherlock](https://github.com/arraypress)
288
+ MIT © [ArrayPress](https://github.com/arraypress) · created by [David Sherlock](https://github.com/arraypress)