@arraypress/waveform-player-astro 0.1.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 ADDED
@@ -0,0 +1,44 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@arraypress/waveform-player-astro` are documented here.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] — Unreleased
9
+
10
+ Initial release.
11
+
12
+ ### Added
13
+
14
+ - `<WaveformPlayer>` Astro component wrapping every option exposed by
15
+ `@arraypress/waveform-player` 1.6.x as a typed prop:
16
+ - Audio source props (`url`, `audioMode`, `preload`)
17
+ - Waveform visualisation props (`waveformStyle`, `height`, `samples`,
18
+ `barWidth`, `barSpacing`, `waveform`)
19
+ - Colour props (`colorPreset`, `waveformColor`, `progressColor`,
20
+ `buttonColor`, `buttonHoverColor`, `textColor`,
21
+ `textSecondaryColor`, `backgroundColor`, `borderColor`)
22
+ - Playback control props (`playbackRate`, `showPlaybackSpeed`,
23
+ `playbackRates`)
24
+ - UI toggle props (`showControls`, `showInfo`, `showTime`,
25
+ `showHoverTime`, `showBPM`, `buttonAlign`)
26
+ - Marker props (`markers`, `showMarkers`)
27
+ - Content metadata props (`title`, `subtitle`, `artwork`, `album`)
28
+ - Behaviour flags (`autoplay`, `singlePlay`, `playOnSeek`,
29
+ `enableMediaSession`)
30
+ - Icon props (`playIcon`, `pauseIcon`)
31
+ - Astro-specific `lazy` prop that switches the init attribute to
32
+ `data-waveform-player-lazy` and ships a single deduplicated
33
+ `IntersectionObserver` for grids of many previews.
34
+ - Astro-specific `id`, `class`, and `style` pass-throughs.
35
+ - Public TypeScript types: `WaveformPlayerProps`, `WaveformStyle`,
36
+ `WaveformMarker`, `WaveformPeaks`, `ColorPreset`, `AudioMode`,
37
+ `AudioPreload`, `ButtonAlign`.
38
+ - Ambient declaration for `window.WaveformPlayer` to type consumer
39
+ scripts that reach for the global.
40
+ - Vitest suite (29 tests) covering attribute mapping, omission
41
+ semantics, JSON serialisation for array props, lazy-mount script
42
+ presence, and pass-through props.
43
+ - Documentation: full prop reference, setup guide, seven usage
44
+ examples (`examples/basic.astro`).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ArrayPress
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,255 @@
1
+ # @arraypress/waveform-player-astro
2
+
3
+ Astro component wrapper around [`@arraypress/waveform-player`](https://github.com/arraypress/waveform-player). Exposes every library option as a typed prop, handles `data-*` attribute serialisation for you, and ships an optional lazy-mount `IntersectionObserver` for grids of many previews.
4
+
5
+ The core library stays a zero-dependency vanilla-JS package that works anywhere a `<script>` tag does (WordPress, Shopify, raw HTML). This package adds the framework-native ergonomics Astro users expect — without touching the runtime.
6
+
7
+ ```astro
8
+ ---
9
+ import WaveformPlayer from '@arraypress/waveform-player-astro';
10
+ import '@arraypress/waveform-player/dist/waveform-player.css';
11
+ import wfpJsUrl from '@arraypress/waveform-player/dist/waveform-player.min.js?url';
12
+ ---
13
+ <script src={wfpJsUrl} is:inline></script>
14
+
15
+ <WaveformPlayer
16
+ url="/audio/track.mp3"
17
+ title="My Track"
18
+ waveformStyle="bars"
19
+ showBPM
20
+ />
21
+ ```
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install @arraypress/waveform-player-astro @arraypress/waveform-player
27
+ ```
28
+
29
+ `@arraypress/waveform-player` is a peer dependency — you bring it explicitly so you control the version.
30
+
31
+ ## Setup
32
+
33
+ The wrapper does **not** load the core library's JS or CSS for you. This is deliberate: you might want a CDN, a self-hosted asset, or the bundled npm path. Load both once in your root layout:
34
+
35
+ ```astro
36
+ ---
37
+ // src/layouts/Layout.astro
38
+ import '@arraypress/waveform-player/dist/waveform-player.css';
39
+ import wfpJsUrl from '@arraypress/waveform-player/dist/waveform-player.min.js?url';
40
+ ---
41
+ <html>
42
+ <head>
43
+ <script src={wfpJsUrl} is:inline></script>
44
+ </head>
45
+ <body><slot /></body>
46
+ </html>
47
+ ```
48
+
49
+ Then `<WaveformPlayer>` works on any page.
50
+
51
+ ## Usage
52
+
53
+ ### Minimal
54
+
55
+ ```astro
56
+ <WaveformPlayer url="/audio/track.mp3" />
57
+ ```
58
+
59
+ ### With metadata + a chosen style
60
+
61
+ ```astro
62
+ <WaveformPlayer
63
+ url="/audio/track.mp3"
64
+ title="Midnight Dreams"
65
+ subtitle="The Wavelength"
66
+ artwork="/img/cover.jpg"
67
+ waveformStyle="bars"
68
+ barWidth={3}
69
+ barSpacing={1}
70
+ height={80}
71
+ />
72
+ ```
73
+
74
+ ### Pre-computed peaks (recommended for catalogues)
75
+
76
+ ```astro
77
+ <WaveformPlayer
78
+ url="/audio/track.mp3"
79
+ waveform="/peaks/track.json"
80
+ />
81
+ ```
82
+
83
+ Generate the JSON at build time with [`@arraypress/waveform-gen`](https://github.com/arraypress/waveform-gen). Removes a full Web Audio decode from the runtime render path.
84
+
85
+ ### Chapter markers
86
+
87
+ ```astro
88
+ <WaveformPlayer
89
+ url="/audio/podcast.mp3"
90
+ markers={[
91
+ { time: 0, label: 'Intro' },
92
+ { time: 60, label: 'Main topic', color: '#a855f7' },
93
+ { time: 600, label: 'Q&A' },
94
+ ]}
95
+ />
96
+ ```
97
+
98
+ ### Lazy mounting (large grids)
99
+
100
+ Pass `lazy` and the wrapper switches the init attribute to `data-waveform-player-lazy`, then installs a single shared `IntersectionObserver` that promotes each mount when it crosses the viewport (with 200 px of buffer so audio is decoded before the user actually sees the player).
101
+
102
+ ```astro
103
+ {previews.map((p) => (
104
+ <WaveformPlayer url={p.url} title={p.title} lazy />
105
+ ))}
106
+ ```
107
+
108
+ The observer is installed at most once per page (`window.__wfpLazyMountBound` flag), and re-fires on Astro's `astro:page-load` event so cross-page navigations pick up new mounts.
109
+
110
+ ### External audio mode
111
+
112
+ For setups where one audio element (e.g. [`@arraypress/waveform-bar`](https://github.com/arraypress/waveform-bar)) drives many visual surfaces:
113
+
114
+ ```astro
115
+ <WaveformPlayer
116
+ url={track.url}
117
+ audioMode="external"
118
+ waveformStyle="seekbar"
119
+ showInfo={false}
120
+ />
121
+ ```
122
+
123
+ The player dispatches `waveformplayer:request-play | request-pause | request-seek` custom events instead of touching audio itself. Drive the visualisation from your controller via the player instance's `setProgress(currentTime, duration)` and `setPlayingState(playing)` methods.
124
+
125
+ ## Props
126
+
127
+ Every prop maps 1:1 to an option on the core library. Omitting a prop emits no `data-*` attribute and lets the library apply its own default.
128
+
129
+ ### Audio source
130
+
131
+ | Prop | Type | Default | Description |
132
+ | ----------- | ------------------------------------- | ------------ | ----------- |
133
+ | `url` | `string` *(required)* | — | Audio file URL. |
134
+ | `audioMode` | `'self' \| 'external'` | `'self'` | `'external'` renders waveform only and emits request events. |
135
+ | `preload` | `'auto' \| 'metadata' \| 'none'` | `'metadata'` | Browser preload hint. |
136
+
137
+ ### Waveform visualisation
138
+
139
+ | Prop | Type | Default | Description |
140
+ | --------------- | ------------------------------------------------------------- | ---------- | ----------- |
141
+ | `waveformStyle` | `'bars' \| 'mirror' \| 'line' \| 'blocks' \| 'dots' \| 'seekbar'` | `'mirror'` | Visual style. |
142
+ | `height` | `number` | `60` | Canvas height in pixels. |
143
+ | `samples` | `number` | `200` | Number of waveform peaks to render. |
144
+ | `barWidth` | `number` | style-dependent | Width of each bar/block. |
145
+ | `barSpacing` | `number` | style-dependent | Gap between bars. |
146
+ | `waveform` | `number[] \| string` | — | Pre-computed peaks (array, JSON URL, or inline JSON). |
147
+
148
+ ### Colours
149
+
150
+ All optional. `colorPreset` controls the auto theme, and any individual colour wins over the preset.
151
+
152
+ | Prop | Type | Description |
153
+ | --------------------- | ------------------------------- | ----------- |
154
+ | `colorPreset` | `'dark' \| 'light' \| null` | `null` (default) auto-detects. |
155
+ | `waveformColor` | `string` | Unplayed peak colour. |
156
+ | `progressColor` | `string` | Played-through peak colour. |
157
+ | `buttonColor` | `string` | Play-button colour. |
158
+ | `buttonHoverColor` | `string` | Play-button hover colour. |
159
+ | `textColor` | `string` | Primary text colour. |
160
+ | `textSecondaryColor` | `string` | Secondary text colour. |
161
+ | `backgroundColor` | `string` | Reserved. |
162
+ | `borderColor` | `string` | Reserved. |
163
+
164
+ ### Playback controls
165
+
166
+ | Prop | Type | Default | Description |
167
+ | ------------------- | ----------- | ------------------------------------ | ----------- |
168
+ | `playbackRate` | `number` | `1` | Initial speed. |
169
+ | `showPlaybackSpeed` | `boolean` | `false` | Show the speed menu. |
170
+ | `playbackRates` | `number[]` | `[0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]` | Speeds offered in the menu. |
171
+
172
+ ### UI toggles
173
+
174
+ | Prop | Type | Default | Description |
175
+ | --------------- | ------------------------------------------ | -------- | ----------- |
176
+ | `showControls` | `boolean` | `true` | Show the play/pause button. |
177
+ | `showInfo` | `boolean` | `true` | Show the info bar (title, time, etc.). |
178
+ | `showTime` | `boolean` | `true` | Show current / total time. |
179
+ | `showHoverTime` | `boolean` | `false` | Reserved. |
180
+ | `showBPM` | `boolean` | `false` | Detect and display BPM. |
181
+ | `buttonAlign` | `'auto' \| 'top' \| 'center' \| 'bottom'` | `'auto'` | Play-button vertical alignment. |
182
+
183
+ ### Markers
184
+
185
+ | Prop | Type | Default | Description |
186
+ | ------------- | ----------------------------------------------------------------- | ------- | ----------- |
187
+ | `markers` | `Array<{ time: number; label: string; color?: string }>` | — | Clickable seek-point markers. |
188
+ | `showMarkers` | `boolean` | `true` | Render markers (false to hide without losing data). |
189
+
190
+ ### Content metadata
191
+
192
+ | Prop | Type | Description |
193
+ | ---------- | -------- | ----------- |
194
+ | `title` | `string` | Track title (defaults to a prettified filename). |
195
+ | `subtitle` | `string` | Subtitle / artist. |
196
+ | `artwork` | `string` | Thumbnail image URL. |
197
+ | `album` | `string` | Album name (Media Session API). |
198
+
199
+ ### Behaviour
200
+
201
+ | Prop | Type | Default | Description |
202
+ | -------------------- | --------- | ------- | ----------- |
203
+ | `autoplay` | `boolean` | `false` | Start as soon as metadata loads. |
204
+ | `singlePlay` | `boolean` | `true` | Pause other players on the page when this one starts. |
205
+ | `playOnSeek` | `boolean` | `true` | Resume on seek if paused. |
206
+ | `enableMediaSession` | `boolean` | `true` | Wire up the Media Session API. |
207
+
208
+ ### Icons
209
+
210
+ | Prop | Type | Description |
211
+ | ----------- | -------- | ----------- |
212
+ | `playIcon` | `string` | Inline HTML / SVG for the play button (injected raw — trust your source). |
213
+ | `pauseIcon` | `string` | Inline HTML / SVG for the pause button. |
214
+
215
+ ### Astro-specific
216
+
217
+ | Prop | Type | Default | Description |
218
+ | ------- | --------- | ------- | ----------- |
219
+ | `lazy` | `boolean` | `false` | Defer mount until viewport entry. |
220
+ | `id` | `string` | — | Forwarded to the container element. |
221
+ | `class` | `string` | — | Appended to the container's classes (`wfp-host` is always present). |
222
+ | `style` | `string` | — | Inline style on the container. |
223
+
224
+ ## TypeScript
225
+
226
+ Importing the types directly:
227
+
228
+ ```ts
229
+ import type {
230
+ WaveformPlayerProps,
231
+ WaveformStyle,
232
+ WaveformMarker,
233
+ WaveformPeaks,
234
+ ColorPreset,
235
+ AudioMode,
236
+ AudioPreload,
237
+ ButtonAlign,
238
+ } from '@arraypress/waveform-player-astro';
239
+ ```
240
+
241
+ The package also ships an ambient declaration for `window.WaveformPlayer` so consumers reaching for it from their own scripts get autocomplete.
242
+
243
+ ## Testing
244
+
245
+ ```bash
246
+ npm test # one-shot
247
+ npm run test:watch
248
+ npm run typecheck
249
+ ```
250
+
251
+ Tests use Astro's official `experimental_AstroContainer` API to render the component to a string and assert on the resulting HTML. No browser required.
252
+
253
+ ## License
254
+
255
+ MIT © ArrayPress
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@arraypress/waveform-player-astro",
3
+ "version": "0.1.0",
4
+ "description": "Astro component wrapper for @arraypress/waveform-player — typed props for every option, lazy-mount support, framework-idiomatic ergonomics.",
5
+ "type": "module",
6
+ "files": [
7
+ "src/",
8
+ "README.md",
9
+ "CHANGELOG.md",
10
+ "LICENSE"
11
+ ],
12
+ "exports": {
13
+ ".": {
14
+ "types": "./src/index.ts",
15
+ "default": "./src/index.ts"
16
+ },
17
+ "./WaveformPlayer.astro": "./src/WaveformPlayer.astro",
18
+ "./types": {
19
+ "types": "./src/types.ts",
20
+ "default": "./src/types.ts"
21
+ },
22
+ "./package.json": "./package.json"
23
+ },
24
+ "keywords": [
25
+ "astro",
26
+ "astro-component",
27
+ "audio",
28
+ "player",
29
+ "waveform",
30
+ "visualization",
31
+ "music",
32
+ "sound",
33
+ "html5",
34
+ "web-audio",
35
+ "media-session",
36
+ "audio-player",
37
+ "waveform-visualization",
38
+ "podcast-player",
39
+ "audio-visualization",
40
+ "bpm-detection",
41
+ "withastro"
42
+ ],
43
+ "author": "ArrayPress",
44
+ "license": "MIT",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/arraypress/waveform-player-astro.git"
48
+ },
49
+ "bugs": {
50
+ "url": "https://github.com/arraypress/waveform-player-astro/issues"
51
+ },
52
+ "homepage": "https://waveformplayer.com",
53
+ "funding": {
54
+ "type": "github",
55
+ "url": "https://github.com/sponsors/arraypress"
56
+ },
57
+ "engines": {
58
+ "node": ">=18.0.0"
59
+ },
60
+ "publishConfig": {
61
+ "access": "public",
62
+ "registry": "https://registry.npmjs.org/"
63
+ },
64
+ "peerDependencies": {
65
+ "@arraypress/waveform-player": "^1.6.0",
66
+ "astro": "^6.0.0"
67
+ },
68
+ "scripts": {
69
+ "test": "vitest run",
70
+ "test:watch": "vitest",
71
+ "typecheck": "tsc --noEmit"
72
+ },
73
+ "devDependencies": {
74
+ "@arraypress/waveform-player": "^1.6.0",
75
+ "astro": "^6.3.7",
76
+ "typescript": "^6.0.3",
77
+ "vitest": "^4.1.7"
78
+ }
79
+ }
@@ -0,0 +1,337 @@
1
+ ---
2
+ /**
3
+ * WaveformPlayer.astro
4
+ * --------------------
5
+ *
6
+ * Astro component wrapper around `@arraypress/waveform-player`. Exposes
7
+ * every library option as a typed prop and emits the equivalent
8
+ * `data-*` attribute on the mount container. The library's own
9
+ * auto-initialiser (registered globally when its script loads) picks up
10
+ * the container and renders the player into it.
11
+ *
12
+ * ## Why a wrapper?
13
+ *
14
+ * The core library is intentionally framework-agnostic vanilla JS. That
15
+ * works great everywhere (WordPress, Shopify, plain HTML) but means
16
+ * Astro users have to hand-stringify every option as a `data-*`
17
+ * attribute:
18
+ *
19
+ * ```astro
20
+ * <div data-waveform-player
21
+ * data-url={url}
22
+ * data-waveform-style={style}
23
+ * data-bar-width={String(2)}
24
+ * data-markers={JSON.stringify(markers)}
25
+ * data-show-bpm="true">
26
+ * </div>
27
+ * ```
28
+ *
29
+ * This wrapper turns that into:
30
+ *
31
+ * ```astro
32
+ * <WaveformPlayer
33
+ * url={url}
34
+ * waveformStyle={style}
35
+ * barWidth={2}
36
+ * markers={markers}
37
+ * showBPM
38
+ * />
39
+ * ```
40
+ *
41
+ * Typed props, correct stringification, omission semantics that let the
42
+ * library's defaults shine through.
43
+ *
44
+ * ## Setup
45
+ *
46
+ * Load the core library's JS + CSS once in your layout (the wrapper
47
+ * intentionally does NOT do this for you — you may want a CDN, a
48
+ * self-hosted asset, or the bundled npm path):
49
+ *
50
+ * ```astro
51
+ * ---
52
+ * import '@arraypress/waveform-player/dist/waveform-player.css';
53
+ * import wfpJsUrl from '@arraypress/waveform-player/dist/waveform-player.min.js?url';
54
+ * ---
55
+ * <script src={wfpJsUrl} is:inline></script>
56
+ * ```
57
+ *
58
+ * Then drop `<WaveformPlayer>` anywhere.
59
+ *
60
+ * ## Lazy mounting
61
+ *
62
+ * Passing `lazy` switches the init attribute from `data-waveform-player`
63
+ * to `data-waveform-player-lazy` and ships a one-time
64
+ * `IntersectionObserver` script that promotes lazy mounts when they
65
+ * approach the viewport. The script self-deduplicates via
66
+ * `window.__wfpLazyMountBound` so multiple `<WaveformPlayer lazy>`
67
+ * instances on the same page reuse a single observer.
68
+ *
69
+ * @example Basic
70
+ * <WaveformPlayer url="/audio/track.mp3" title="My Track" />
71
+ *
72
+ * @example Pre-computed peaks (recommended for catalogues)
73
+ * <WaveformPlayer
74
+ * url="/audio/track.mp3"
75
+ * waveform="/peaks/track.json"
76
+ * waveformStyle="bars"
77
+ * barWidth={3}
78
+ * showBPM
79
+ * />
80
+ *
81
+ * @example External mode (paired with `@arraypress/waveform-bar`)
82
+ * <WaveformPlayer
83
+ * url={track.url}
84
+ * audioMode="external"
85
+ * showInfo={false}
86
+ * waveformStyle="seekbar"
87
+ * />
88
+ *
89
+ * @example Lazy-mount grid item
90
+ * <WaveformPlayer url={preview.url} lazy />
91
+ */
92
+
93
+ import type { WaveformPlayerProps } from './types';
94
+
95
+ const {
96
+ // Audio
97
+ url,
98
+ audioMode,
99
+ preload,
100
+ // Waveform visualisation
101
+ waveformStyle,
102
+ height,
103
+ samples,
104
+ barWidth,
105
+ barSpacing,
106
+ waveform,
107
+ // Colours
108
+ colorPreset,
109
+ waveformColor,
110
+ progressColor,
111
+ buttonColor,
112
+ buttonHoverColor,
113
+ textColor,
114
+ textSecondaryColor,
115
+ backgroundColor,
116
+ borderColor,
117
+ // Playback controls
118
+ playbackRate,
119
+ showPlaybackSpeed,
120
+ playbackRates,
121
+ // UI toggles
122
+ showControls,
123
+ showInfo,
124
+ showTime,
125
+ showHoverTime,
126
+ showBPM,
127
+ buttonAlign,
128
+ // Markers
129
+ markers,
130
+ showMarkers,
131
+ // Metadata
132
+ title,
133
+ subtitle,
134
+ artwork,
135
+ album,
136
+ // Behaviour
137
+ autoplay,
138
+ singlePlay,
139
+ playOnSeek,
140
+ enableMediaSession,
141
+ // Icons
142
+ playIcon,
143
+ pauseIcon,
144
+ // Astro extras
145
+ lazy = false,
146
+ id,
147
+ class: className,
148
+ style,
149
+ } = Astro.props as WaveformPlayerProps;
150
+
151
+ /**
152
+ * Build the data-attribute map programmatically so an omitted prop
153
+ * never emits an attribute. This is the contract that lets the library
154
+ * apply its own defaults when a prop is left out, and keeps the
155
+ * resulting DOM tidy.
156
+ *
157
+ * @type {Record<string, string>}
158
+ */
159
+ const data: Record<string, string> = {};
160
+
161
+ /**
162
+ * Helper — set a string attr only when the value is non-null/non-undefined.
163
+ * Empty strings are preserved (the library treats them as "set but blank").
164
+ */
165
+ const setStr = (key: string, value: string | undefined | null): void => {
166
+ if (value === undefined || value === null) return;
167
+ data[key] = value;
168
+ };
169
+
170
+ /**
171
+ * Helper — set a numeric attr only when the value is a finite number.
172
+ */
173
+ const setNum = (key: string, value: number | undefined | null): void => {
174
+ if (value === undefined || value === null) return;
175
+ if (typeof value !== 'number' || !Number.isFinite(value)) return;
176
+ data[key] = String(value);
177
+ };
178
+
179
+ /**
180
+ * Helper — set a boolean attr as the string `'true'` / `'false'`.
181
+ * Required because the library compares against `=== 'true'`.
182
+ */
183
+ const setBool = (key: string, value: boolean | undefined | null): void => {
184
+ if (value === undefined || value === null) return;
185
+ data[key] = value ? 'true' : 'false';
186
+ };
187
+
188
+ // ─── Audio source ────────────────────────────────────────────────────────
189
+ setStr('data-url', url);
190
+ setStr('data-audio-mode', audioMode ?? null);
191
+ setStr('data-preload', preload ?? null);
192
+
193
+ // ─── Waveform visualisation ──────────────────────────────────────────────
194
+ setStr('data-waveform-style', waveformStyle ?? null);
195
+ setNum('data-height', height);
196
+ setNum('data-samples', samples);
197
+ setNum('data-bar-width', barWidth);
198
+ setNum('data-bar-spacing', barSpacing);
199
+
200
+ // `waveform` accepts an array (JSON-stringify it) or a string (URL/inline
201
+ // JSON — pass through). The library handles both forms.
202
+ if (Array.isArray(waveform)) {
203
+ data['data-waveform'] = JSON.stringify(waveform);
204
+ } else if (typeof waveform === 'string') {
205
+ data['data-waveform'] = waveform;
206
+ }
207
+
208
+ // ─── Colours ─────────────────────────────────────────────────────────────
209
+ // `colorPreset` is `'dark' | 'light' | null` — only emit when non-null.
210
+ if (colorPreset === 'dark' || colorPreset === 'light') {
211
+ data['data-color-preset'] = colorPreset;
212
+ }
213
+ setStr('data-waveform-color', waveformColor ?? null);
214
+ setStr('data-progress-color', progressColor ?? null);
215
+ setStr('data-button-color', buttonColor ?? null);
216
+ setStr('data-button-hover-color', buttonHoverColor ?? null);
217
+ setStr('data-text-color', textColor ?? null);
218
+ setStr('data-text-secondary-color', textSecondaryColor ?? null);
219
+ setStr('data-background-color', backgroundColor ?? null);
220
+ setStr('data-border-color', borderColor ?? null);
221
+
222
+ // ─── Playback controls ───────────────────────────────────────────────────
223
+ setNum('data-playback-rate', playbackRate);
224
+ setBool('data-show-playback-speed', showPlaybackSpeed);
225
+ if (Array.isArray(playbackRates) && playbackRates.length > 0) {
226
+ data['data-playback-rates'] = JSON.stringify(playbackRates);
227
+ }
228
+
229
+ // ─── UI toggles ──────────────────────────────────────────────────────────
230
+ setBool('data-show-controls', showControls);
231
+ setBool('data-show-info', showInfo);
232
+ setBool('data-show-time', showTime);
233
+ setBool('data-show-hover-time', showHoverTime);
234
+ // The library's data-attribute parser reads `data-show-bpm` (lowercased
235
+ // `bpm`), NOT `data-show-b-p-m`. Get this wrong and BPM never displays.
236
+ setBool('data-show-bpm', showBPM);
237
+ setStr('data-button-align', buttonAlign ?? null);
238
+
239
+ // ─── Markers ─────────────────────────────────────────────────────────────
240
+ if (Array.isArray(markers) && markers.length > 0) {
241
+ data['data-markers'] = JSON.stringify(markers);
242
+ }
243
+ setBool('data-show-markers', showMarkers);
244
+
245
+ // ─── Metadata ────────────────────────────────────────────────────────────
246
+ setStr('data-title', title ?? null);
247
+ setStr('data-subtitle', subtitle ?? null);
248
+ setStr('data-artwork', artwork ?? null);
249
+ setStr('data-album', album ?? null);
250
+
251
+ // ─── Behaviour ───────────────────────────────────────────────────────────
252
+ setBool('data-autoplay', autoplay);
253
+ setBool('data-single-play', singlePlay);
254
+ setBool('data-play-on-seek', playOnSeek);
255
+ setBool('data-enable-media-session', enableMediaSession);
256
+
257
+ // ─── Icons ───────────────────────────────────────────────────────────────
258
+ // Pass icon markup verbatim. Consumers are responsible for ensuring
259
+ // the HTML they hand us is safe to inject.
260
+ setStr('data-play-icon', playIcon ?? null);
261
+ setStr('data-pause-icon', pauseIcon ?? null);
262
+
263
+ /**
264
+ * The init attribute — the library auto-init scans for
265
+ * `[data-waveform-player]`. For lazy mounting we emit
266
+ * `[data-waveform-player-lazy]` instead and let our companion script
267
+ * promote it on viewport entry.
268
+ */
269
+ const initAttr = lazy ? 'data-waveform-player-lazy' : 'data-waveform-player';
270
+ ---
271
+
272
+ <div
273
+ {...{ [initAttr]: '' }}
274
+ {...data}
275
+ id={id}
276
+ class:list={['wfp-host', className]}
277
+ style={style}
278
+ ></div>
279
+
280
+ {lazy && (
281
+ <script is:inline>
282
+ /**
283
+ * Lazy-mount watcher for `<WaveformPlayer lazy>` instances.
284
+ *
285
+ * Installs a single `IntersectionObserver` (deduplicated via a
286
+ * `window` flag) that promotes `data-waveform-player-lazy` to
287
+ * `data-waveform-player` once an element approaches the viewport,
288
+ * then triggers `WaveformPlayer.init()` to mount it.
289
+ *
290
+ * The 200 px `rootMargin` gives the browser headroom to fetch and
291
+ * decode the audio so the player is ready by the time the user
292
+ * scrolls it into actual view — eliminates the "Unable to load
293
+ * audio" stutter you get when 15+ players try to decode at once
294
+ * on page load.
295
+ *
296
+ * Re-runs on Astro `astro:page-load` so cross-page navigations
297
+ * pick up newly-rendered lazy mounts.
298
+ */
299
+ (function () {
300
+ if (window.__wfpLazyMountBound) return;
301
+ window.__wfpLazyMountBound = true;
302
+
303
+ function mountLazy() {
304
+ if (!window.WaveformPlayer) return;
305
+ const targets = document.querySelectorAll('[data-waveform-player-lazy]');
306
+ if (!targets.length) return;
307
+
308
+ const io = new IntersectionObserver(
309
+ (entries) => {
310
+ entries.forEach((entry) => {
311
+ if (!entry.isIntersecting) return;
312
+ const el = entry.target;
313
+ el.removeAttribute('data-waveform-player-lazy');
314
+ el.setAttribute('data-waveform-player', '');
315
+ io.unobserve(el);
316
+ try {
317
+ window.WaveformPlayer.init();
318
+ } catch (err) {
319
+ console.error('[waveform-player-astro] init failed:', err);
320
+ }
321
+ });
322
+ },
323
+ { rootMargin: '200px 0px 200px 0px', threshold: 0.01 }
324
+ );
325
+
326
+ targets.forEach((target) => io.observe(target));
327
+ }
328
+
329
+ document.addEventListener('astro:page-load', mountLazy);
330
+ if (document.readyState !== 'loading') {
331
+ mountLazy();
332
+ } else {
333
+ document.addEventListener('DOMContentLoaded', mountLazy);
334
+ }
335
+ })();
336
+ </script>
337
+ )}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @module astro-shim
3
+ * @description
4
+ * Ambient declaration for `*.astro` modules so plain `tsc --noEmit`
5
+ * can typecheck `index.ts` and the test suite without choking on
6
+ * `.astro` extensions.
7
+ *
8
+ * At runtime, Astro's Vite plugin resolves these imports to real
9
+ * component factories. For type-check-only contexts (like our
10
+ * `typecheck` npm script) we model them as opaque components that
11
+ * accept any props.
12
+ *
13
+ * In a consumer's Astro project, this shim is harmless — Astro's own
14
+ * `astro/client` types win because they're loaded via the project's
15
+ * `tsconfig` extends chain.
16
+ */
17
+ declare module '*.astro' {
18
+ const component: (_props: Record<string, unknown>) => unknown;
19
+ export default component;
20
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @module globals
3
+ * @description
4
+ * Ambient declarations for the globals the core `@arraypress/waveform-player`
5
+ * library installs on `window` when its IIFE script runs.
6
+ *
7
+ * Consumers don't need to import this file — TypeScript picks it up
8
+ * automatically via the package's exported types. The shim lets you do
9
+ * things like:
10
+ *
11
+ * ```ts
12
+ * window.WaveformPlayer?.init();
13
+ * const player = window.WaveformPlayer?.getInstance('my-player-id');
14
+ * ```
15
+ *
16
+ * without TypeScript complaining about `window.WaveformPlayer` being
17
+ * `any` / undefined.
18
+ *
19
+ * The shape is intentionally minimal — just the static surface a typical
20
+ * consumer touches. The full instance API lives in the core library's
21
+ * own types (when those ship) or via `// @ts-expect-error` for now.
22
+ */
23
+
24
+ /**
25
+ * Static / global surface of `window.WaveformPlayer`.
26
+ *
27
+ * The core library exposes its class constructor directly on `window`;
28
+ * static helpers like `init()`, `getInstance()`, `getAllInstances()`,
29
+ * and `destroyAll()` hang off the same object.
30
+ */
31
+ interface WaveformPlayerGlobal {
32
+ /**
33
+ * Manually scan the document for any `[data-waveform-player]`
34
+ * elements and mount players into them. Called automatically on
35
+ * `DOMContentLoaded`; call again after dynamically inserting
36
+ * markup.
37
+ */
38
+ init(): void;
39
+
40
+ /**
41
+ * Retrieve a player instance by element id, DOM element, or
42
+ * internally-generated player id.
43
+ */
44
+ getInstance(idOrElement: string | HTMLElement): unknown | undefined;
45
+
46
+ /** Return every active player instance. */
47
+ getAllInstances(): unknown[];
48
+
49
+ /** Destroy every active player instance on the page. */
50
+ destroyAll(): void;
51
+
52
+ /**
53
+ * Pre-compute peaks from an audio URL using the Web Audio API.
54
+ * Useful for ad-hoc generation when you don't have a build-time
55
+ * pipeline.
56
+ */
57
+ generateWaveformData(url: string, samples?: number): Promise<number[]>;
58
+
59
+ /** Allow `new WaveformPlayer(...)` from user code. */
60
+ new (container: string | HTMLElement, options?: Record<string, unknown>): unknown;
61
+ }
62
+
63
+ declare global {
64
+ interface Window {
65
+ /**
66
+ * Installed by `@arraypress/waveform-player`'s IIFE script.
67
+ * `undefined` until that script loads.
68
+ */
69
+ WaveformPlayer?: WaveformPlayerGlobal;
70
+
71
+ /**
72
+ * Internal — set by `<WaveformPlayer lazy>`'s inline mount
73
+ * script to deduplicate the `IntersectionObserver`. Do not
74
+ * rely on this; it is an implementation detail.
75
+ *
76
+ * @internal
77
+ */
78
+ __wfpLazyMountBound?: boolean;
79
+ }
80
+ }
81
+
82
+ export {};
package/src/index.ts ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @module @arraypress/waveform-player-astro
3
+ * @description
4
+ * Public entry point for the Astro wrapper around
5
+ * `@arraypress/waveform-player`.
6
+ *
7
+ * ## Importing the component
8
+ *
9
+ * ```astro
10
+ * ---
11
+ * import WaveformPlayer from '@arraypress/waveform-player-astro';
12
+ * // or, if you prefer the explicit path:
13
+ * import WaveformPlayer from '@arraypress/waveform-player-astro/WaveformPlayer.astro';
14
+ * ---
15
+ * <WaveformPlayer url="/audio/track.mp3" />
16
+ * ```
17
+ *
18
+ * ## Importing the types
19
+ *
20
+ * ```ts
21
+ * import type {
22
+ * WaveformPlayerProps,
23
+ * WaveformStyle,
24
+ * WaveformMarker,
25
+ * } from '@arraypress/waveform-player-astro';
26
+ * ```
27
+ *
28
+ * @see {@link ./WaveformPlayer.astro} for the component implementation
29
+ * @see {@link ./types.ts} for the full prop interface
30
+ */
31
+
32
+ import WaveformPlayer from './WaveformPlayer.astro';
33
+
34
+ export { WaveformPlayer };
35
+ export default WaveformPlayer;
36
+
37
+ export type {
38
+ WaveformPlayerProps,
39
+ WaveformStyle,
40
+ WaveformMarker,
41
+ WaveformPeaks,
42
+ ColorPreset,
43
+ AudioMode,
44
+ AudioPreload,
45
+ ButtonAlign,
46
+ } from './types';
package/src/types.ts ADDED
@@ -0,0 +1,442 @@
1
+ /**
2
+ * @module types
3
+ * @description
4
+ * Public TypeScript types for `@arraypress/waveform-player-astro`.
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.
10
+ *
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.
15
+ *
16
+ * @see {@link https://github.com/arraypress/waveform-player} — core library
17
+ */
18
+
19
+ /**
20
+ * Visual style of the waveform rendering.
21
+ *
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`.
40
+ */
41
+ export type ColorPreset = 'dark' | 'light' | null;
42
+
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';
55
+
56
+ /**
57
+ * Browser preload hint passed through to the underlying `<audio>` element.
58
+ *
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.
67
+ *
68
+ * `auto` picks `bottom` for the `bars` style and `center` for everything
69
+ * else.
70
+ */
71
+ export type ButtonAlign = 'auto' | 'top' | 'center' | 'bottom';
72
+
73
+ /**
74
+ * A clickable chapter marker rendered on top of the waveform.
75
+ *
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.
91
+ *
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)
97
+ *
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.
101
+ */
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 {
120
+ // ─────────────────────────────────────────────────────────────────────
121
+ // 1. Audio source
122
+ // ─────────────────────────────────────────────────────────────────────
123
+
124
+ /**
125
+ * Audio file URL. Required for the player to do anything useful.
126
+ *
127
+ * Supported formats are whatever the user's browser supports — MP3,
128
+ * WAV, OGG, AAC, etc. CORS headers must allow cross-origin loads if
129
+ * hosted on a different domain.
130
+ */
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;
403
+
404
+ // ─────────────────────────────────────────────────────────────────────
405
+ // 10. Astro-specific extras
406
+ // ─────────────────────────────────────────────────────────────────────
407
+
408
+ /**
409
+ * Defer initialisation until the player scrolls into view.
410
+ *
411
+ * When `true`, the component emits `data-waveform-player-lazy` and
412
+ * the bundled `IntersectionObserver` script promotes it to
413
+ * `data-waveform-player` once it crosses the viewport (with a 200px
414
+ * buffer so audio is decoded ahead of time).
415
+ *
416
+ * Use this on grids of many previews to avoid spawning N concurrent
417
+ * `fetch()` + `decodeAudioData` jobs on page load.
418
+ *
419
+ * @default false
420
+ */
421
+ lazy?: boolean;
422
+
423
+ /**
424
+ * DOM id forwarded to the player container. Useful for targeting
425
+ * the player from external scripts via `WaveformPlayer.getInstance(id)`.
426
+ */
427
+ id?: string;
428
+
429
+ /**
430
+ * Extra class names appended to the container's `class` attribute.
431
+ *
432
+ * The base class `wfp-host` is always applied so the package's CSS
433
+ * (or your own overrides) can target the wrapper.
434
+ */
435
+ class?: string;
436
+
437
+ /**
438
+ * Inline style passed through to the container. Useful for setting
439
+ * `min-height` to reserve layout space before the waveform draws.
440
+ */
441
+ style?: string;
442
+ }