@arraypress/waveform-player-astro 0.1.2 → 0.2.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/CHANGELOG.md +22 -0
- package/README.md +16 -2
- package/package.json +5 -5
- package/src/WaveformPlayer.astro +121 -15
- package/src/globals.d.ts +11 -0
- package/src/types.ts +77 -373
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,28 @@ All notable changes to `@arraypress/waveform-player-astro` are documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Types are now sourced directly from the core
|
|
13
|
+
`@arraypress/waveform-player` package — a single source of truth. The
|
|
14
|
+
shared option types (`WaveformStyle`, `ColorPreset`, `AudioMode`,
|
|
15
|
+
`AudioPreload`, `ButtonAlign`, `WaveformMarker`, `WaveformPeaks`) are
|
|
16
|
+
re-exported from the core, and `WaveformPlayerProps` now `extends` the
|
|
17
|
+
core's `WaveformPlayerOptions` instead of re-declaring every option, so
|
|
18
|
+
the two packages can no longer drift. Every previously-exported type name
|
|
19
|
+
is preserved.
|
|
20
|
+
- Bumped the `@arraypress/waveform-player` peer (and dev) dependency to
|
|
21
|
+
`^1.8.0`, which ships the hand-authored `index.d.ts` these types adopt.
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- `accessibleSeek`, `seekLabel`, `barRadius`, and the gradient-array forms
|
|
26
|
+
of `waveformColor` / `progressColor` are now exposed on
|
|
27
|
+
`WaveformPlayerProps` (inherited from the core option surface), filling
|
|
28
|
+
gaps where the previous hand-maintained copy had missed or drifted.
|
|
29
|
+
|
|
8
30
|
## [0.1.2] — 2026-06-27
|
|
9
31
|
|
|
10
32
|
### Changed
|
package/README.md
CHANGED
|
@@ -13,13 +13,18 @@ import wfpJsUrl from '@arraypress/waveform-player/dist/waveform-player.min.js?ur
|
|
|
13
13
|
<script src={wfpJsUrl} is:inline></script>
|
|
14
14
|
|
|
15
15
|
<WaveformPlayer
|
|
16
|
-
|
|
16
|
+
src="/audio/track.mp3"
|
|
17
17
|
title="My Track"
|
|
18
18
|
waveformStyle="bars"
|
|
19
19
|
showBPM
|
|
20
20
|
/>
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
> **Naming note.** `src` is shorthand for `url`. The visual style is `waveformStyle`
|
|
24
|
+
> — **not** `style`, which (as in any Astro component) is the host element's inline
|
|
25
|
+
> CSS. So `style={...}` styles the container; `waveformStyle="bars"` picks the
|
|
26
|
+
> waveform look.
|
|
27
|
+
|
|
23
28
|
## Installation
|
|
24
29
|
|
|
25
30
|
```bash
|
|
@@ -130,7 +135,8 @@ Every prop maps 1:1 to an option on the core library. Omitting a prop emits no `
|
|
|
130
135
|
|
|
131
136
|
| Prop | Type | Default | Description |
|
|
132
137
|
| ----------- | ------------------------------------- | ------------ | ----------- |
|
|
133
|
-
| `url` | `string`
|
|
138
|
+
| `url` | `string` | — | Audio file URL. Provide one of `url` or `src`. |
|
|
139
|
+
| `src` | `string` | — | Shorthand alias for `url` (the core's `data-src`). `url` wins if both are set. |
|
|
134
140
|
| `audioMode` | `'self' \| 'external'` | `'self'` | `'external'` renders waveform only and emits request events. |
|
|
135
141
|
| `preload` | `'auto' \| 'metadata' \| 'none'` | `'metadata'` | Browser preload hint. |
|
|
136
142
|
|
|
@@ -180,6 +186,14 @@ All optional. `colorPreset` controls the auto theme, and any individual colour w
|
|
|
180
186
|
| `showBPM` | `boolean` | `false` | Detect and display BPM. |
|
|
181
187
|
| `buttonAlign` | `'auto' \| 'top' \| 'center' \| 'bottom'` | `'auto'` | Play-button vertical alignment. |
|
|
182
188
|
|
|
189
|
+
### Accessibility & error UI
|
|
190
|
+
|
|
191
|
+
| Prop | Type | Default | Description |
|
|
192
|
+
| ---------------- | --------- | --------------------- | ----------- |
|
|
193
|
+
| `accessibleSeek` | `boolean` | `true` | Render the waveform as an ARIA slider (keyboard-seekable). |
|
|
194
|
+
| `seekLabel` | `string` | — | Accessible name for the seek slider (falls back to the title). |
|
|
195
|
+
| `errorText` | `string` | `'Unable to load audio'` | Message shown when audio fails to load. |
|
|
196
|
+
|
|
183
197
|
### Markers
|
|
184
198
|
|
|
185
199
|
| Prop | Type | Default | Description |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arraypress/waveform-player-astro",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Astro component wrapper for @arraypress/waveform-player — typed props for every option, lazy-mount support, framework-idiomatic ergonomics.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"registry": "https://registry.npmjs.org/"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
|
-
"@arraypress/waveform-player": "^1.
|
|
65
|
+
"@arraypress/waveform-player": "^1.8.0",
|
|
66
66
|
"astro": "^6.0.0 || ^7.0.0"
|
|
67
67
|
},
|
|
68
68
|
"scripts": {
|
|
@@ -71,9 +71,9 @@
|
|
|
71
71
|
"typecheck": "tsc --noEmit"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
|
-
"@arraypress/waveform-player": "^1.
|
|
75
|
-
"astro": "^
|
|
74
|
+
"@arraypress/waveform-player": "^1.12.0",
|
|
75
|
+
"astro": "^7.0.3",
|
|
76
76
|
"typescript": "^6.0.3",
|
|
77
|
-
"vitest": "^4.1.
|
|
77
|
+
"vitest": "^4.1.9"
|
|
78
78
|
}
|
|
79
79
|
}
|
package/src/WaveformPlayer.astro
CHANGED
|
@@ -64,7 +64,22 @@
|
|
|
64
64
|
* `IntersectionObserver` script that promotes lazy mounts when they
|
|
65
65
|
* approach the viewport. The script self-deduplicates via
|
|
66
66
|
* `window.__wfpLazyMountBound` so multiple `<WaveformPlayer lazy>`
|
|
67
|
-
* instances on the same page reuse a single observer.
|
|
67
|
+
* instances on the same page reuse a single observer. If the core
|
|
68
|
+
* library's script hasn't loaded yet, the lazy script waits for
|
|
69
|
+
* `window.WaveformPlayer` via a small bounded retry rather than silently
|
|
70
|
+
* never mounting.
|
|
71
|
+
*
|
|
72
|
+
* ## Astro View Transitions
|
|
73
|
+
*
|
|
74
|
+
* The core library auto-initialises on `DOMContentLoaded` only. Under
|
|
75
|
+
* Astro View Transitions, client-side navigations swap the DOM without
|
|
76
|
+
* re-firing `DOMContentLoaded`, so a freshly-rendered (non-lazy) player
|
|
77
|
+
* would never be mounted after navigation. To cover that, non-lazy
|
|
78
|
+
* instances ship a tiny one-time script that re-runs the core's
|
|
79
|
+
* (idempotent) `WaveformPlayer.init()` on every `astro:page-load`.
|
|
80
|
+
* It self-deduplicates via `window.__wfpInitBound`, and the core skips
|
|
81
|
+
* any container already flagged `data-waveform-initialized`, so the
|
|
82
|
+
* re-init is safe to call repeatedly.
|
|
68
83
|
*
|
|
69
84
|
* @example Basic
|
|
70
85
|
* <WaveformPlayer url="/audio/track.mp3" title="My Track" />
|
|
@@ -95,6 +110,7 @@ import type { WaveformPlayerProps } from './types';
|
|
|
95
110
|
const {
|
|
96
111
|
// Audio
|
|
97
112
|
url,
|
|
113
|
+
src,
|
|
98
114
|
audioMode,
|
|
99
115
|
preload,
|
|
100
116
|
// Waveform visualisation
|
|
@@ -103,6 +119,7 @@ const {
|
|
|
103
119
|
samples,
|
|
104
120
|
barWidth,
|
|
105
121
|
barSpacing,
|
|
122
|
+
barRadius,
|
|
106
123
|
waveform,
|
|
107
124
|
// Colours
|
|
108
125
|
colorPreset,
|
|
@@ -124,7 +141,14 @@ const {
|
|
|
124
141
|
showTime,
|
|
125
142
|
showHoverTime,
|
|
126
143
|
showBPM,
|
|
144
|
+
bpm,
|
|
127
145
|
buttonAlign,
|
|
146
|
+
layout,
|
|
147
|
+
buttonStyle,
|
|
148
|
+
// Accessibility + error UI
|
|
149
|
+
accessibleSeek,
|
|
150
|
+
seekLabel,
|
|
151
|
+
errorText,
|
|
128
152
|
// Markers
|
|
129
153
|
markers,
|
|
130
154
|
showMarkers,
|
|
@@ -186,7 +210,9 @@ const setBool = (key: string, value: boolean | undefined | null): void => {
|
|
|
186
210
|
};
|
|
187
211
|
|
|
188
212
|
// ─── Audio source ────────────────────────────────────────────────────────
|
|
189
|
-
|
|
213
|
+
// `src` is the core's shorthand alias for `url`; emit a single canonical
|
|
214
|
+
// `data-url` from whichever was provided (`url` wins if both are set).
|
|
215
|
+
setStr('data-url', url ?? src ?? null);
|
|
190
216
|
setStr('data-audio-mode', audioMode ?? null);
|
|
191
217
|
setStr('data-preload', preload ?? null);
|
|
192
218
|
|
|
@@ -196,6 +222,7 @@ setNum('data-height', height);
|
|
|
196
222
|
setNum('data-samples', samples);
|
|
197
223
|
setNum('data-bar-width', barWidth);
|
|
198
224
|
setNum('data-bar-spacing', barSpacing);
|
|
225
|
+
setNum('data-bar-radius', barRadius);
|
|
199
226
|
|
|
200
227
|
// `waveform` accepts an array (JSON-stringify it) or a string (URL/inline
|
|
201
228
|
// JSON — pass through). The library handles both forms.
|
|
@@ -210,8 +237,12 @@ if (Array.isArray(waveform)) {
|
|
|
210
237
|
if (colorPreset === 'dark' || colorPreset === 'light') {
|
|
211
238
|
data['data-color-preset'] = colorPreset;
|
|
212
239
|
}
|
|
213
|
-
|
|
214
|
-
|
|
240
|
+
// Colours accept an array of gradient stops — JSON-encode those so the
|
|
241
|
+
// library's data-attribute parser can rebuild the gradient.
|
|
242
|
+
if (Array.isArray(waveformColor)) data['data-waveform-color'] = JSON.stringify(waveformColor);
|
|
243
|
+
else setStr('data-waveform-color', waveformColor ?? null);
|
|
244
|
+
if (Array.isArray(progressColor)) data['data-progress-color'] = JSON.stringify(progressColor);
|
|
245
|
+
else setStr('data-progress-color', progressColor ?? null);
|
|
215
246
|
setStr('data-button-color', buttonColor ?? null);
|
|
216
247
|
setStr('data-button-hover-color', buttonHoverColor ?? null);
|
|
217
248
|
setStr('data-text-color', textColor ?? null);
|
|
@@ -234,7 +265,15 @@ setBool('data-show-hover-time', showHoverTime);
|
|
|
234
265
|
// The library's data-attribute parser reads `data-show-bpm` (lowercased
|
|
235
266
|
// `bpm`), NOT `data-show-b-p-m`. Get this wrong and BPM never displays.
|
|
236
267
|
setBool('data-show-bpm', showBPM);
|
|
268
|
+
setNum('data-bpm', bpm);
|
|
237
269
|
setStr('data-button-align', buttonAlign ?? null);
|
|
270
|
+
setStr('data-layout', layout ?? null);
|
|
271
|
+
setStr('data-button-style', buttonStyle ?? null);
|
|
272
|
+
|
|
273
|
+
// ─── Accessibility + error UI ────────────────────────────────────────────
|
|
274
|
+
setBool('data-accessible-seek', accessibleSeek);
|
|
275
|
+
setStr('data-seek-label', seekLabel ?? null);
|
|
276
|
+
setStr('data-error-text', errorText ?? null);
|
|
238
277
|
|
|
239
278
|
// ─── Markers ─────────────────────────────────────────────────────────────
|
|
240
279
|
if (Array.isArray(markers) && markers.length > 0) {
|
|
@@ -277,7 +316,7 @@ const initAttr = lazy ? 'data-waveform-player-lazy' : 'data-waveform-player';
|
|
|
277
316
|
style={style}
|
|
278
317
|
></div>
|
|
279
318
|
|
|
280
|
-
{lazy
|
|
319
|
+
{lazy ? (
|
|
281
320
|
<script is:inline>
|
|
282
321
|
/**
|
|
283
322
|
* Lazy-mount watcher for `<WaveformPlayer lazy>` instances.
|
|
@@ -295,35 +334,70 @@ const initAttr = lazy ? 'data-waveform-player-lazy' : 'data-waveform-player';
|
|
|
295
334
|
*
|
|
296
335
|
* Re-runs on Astro `astro:page-load` so cross-page navigations
|
|
297
336
|
* pick up newly-rendered lazy mounts.
|
|
337
|
+
*
|
|
338
|
+
* If the core library's script hasn't installed `window.WaveformPlayer`
|
|
339
|
+
* yet (e.g. on a plain page where this inline script runs before the
|
|
340
|
+
* library's `<script>`), we wait for it via a small bounded retry
|
|
341
|
+
* instead of returning and never mounting.
|
|
298
342
|
*/
|
|
299
343
|
(function () {
|
|
300
344
|
if (window.__wfpLazyMountBound) return;
|
|
301
345
|
window.__wfpLazyMountBound = true;
|
|
302
346
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
347
|
+
var MAX_WAIT_ATTEMPTS = 20; // ~1s total at 50ms between attempts
|
|
348
|
+
|
|
349
|
+
function observeLazyMounts() {
|
|
350
|
+
var targets = document.querySelectorAll('[data-waveform-player-lazy]');
|
|
306
351
|
if (!targets.length) return;
|
|
307
352
|
|
|
308
|
-
|
|
309
|
-
(entries)
|
|
310
|
-
entries.forEach((entry)
|
|
353
|
+
var io = new IntersectionObserver(
|
|
354
|
+
function (entries) {
|
|
355
|
+
entries.forEach(function (entry) {
|
|
311
356
|
if (!entry.isIntersecting) return;
|
|
312
|
-
|
|
357
|
+
var el = entry.target;
|
|
313
358
|
el.removeAttribute('data-waveform-player-lazy');
|
|
314
359
|
el.setAttribute('data-waveform-player', '');
|
|
315
360
|
io.unobserve(el);
|
|
316
361
|
try {
|
|
317
|
-
window.WaveformPlayer.init();
|
|
362
|
+
window.WaveformPlayer && window.WaveformPlayer.init();
|
|
318
363
|
} catch (err) {
|
|
319
|
-
console.error('[
|
|
364
|
+
console.error('[WaveformPlayerAstro] init failed:', err);
|
|
320
365
|
}
|
|
321
366
|
});
|
|
322
367
|
},
|
|
323
368
|
{ rootMargin: '200px 0px 200px 0px', threshold: 0.01 }
|
|
324
369
|
);
|
|
325
370
|
|
|
326
|
-
targets.forEach((target)
|
|
371
|
+
targets.forEach(function (target) {
|
|
372
|
+
io.observe(target);
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function mountLazy() {
|
|
377
|
+
// Core already present — wire up the observer immediately.
|
|
378
|
+
if (window.WaveformPlayer) {
|
|
379
|
+
observeLazyMounts();
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
// Core script not loaded yet. Poll a bounded number of times
|
|
383
|
+
// for `window.WaveformPlayer` rather than silently giving up.
|
|
384
|
+
var attempts = 0;
|
|
385
|
+
(function waitForCore() {
|
|
386
|
+
if (window.WaveformPlayer) {
|
|
387
|
+
observeLazyMounts();
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
if (++attempts > MAX_WAIT_ATTEMPTS) {
|
|
391
|
+
console.warn(
|
|
392
|
+
'[WaveformPlayerAstro] window.WaveformPlayer not found after ' +
|
|
393
|
+
MAX_WAIT_ATTEMPTS +
|
|
394
|
+
' attempts; lazy players will not mount. Did you load ' +
|
|
395
|
+
"@arraypress/waveform-player's script?"
|
|
396
|
+
);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
setTimeout(waitForCore, 50);
|
|
400
|
+
})();
|
|
327
401
|
}
|
|
328
402
|
|
|
329
403
|
document.addEventListener('astro:page-load', mountLazy);
|
|
@@ -334,4 +408,36 @@ const initAttr = lazy ? 'data-waveform-player-lazy' : 'data-waveform-player';
|
|
|
334
408
|
}
|
|
335
409
|
})();
|
|
336
410
|
</script>
|
|
411
|
+
) : (
|
|
412
|
+
<script is:inline>
|
|
413
|
+
/**
|
|
414
|
+
* View Transitions re-init for non-lazy `<WaveformPlayer>` instances.
|
|
415
|
+
*
|
|
416
|
+
* The core library auto-initialises on `DOMContentLoaded` only.
|
|
417
|
+
* Under Astro View Transitions, client-side navigations swap the DOM
|
|
418
|
+
* without re-firing `DOMContentLoaded`, so freshly-rendered
|
|
419
|
+
* `[data-waveform-player]` containers would never be mounted after a
|
|
420
|
+
* navigation. Re-run the core initialiser on every `astro:page-load`.
|
|
421
|
+
*
|
|
422
|
+
* `WaveformPlayer.init()` is idempotent — it skips any container
|
|
423
|
+
* already flagged `data-waveform-initialized` (which the core sets on
|
|
424
|
+
* first mount) — so calling it repeatedly only mounts the new ones.
|
|
425
|
+
*
|
|
426
|
+
* Deduplicated via `window.__wfpInitBound` so multiple non-lazy
|
|
427
|
+
* instances bind a single shared `astro:page-load` listener that
|
|
428
|
+
* persists across navigations.
|
|
429
|
+
*/
|
|
430
|
+
(function () {
|
|
431
|
+
if (window.__wfpInitBound) return;
|
|
432
|
+
window.__wfpInitBound = true;
|
|
433
|
+
|
|
434
|
+
document.addEventListener('astro:page-load', function () {
|
|
435
|
+
try {
|
|
436
|
+
window.WaveformPlayer && window.WaveformPlayer.init();
|
|
437
|
+
} catch (err) {
|
|
438
|
+
console.error('[WaveformPlayerAstro] init failed:', err);
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
})();
|
|
442
|
+
</script>
|
|
337
443
|
)}
|
package/src/globals.d.ts
CHANGED
|
@@ -76,6 +76,17 @@ declare global {
|
|
|
76
76
|
* @internal
|
|
77
77
|
*/
|
|
78
78
|
__wfpLazyMountBound?: boolean;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Internal — set by the non-lazy `<WaveformPlayer>`'s inline
|
|
82
|
+
* script to deduplicate the `astro:page-load` re-init listener
|
|
83
|
+
* that re-runs `WaveformPlayer.init()` after View Transitions
|
|
84
|
+
* navigations. Do not rely on this; it is an implementation
|
|
85
|
+
* detail.
|
|
86
|
+
*
|
|
87
|
+
* @internal
|
|
88
|
+
*/
|
|
89
|
+
__wfpInitBound?: boolean;
|
|
79
90
|
}
|
|
80
91
|
}
|
|
81
92
|
|
package/src/types.ts
CHANGED
|
@@ -3,406 +3,110 @@
|
|
|
3
3
|
* @description
|
|
4
4
|
* Public TypeScript types for `@arraypress/waveform-player-astro`.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* The shared option surface — visual styles, colour presets, audio modes,
|
|
7
|
+
* markers, peaks, and the full per-option list — is owned by the core
|
|
8
|
+
* `@arraypress/waveform-player` library, which ships hand-authored type
|
|
9
|
+
* declarations (`index.d.ts`). This wrapper re-exports those shared types
|
|
10
|
+
* verbatim (so consumers can keep importing them from this package) and
|
|
11
|
+
* derives its component prop interface from the core's
|
|
12
|
+
* {@link WaveformPlayerOptions} rather than re-declaring it. That keeps the
|
|
13
|
+
* two packages from drifting: any option the core adds (or renames) flows
|
|
14
|
+
* through here automatically, and there is a single source of truth.
|
|
10
15
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* own internal defaults.
|
|
16
|
+
* The prop names match the library option names 1:1 (camelCase) — the
|
|
17
|
+
* Astro component handles the conversion to the library's `data-*`
|
|
18
|
+
* attribute contract (kebab-case) under the hood.
|
|
15
19
|
*
|
|
16
|
-
*
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
* Visual style of the waveform rendering.
|
|
20
|
+
* Props are intentionally all optional (except `url`, which the wrapper
|
|
21
|
+
* requires so the player always has audio to load). When a prop is
|
|
22
|
+
* omitted, the wrapper emits no corresponding `data-*` attribute, letting
|
|
23
|
+
* the core library apply its own internal defaults.
|
|
21
24
|
*
|
|
22
|
-
* -
|
|
23
|
-
* - `mirror` — symmetrical bars mirrored around the centre line
|
|
24
|
-
* - `line` — connected line graph
|
|
25
|
-
* - `blocks` — chunky square blocks
|
|
26
|
-
* - `dots` — dotted plot
|
|
27
|
-
* - `seekbar` — minimal seek bar with no peak detail
|
|
28
|
-
*/
|
|
29
|
-
export type WaveformStyle =
|
|
30
|
-
| 'bars'
|
|
31
|
-
| 'mirror'
|
|
32
|
-
| 'line'
|
|
33
|
-
| 'blocks'
|
|
34
|
-
| 'dots'
|
|
35
|
-
| 'seekbar';
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Forced colour scheme. `null` (the default) auto-detects from the page
|
|
39
|
-
* theme and `prefers-color-scheme`.
|
|
25
|
+
* @see {@link https://github.com/arraypress/waveform-player} — core library
|
|
40
26
|
*/
|
|
41
|
-
export type ColorPreset = 'dark' | 'light' | null;
|
|
42
27
|
|
|
43
|
-
|
|
44
|
-
* How the player handles audio.
|
|
45
|
-
*
|
|
46
|
-
* - `self` — (default) the player owns an `<audio>` element and plays
|
|
47
|
-
* the URL itself. Use this for standalone players.
|
|
48
|
-
* - `external` — the player renders the waveform visualisation only and
|
|
49
|
-
* dispatches `waveformplayer:request-play|pause|seek` events for an
|
|
50
|
-
* external controller (e.g. `@arraypress/waveform-bar`) to handle.
|
|
51
|
-
* Drive the visualisation from the controller by calling
|
|
52
|
-
* `setProgress()` and `setPlayingState()` on the player instance.
|
|
53
|
-
*/
|
|
54
|
-
export type AudioMode = 'self' | 'external';
|
|
28
|
+
import type { WaveformPlayerOptions } from '@arraypress/waveform-player';
|
|
55
29
|
|
|
56
30
|
/**
|
|
57
|
-
*
|
|
31
|
+
* Shared option types, re-exported from the core library so consumers can
|
|
32
|
+
* continue to import them from `@arraypress/waveform-player-astro`:
|
|
58
33
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
* Vertical alignment of the play button relative to the waveform.
|
|
34
|
+
* ```ts
|
|
35
|
+
* import type {
|
|
36
|
+
* WaveformStyle,
|
|
37
|
+
* WaveformMarker,
|
|
38
|
+
* WaveformPeaks,
|
|
39
|
+
* } from '@arraypress/waveform-player-astro';
|
|
40
|
+
* ```
|
|
67
41
|
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
42
|
+
* The core's `index.d.ts` is the single source of truth for their shape —
|
|
43
|
+
* this wrapper no longer maintains parallel copies that can drift.
|
|
70
44
|
*/
|
|
71
|
-
export type
|
|
45
|
+
export type {
|
|
46
|
+
WaveformStyle,
|
|
47
|
+
ColorPreset,
|
|
48
|
+
AudioMode,
|
|
49
|
+
AudioPreload,
|
|
50
|
+
ButtonAlign,
|
|
51
|
+
WaveformMarker,
|
|
52
|
+
WaveformPeaks,
|
|
53
|
+
} from '@arraypress/waveform-player';
|
|
72
54
|
|
|
73
55
|
/**
|
|
74
|
-
*
|
|
56
|
+
* Props accepted by the `<WaveformPlayer>` Astro component.
|
|
75
57
|
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
time: number;
|
|
82
|
-
/** Short label shown as a tooltip. */
|
|
83
|
-
label: string;
|
|
84
|
-
/** Optional override colour (CSS colour string). Falls back to the
|
|
85
|
-
* player's progress colour. */
|
|
86
|
-
color?: string;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Pre-computed waveform peaks, OR a string pointer to them.
|
|
58
|
+
* Inherits every construction option from the core library's
|
|
59
|
+
* {@link WaveformPlayerOptions} (including `accessibleSeek`, `seekLabel`,
|
|
60
|
+
* `barRadius`, and the gradient-array forms of `waveformColor` /
|
|
61
|
+
* `progressColor`) and layers on the Astro-specific extras. Two groups of
|
|
62
|
+
* core options are deliberately removed:
|
|
91
63
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
64
|
+
* - `url` — optional on the core options, but **required** here (the
|
|
65
|
+
* wrapper exists to render a player, which needs a source). Redeclared
|
|
66
|
+
* below as a required `string`.
|
|
67
|
+
* - `style` — the core exposes `style` as a shorthand alias for
|
|
68
|
+
* {@link WaveformStyle} (`data-style`), but in an Astro component `style`
|
|
69
|
+
* is the HTML inline-style attribute. We remove the core's alias and
|
|
70
|
+
* redeclare `style` below as the inline-CSS `string`; consumers select
|
|
71
|
+
* the visual style via the canonical `waveformStyle` prop instead.
|
|
72
|
+
* - the `on*` lifecycle callbacks (`onLoad`, `onPlay`, …) — these are JS
|
|
73
|
+
* functions, and a server-rendered Astro component emits static HTML
|
|
74
|
+
* plus `data-*` attributes with nothing to attach a runtime callback
|
|
75
|
+
* to. Consumers wire lifecycle handling via the DOM
|
|
76
|
+
* `waveformplayer:*` events instead.
|
|
97
77
|
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* tracks; it removes a full Web Audio decode from the render path.
|
|
78
|
+
* Because the option surface is inherited rather than hand-copied, anything
|
|
79
|
+
* the core adds in future is exposed here without a manual edit.
|
|
101
80
|
*/
|
|
102
|
-
export
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
* 7. Content metadata
|
|
115
|
-
* 8. Behaviour flags
|
|
116
|
-
* 9. Icons
|
|
117
|
-
* 10. Astro-specific extras
|
|
118
|
-
*/
|
|
119
|
-
export interface WaveformPlayerProps {
|
|
81
|
+
export interface WaveformPlayerProps
|
|
82
|
+
extends Omit<
|
|
83
|
+
WaveformPlayerOptions,
|
|
84
|
+
| 'url'
|
|
85
|
+
| 'style'
|
|
86
|
+
| 'onLoad'
|
|
87
|
+
| 'onPlay'
|
|
88
|
+
| 'onPause'
|
|
89
|
+
| 'onEnd'
|
|
90
|
+
| 'onError'
|
|
91
|
+
| 'onTimeUpdate'
|
|
92
|
+
> {
|
|
120
93
|
// ─────────────────────────────────────────────────────────────────────
|
|
121
|
-
//
|
|
94
|
+
// Audio source (Astro-required override)
|
|
122
95
|
// ─────────────────────────────────────────────────────────────────────
|
|
123
96
|
|
|
124
97
|
/**
|
|
125
|
-
* Audio file URL.
|
|
98
|
+
* Audio file URL. Provide one of `url` or its shorthand alias `src`
|
|
99
|
+
* (inherited from the core options) — the player needs a source to do
|
|
100
|
+
* anything useful.
|
|
126
101
|
*
|
|
127
102
|
* Supported formats are whatever the user's browser supports — MP3,
|
|
128
103
|
* WAV, OGG, AAC, etc. CORS headers must allow cross-origin loads if
|
|
129
104
|
* hosted on a different domain.
|
|
130
105
|
*/
|
|
131
|
-
url
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Whether the player owns its `<audio>` element (`self`) or only
|
|
135
|
-
* renders visualisation and emits request events (`external`).
|
|
136
|
-
*
|
|
137
|
-
* Use `external` together with `@arraypress/waveform-bar` for
|
|
138
|
-
* persistent-bar setups where one audio element drives many visual
|
|
139
|
-
* surfaces across the page.
|
|
140
|
-
*
|
|
141
|
-
* @default 'self'
|
|
142
|
-
*/
|
|
143
|
-
audioMode?: AudioMode;
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Browser preload hint for the underlying `<audio>` element.
|
|
147
|
-
*
|
|
148
|
-
* @default 'metadata'
|
|
149
|
-
*/
|
|
150
|
-
preload?: AudioPreload;
|
|
151
|
-
|
|
152
|
-
// ─────────────────────────────────────────────────────────────────────
|
|
153
|
-
// 2. Waveform visualisation
|
|
154
|
-
// ─────────────────────────────────────────────────────────────────────
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Which waveform render style to use.
|
|
158
|
-
*
|
|
159
|
-
* @default 'mirror'
|
|
160
|
-
*/
|
|
161
|
-
waveformStyle?: WaveformStyle;
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Canvas height in pixels.
|
|
165
|
-
*
|
|
166
|
-
* @default 60
|
|
167
|
-
*/
|
|
168
|
-
height?: number;
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Number of peak samples to render across the waveform. Higher values
|
|
172
|
-
* give finer detail at the cost of slightly higher CPU and a busier
|
|
173
|
-
* visual. 100–300 is the sweet spot for most player widths.
|
|
174
|
-
*
|
|
175
|
-
* @default 200
|
|
176
|
-
*/
|
|
177
|
-
samples?: number;
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Width of each bar/block in pixels (where applicable). Defaults
|
|
181
|
-
* depend on `waveformStyle` (e.g. `2` for `mirror`, `3` for `bars`).
|
|
182
|
-
*/
|
|
183
|
-
barWidth?: number;
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Gap between adjacent bars/blocks in pixels. Defaults depend on
|
|
187
|
-
* `waveformStyle`.
|
|
188
|
-
*/
|
|
189
|
-
barSpacing?: number;
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Pre-computed peaks data. See {@link WaveformPeaks}.
|
|
193
|
-
*
|
|
194
|
-
* - `number[]` is JSON-stringified into the emitted attribute.
|
|
195
|
-
* - `string` (URL or inline JSON) is emitted verbatim — the library
|
|
196
|
-
* parses it.
|
|
197
|
-
*/
|
|
198
|
-
waveform?: WaveformPeaks;
|
|
199
|
-
|
|
200
|
-
// ─────────────────────────────────────────────────────────────────────
|
|
201
|
-
// 3. Colours
|
|
202
|
-
// ─────────────────────────────────────────────────────────────────────
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Forced colour preset. `null` auto-detects from the page theme.
|
|
206
|
-
*
|
|
207
|
-
* Individual `*Color` props always win over the preset, letting you
|
|
208
|
-
* keep the auto-detection but tweak one or two colours.
|
|
209
|
-
*
|
|
210
|
-
* @default null
|
|
211
|
-
*/
|
|
212
|
-
colorPreset?: ColorPreset;
|
|
213
|
-
|
|
214
|
-
/** Colour of the unplayed waveform peaks (CSS colour string). */
|
|
215
|
-
waveformColor?: string;
|
|
216
|
-
/** Colour of the played-through portion of the waveform. */
|
|
217
|
-
progressColor?: string;
|
|
218
|
-
/** Border/text colour of the play button. */
|
|
219
|
-
buttonColor?: string;
|
|
220
|
-
/** Play button hover colour. */
|
|
221
|
-
buttonHoverColor?: string;
|
|
222
|
-
/** Primary text colour (title). */
|
|
223
|
-
textColor?: string;
|
|
224
|
-
/** Secondary text colour (subtitle, time). */
|
|
225
|
-
textSecondaryColor?: string;
|
|
226
|
-
/** Reserved for future use; currently no visible effect. */
|
|
227
|
-
backgroundColor?: string;
|
|
228
|
-
/** Reserved for future use; currently no visible effect. */
|
|
229
|
-
borderColor?: string;
|
|
230
|
-
|
|
231
|
-
// ─────────────────────────────────────────────────────────────────────
|
|
232
|
-
// 4. Playback controls
|
|
233
|
-
// ─────────────────────────────────────────────────────────────────────
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Initial playback rate (1 = normal speed; 0.5..2 supported).
|
|
237
|
-
*
|
|
238
|
-
* @default 1
|
|
239
|
-
*/
|
|
240
|
-
playbackRate?: number;
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Whether to render the playback-speed control menu.
|
|
244
|
-
*
|
|
245
|
-
* @default false
|
|
246
|
-
*/
|
|
247
|
-
showPlaybackSpeed?: boolean;
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* List of speeds to offer in the speed menu.
|
|
251
|
-
*
|
|
252
|
-
* @default [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]
|
|
253
|
-
*/
|
|
254
|
-
playbackRates?: number[];
|
|
255
|
-
|
|
256
|
-
// ─────────────────────────────────────────────────────────────────────
|
|
257
|
-
// 5. UI toggles
|
|
258
|
-
// ─────────────────────────────────────────────────────────────────────
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Show the play/pause button.
|
|
262
|
-
*
|
|
263
|
-
* @default true
|
|
264
|
-
*/
|
|
265
|
-
showControls?: boolean;
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Show the info bar (title, subtitle, time, BPM, speed).
|
|
269
|
-
*
|
|
270
|
-
* @default true
|
|
271
|
-
*/
|
|
272
|
-
showInfo?: boolean;
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Show the current-time / total-time readout.
|
|
276
|
-
*
|
|
277
|
-
* @default true
|
|
278
|
-
*/
|
|
279
|
-
showTime?: boolean;
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Show a hover-time indicator on the waveform (reserved — not all
|
|
283
|
-
* library versions implement this yet).
|
|
284
|
-
*
|
|
285
|
-
* @default false
|
|
286
|
-
*/
|
|
287
|
-
showHoverTime?: boolean;
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Detect and display the track's BPM in the info bar.
|
|
291
|
-
*
|
|
292
|
-
* Adds a ~50–200 ms onset-detection pass on first load.
|
|
293
|
-
*
|
|
294
|
-
* @default false
|
|
295
|
-
*/
|
|
296
|
-
showBPM?: boolean;
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Where to align the play button vertically relative to the waveform.
|
|
300
|
-
*
|
|
301
|
-
* @default 'auto'
|
|
302
|
-
*/
|
|
303
|
-
buttonAlign?: ButtonAlign;
|
|
304
|
-
|
|
305
|
-
// ─────────────────────────────────────────────────────────────────────
|
|
306
|
-
// 6. Markers
|
|
307
|
-
// ─────────────────────────────────────────────────────────────────────
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Chapter markers to render on the waveform. Each becomes a clickable
|
|
311
|
-
* seek point.
|
|
312
|
-
*
|
|
313
|
-
* Emitted as JSON-stringified `data-markers`. The library parses it.
|
|
314
|
-
*/
|
|
315
|
-
markers?: WaveformMarker[];
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Whether to render markers at all. Useful for hiding markers in
|
|
319
|
-
* compact layouts while still passing them in for future use.
|
|
320
|
-
*
|
|
321
|
-
* @default true
|
|
322
|
-
*/
|
|
323
|
-
showMarkers?: boolean;
|
|
324
|
-
|
|
325
|
-
// ─────────────────────────────────────────────────────────────────────
|
|
326
|
-
// 7. Content metadata
|
|
327
|
-
// ─────────────────────────────────────────────────────────────────────
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Track title shown in the info bar. Falls back to a prettified
|
|
331
|
-
* filename extracted from the URL.
|
|
332
|
-
*/
|
|
333
|
-
title?: string;
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Subtitle shown under the title — typically the artist name.
|
|
337
|
-
*/
|
|
338
|
-
subtitle?: string;
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Album / collection cover image URL displayed as a thumbnail.
|
|
342
|
-
*/
|
|
343
|
-
artwork?: string;
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Album name. Passed through to the Media Session API for lockscreen
|
|
347
|
-
* and OS-level media displays.
|
|
348
|
-
*/
|
|
349
|
-
album?: string;
|
|
350
|
-
|
|
351
|
-
// ─────────────────────────────────────────────────────────────────────
|
|
352
|
-
// 8. Behaviour flags
|
|
353
|
-
// ─────────────────────────────────────────────────────────────────────
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Start playback as soon as audio metadata loads.
|
|
357
|
-
*
|
|
358
|
-
* Note that most browsers block autoplay with audible audio unless
|
|
359
|
-
* the user has interacted with the page first.
|
|
360
|
-
*
|
|
361
|
-
* @default false
|
|
362
|
-
*/
|
|
363
|
-
autoplay?: boolean;
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Pause any other `WaveformPlayer` on the page when this one starts.
|
|
367
|
-
*
|
|
368
|
-
* @default true
|
|
369
|
-
*/
|
|
370
|
-
singlePlay?: boolean;
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Resume playback automatically when the user seeks on a paused
|
|
374
|
-
* waveform.
|
|
375
|
-
*
|
|
376
|
-
* @default true
|
|
377
|
-
*/
|
|
378
|
-
playOnSeek?: boolean;
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* Wire up the browser's Media Session API so OS media keys, lock
|
|
382
|
-
* screens, and bluetooth controls drive the player.
|
|
383
|
-
*
|
|
384
|
-
* @default true
|
|
385
|
-
*/
|
|
386
|
-
enableMediaSession?: boolean;
|
|
387
|
-
|
|
388
|
-
// ─────────────────────────────────────────────────────────────────────
|
|
389
|
-
// 9. Icons
|
|
390
|
-
// ─────────────────────────────────────────────────────────────────────
|
|
391
|
-
|
|
392
|
-
/**
|
|
393
|
-
* Inline HTML/SVG to use for the play button. Anything you pass is
|
|
394
|
-
* injected raw — use trusted markup only.
|
|
395
|
-
*/
|
|
396
|
-
playIcon?: string;
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* Inline HTML/SVG to use for the pause button. Anything you pass is
|
|
400
|
-
* injected raw — use trusted markup only.
|
|
401
|
-
*/
|
|
402
|
-
pauseIcon?: string;
|
|
106
|
+
url?: string;
|
|
403
107
|
|
|
404
108
|
// ─────────────────────────────────────────────────────────────────────
|
|
405
|
-
//
|
|
109
|
+
// Astro-specific extras
|
|
406
110
|
// ─────────────────────────────────────────────────────────────────────
|
|
407
111
|
|
|
408
112
|
/**
|