@arraypress/waveform-player-astro 0.1.2 → 0.1.3

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 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
- url="/audio/track.mp3"
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` *(required)* | — | Audio file URL. |
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.1.2",
3
+ "version": "0.1.3",
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.7.2",
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.7.2",
75
- "astro": "^6.3.7",
74
+ "@arraypress/waveform-player": "^1.8.0",
75
+ "astro": "^7.0.3",
76
76
  "typescript": "^6.0.3",
77
- "vitest": "^4.1.7"
77
+ "vitest": "^4.1.9"
78
78
  }
79
79
  }
@@ -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,
@@ -125,6 +142,10 @@ const {
125
142
  showHoverTime,
126
143
  showBPM,
127
144
  buttonAlign,
145
+ // Accessibility + error UI
146
+ accessibleSeek,
147
+ seekLabel,
148
+ errorText,
128
149
  // Markers
129
150
  markers,
130
151
  showMarkers,
@@ -186,7 +207,9 @@ const setBool = (key: string, value: boolean | undefined | null): void => {
186
207
  };
187
208
 
188
209
  // ─── Audio source ────────────────────────────────────────────────────────
189
- setStr('data-url', url);
210
+ // `src` is the core's shorthand alias for `url`; emit a single canonical
211
+ // `data-url` from whichever was provided (`url` wins if both are set).
212
+ setStr('data-url', url ?? src ?? null);
190
213
  setStr('data-audio-mode', audioMode ?? null);
191
214
  setStr('data-preload', preload ?? null);
192
215
 
@@ -196,6 +219,7 @@ setNum('data-height', height);
196
219
  setNum('data-samples', samples);
197
220
  setNum('data-bar-width', barWidth);
198
221
  setNum('data-bar-spacing', barSpacing);
222
+ setNum('data-bar-radius', barRadius);
199
223
 
200
224
  // `waveform` accepts an array (JSON-stringify it) or a string (URL/inline
201
225
  // JSON — pass through). The library handles both forms.
@@ -210,8 +234,12 @@ if (Array.isArray(waveform)) {
210
234
  if (colorPreset === 'dark' || colorPreset === 'light') {
211
235
  data['data-color-preset'] = colorPreset;
212
236
  }
213
- setStr('data-waveform-color', waveformColor ?? null);
214
- setStr('data-progress-color', progressColor ?? null);
237
+ // Colours accept an array of gradient stops — JSON-encode those so the
238
+ // library's data-attribute parser can rebuild the gradient.
239
+ if (Array.isArray(waveformColor)) data['data-waveform-color'] = JSON.stringify(waveformColor);
240
+ else setStr('data-waveform-color', waveformColor ?? null);
241
+ if (Array.isArray(progressColor)) data['data-progress-color'] = JSON.stringify(progressColor);
242
+ else setStr('data-progress-color', progressColor ?? null);
215
243
  setStr('data-button-color', buttonColor ?? null);
216
244
  setStr('data-button-hover-color', buttonHoverColor ?? null);
217
245
  setStr('data-text-color', textColor ?? null);
@@ -236,6 +264,11 @@ setBool('data-show-hover-time', showHoverTime);
236
264
  setBool('data-show-bpm', showBPM);
237
265
  setStr('data-button-align', buttonAlign ?? null);
238
266
 
267
+ // ─── Accessibility + error UI ────────────────────────────────────────────
268
+ setBool('data-accessible-seek', accessibleSeek);
269
+ setStr('data-seek-label', seekLabel ?? null);
270
+ setStr('data-error-text', errorText ?? null);
271
+
239
272
  // ─── Markers ─────────────────────────────────────────────────────────────
240
273
  if (Array.isArray(markers) && markers.length > 0) {
241
274
  data['data-markers'] = JSON.stringify(markers);
@@ -277,7 +310,7 @@ const initAttr = lazy ? 'data-waveform-player-lazy' : 'data-waveform-player';
277
310
  style={style}
278
311
  ></div>
279
312
 
280
- {lazy && (
313
+ {lazy ? (
281
314
  <script is:inline>
282
315
  /**
283
316
  * Lazy-mount watcher for `<WaveformPlayer lazy>` instances.
@@ -295,35 +328,70 @@ const initAttr = lazy ? 'data-waveform-player-lazy' : 'data-waveform-player';
295
328
  *
296
329
  * Re-runs on Astro `astro:page-load` so cross-page navigations
297
330
  * pick up newly-rendered lazy mounts.
331
+ *
332
+ * If the core library's script hasn't installed `window.WaveformPlayer`
333
+ * yet (e.g. on a plain page where this inline script runs before the
334
+ * library's `<script>`), we wait for it via a small bounded retry
335
+ * instead of returning and never mounting.
298
336
  */
299
337
  (function () {
300
338
  if (window.__wfpLazyMountBound) return;
301
339
  window.__wfpLazyMountBound = true;
302
340
 
303
- function mountLazy() {
304
- if (!window.WaveformPlayer) return;
305
- const targets = document.querySelectorAll('[data-waveform-player-lazy]');
341
+ var MAX_WAIT_ATTEMPTS = 20; // ~1s total at 50ms between attempts
342
+
343
+ function observeLazyMounts() {
344
+ var targets = document.querySelectorAll('[data-waveform-player-lazy]');
306
345
  if (!targets.length) return;
307
346
 
308
- const io = new IntersectionObserver(
309
- (entries) => {
310
- entries.forEach((entry) => {
347
+ var io = new IntersectionObserver(
348
+ function (entries) {
349
+ entries.forEach(function (entry) {
311
350
  if (!entry.isIntersecting) return;
312
- const el = entry.target;
351
+ var el = entry.target;
313
352
  el.removeAttribute('data-waveform-player-lazy');
314
353
  el.setAttribute('data-waveform-player', '');
315
354
  io.unobserve(el);
316
355
  try {
317
- window.WaveformPlayer.init();
356
+ window.WaveformPlayer && window.WaveformPlayer.init();
318
357
  } catch (err) {
319
- console.error('[waveform-player-astro] init failed:', err);
358
+ console.error('[WaveformPlayerAstro] init failed:', err);
320
359
  }
321
360
  });
322
361
  },
323
362
  { rootMargin: '200px 0px 200px 0px', threshold: 0.01 }
324
363
  );
325
364
 
326
- targets.forEach((target) => io.observe(target));
365
+ targets.forEach(function (target) {
366
+ io.observe(target);
367
+ });
368
+ }
369
+
370
+ function mountLazy() {
371
+ // Core already present — wire up the observer immediately.
372
+ if (window.WaveformPlayer) {
373
+ observeLazyMounts();
374
+ return;
375
+ }
376
+ // Core script not loaded yet. Poll a bounded number of times
377
+ // for `window.WaveformPlayer` rather than silently giving up.
378
+ var attempts = 0;
379
+ (function waitForCore() {
380
+ if (window.WaveformPlayer) {
381
+ observeLazyMounts();
382
+ return;
383
+ }
384
+ if (++attempts > MAX_WAIT_ATTEMPTS) {
385
+ console.warn(
386
+ '[WaveformPlayerAstro] window.WaveformPlayer not found after ' +
387
+ MAX_WAIT_ATTEMPTS +
388
+ ' attempts; lazy players will not mount. Did you load ' +
389
+ "@arraypress/waveform-player's script?"
390
+ );
391
+ return;
392
+ }
393
+ setTimeout(waitForCore, 50);
394
+ })();
327
395
  }
328
396
 
329
397
  document.addEventListener('astro:page-load', mountLazy);
@@ -334,4 +402,36 @@ const initAttr = lazy ? 'data-waveform-player-lazy' : 'data-waveform-player';
334
402
  }
335
403
  })();
336
404
  </script>
405
+ ) : (
406
+ <script is:inline>
407
+ /**
408
+ * View Transitions re-init for non-lazy `<WaveformPlayer>` instances.
409
+ *
410
+ * The core library auto-initialises on `DOMContentLoaded` only.
411
+ * Under Astro View Transitions, client-side navigations swap the DOM
412
+ * without re-firing `DOMContentLoaded`, so freshly-rendered
413
+ * `[data-waveform-player]` containers would never be mounted after a
414
+ * navigation. Re-run the core initialiser on every `astro:page-load`.
415
+ *
416
+ * `WaveformPlayer.init()` is idempotent — it skips any container
417
+ * already flagged `data-waveform-initialized` (which the core sets on
418
+ * first mount) — so calling it repeatedly only mounts the new ones.
419
+ *
420
+ * Deduplicated via `window.__wfpInitBound` so multiple non-lazy
421
+ * instances bind a single shared `astro:page-load` listener that
422
+ * persists across navigations.
423
+ */
424
+ (function () {
425
+ if (window.__wfpInitBound) return;
426
+ window.__wfpInitBound = true;
427
+
428
+ document.addEventListener('astro:page-load', function () {
429
+ try {
430
+ window.WaveformPlayer && window.WaveformPlayer.init();
431
+ } catch (err) {
432
+ console.error('[WaveformPlayerAstro] init failed:', err);
433
+ }
434
+ });
435
+ })();
436
+ </script>
337
437
  )}
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
- * Every option exposed by the core `@arraypress/waveform-player` library is
7
- * surfaced here as a typed prop. The prop names match the library option
8
- * names 1:1 (camelCase) the Astro component handles the conversion to the
9
- * library's `data-*` attribute contract (kebab-case) under the hood.
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
- * Props are intentionally all optional (except `url`, which the library
12
- * requires to load audio). When a prop is omitted, the wrapper emits no
13
- * corresponding `data-*` attribute, letting the core library apply its
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
- * @see {@link https://github.com/arraypress/waveform-player} core library
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
- * - `bars` classic vertical bars from the baseline up
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
- * Browser preload hint passed through to the underlying `<audio>` element.
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
- * - `auto` — fetch the entire file ahead of time
60
- * - `metadata` — (default) fetch only enough to determine duration
61
- * - `none` — fetch nothing until play is requested
62
- */
63
- export type AudioPreload = 'auto' | 'metadata' | 'none';
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
- * `auto` picks `bottom` for the `bars` style and `center` for everything
69
- * else.
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 ButtonAlign = 'auto' | 'top' | 'center' | 'bottom';
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
- * A clickable chapter marker rendered on top of the waveform.
56
+ * Props accepted by the `<WaveformPlayer>` Astro component.
75
57
  *
76
- * Clicking a marker seeks the player to its `time`. Markers also display
77
- * their `label` as a tooltip on hover.
78
- */
79
- export interface WaveformMarker {
80
- /** Time in seconds at which the marker appears. */
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
- * - `number[]` inline array of peak amplitudes (0..1)
93
- * - `string` (.json URL) — JSON file URL the library will `fetch()`
94
- * - `string` (JSON array) — inline JSON string the library will parse
95
- * - `null` / omitted — the library decodes the audio file with the
96
- * Web Audio API at load time (slowest path)
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
- * Pre-computing peaks with `@arraypress/waveform-gen` at build time is
99
- * strongly recommended for any catalogue of more than a handful of
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 type WaveformPeaks = number[] | string | null;
103
-
104
- /**
105
- * Props accepted by the `<WaveformPlayer>` Astro component.
106
- *
107
- * Grouped to mirror the README sections:
108
- * 1. Audio source
109
- * 2. Waveform visualisation
110
- * 3. Colours
111
- * 4. Playback controls
112
- * 5. UI toggles
113
- * 6. Markers
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
- // 1. Audio source
94
+ // Audio source (Astro-required override)
122
95
  // ─────────────────────────────────────────────────────────────────────
123
96
 
124
97
  /**
125
- * Audio file URL. Required for the player to do anything useful.
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: string;
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
- // 10. Astro-specific extras
109
+ // Astro-specific extras
406
110
  // ─────────────────────────────────────────────────────────────────────
407
111
 
408
112
  /**