@juandinella/audio-bands 0.5.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -3
- package/dist/{chunk-BA6JOXL4.js → chunk-EGVVFGLZ.js} +83 -6
- package/dist/chunk-EGVVFGLZ.js.map +1 -0
- package/dist/core-entry.cjs +82 -5
- package/dist/core-entry.cjs.map +1 -1
- package/dist/core-entry.d.cts +5 -0
- package/dist/core-entry.d.ts +5 -0
- package/dist/core-entry.js +1 -1
- package/dist/index.cjs +82 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/react-entry.cjs +114 -26
- package/dist/react-entry.cjs.map +1 -1
- package/dist/react-entry.js +33 -22
- package/dist/react-entry.js.map +1 -1
- package/package.json +11 -2
- package/dist/chunk-BA6JOXL4.js.map +0 -1
package/README.md
CHANGED
|
@@ -25,6 +25,8 @@ The intended center of the API is `snapshot()`: one call, one coherent analysis
|
|
|
25
25
|
npm install @juandinella/audio-bands
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
For repository development, `npm test` covers the mocked unit suite and `npm run test:browser` runs a small browser smoke test against the built bundles.
|
|
29
|
+
|
|
28
30
|
### Entry points
|
|
29
31
|
|
|
30
32
|
- `@juandinella/audio-bands`: main framework-agnostic export
|
|
@@ -33,6 +35,8 @@ npm install @juandinella/audio-bands
|
|
|
33
35
|
|
|
34
36
|
If you use the React hook, install `react` as well.
|
|
35
37
|
|
|
38
|
+
Minimal reference examples live in [`examples/README.md`](./examples/README.md). The Vite app in `examples/src/App.tsx` is a showcase demo, while `examples/snippets/` contains the smallest copyable integrations.
|
|
39
|
+
|
|
36
40
|
## Usage
|
|
37
41
|
|
|
38
42
|
### Vanilla JS
|
|
@@ -192,7 +196,7 @@ new AudioBands(options?: AudioBandsOptions)
|
|
|
192
196
|
|
|
193
197
|
| Method | Description |
|
|
194
198
|
| ----------------------- | ----------- |
|
|
195
|
-
| `load(url)` | Load a track
|
|
199
|
+
| `load(url)` | Load a track, connect it to the analyser, and resolve when the media is ready. Rejects with `AudioBandsError` on load failure. |
|
|
196
200
|
| `play()` | Start playback for the current track. Rejects with `AudioBandsError` on failure. |
|
|
197
201
|
| `pause()` | Pause the current track. |
|
|
198
202
|
| `setLoop(loop)` | Set whether the current and future loaded tracks should loop. |
|
|
@@ -285,9 +289,10 @@ type AudioBandsState = {
|
|
|
285
289
|
## Notes
|
|
286
290
|
|
|
287
291
|
- `AudioContext` is created lazily on the first call to `load()` or `enableMic()`.
|
|
288
|
-
- `load()` prepares the current track but does not start playback.
|
|
292
|
+
- `load()` prepares the current track but does not start playback. It resolves only after the media is ready enough for duration/seek reads to be meaningful, then you can call `play()` or `togglePlayPause()`.
|
|
289
293
|
- `togglePlayPause()` follows the same playback error contract as `play()`: if toggling into play fails, the returned promise rejects.
|
|
290
|
-
- `hasTrack` means
|
|
294
|
+
- `hasTrack` means the current track finished loading and is ready on the instance. It can still be `true` if `play()` fails later due to autoplay policy or another playback error.
|
|
295
|
+
- `isPlaying` follows the underlying media element events, so it falls back to `false` when the track pauses or reaches `ended`.
|
|
291
296
|
- `loadError` stores track loading failures only.
|
|
292
297
|
- `playbackError` stores playback failures for the current track, such as autoplay-policy rejections.
|
|
293
298
|
- In the React hook, changing `music`, `mic`, `bandRanges`, or `customBands` recreates the underlying `AudioBands` instance.
|
|
@@ -300,6 +305,11 @@ type AudioBandsState = {
|
|
|
300
305
|
- The default `bass` / `mid` / `high` labels are convenience names for analyser regions, not fixed Hz buckets.
|
|
301
306
|
- `overall` is intended as a simple UI summary, not as an acoustically weighted loudness value.
|
|
302
307
|
|
|
308
|
+
## Development
|
|
309
|
+
|
|
310
|
+
- `npm test` builds the package and runs the unit suite.
|
|
311
|
+
- Releases are published from GitHub Actions when a `v*` tag is pushed.
|
|
312
|
+
|
|
303
313
|
## License
|
|
304
314
|
|
|
305
315
|
MIT
|
|
@@ -128,6 +128,9 @@ var AudioBands = class {
|
|
|
128
128
|
this.musicSource = null;
|
|
129
129
|
this.micSource = null;
|
|
130
130
|
this.micStream = null;
|
|
131
|
+
this.musicEventCleanup = null;
|
|
132
|
+
this.pendingLoadCleanup = null;
|
|
133
|
+
this.pendingLoadReject = null;
|
|
131
134
|
this.trackLoop = false;
|
|
132
135
|
this.destroyed = false;
|
|
133
136
|
this.options = options;
|
|
@@ -154,21 +157,50 @@ var AudioBands = class {
|
|
|
154
157
|
this.teardownMusic();
|
|
155
158
|
const audio = new Audio();
|
|
156
159
|
audio.crossOrigin = "anonymous";
|
|
160
|
+
audio.preload = "auto";
|
|
157
161
|
audio.src = url;
|
|
158
162
|
audio.loop = this.trackLoop;
|
|
163
|
+
this.bindMusicElementEvents(audio);
|
|
159
164
|
this.audioEl = audio;
|
|
160
|
-
this.setState({ hasTrack:
|
|
165
|
+
this.setState({ hasTrack: false, loadError: null, playbackError: null });
|
|
161
166
|
const source = ctx.createMediaElementSource(audio);
|
|
162
167
|
source.connect(this.musicAnalyser);
|
|
163
168
|
this.musicSource = source;
|
|
169
|
+
await new Promise((resolve, reject) => {
|
|
170
|
+
const finalize = () => {
|
|
171
|
+
cleanup();
|
|
172
|
+
this.pendingLoadCleanup = null;
|
|
173
|
+
this.pendingLoadReject = null;
|
|
174
|
+
};
|
|
175
|
+
const handleReady = () => {
|
|
176
|
+
finalize();
|
|
177
|
+
this.setState({ hasTrack: true, loadError: null });
|
|
178
|
+
resolve();
|
|
179
|
+
};
|
|
180
|
+
const handleFailure = (event) => {
|
|
181
|
+
finalize();
|
|
182
|
+
const mediaError = event?.target?.error ?? audio.error ?? event;
|
|
183
|
+
reject(this.handleLoadFailure(mediaError));
|
|
184
|
+
};
|
|
185
|
+
const cleanup = () => {
|
|
186
|
+
audio.removeEventListener("loadedmetadata", handleReady);
|
|
187
|
+
audio.removeEventListener("canplay", handleReady);
|
|
188
|
+
audio.removeEventListener("error", handleFailure);
|
|
189
|
+
};
|
|
190
|
+
this.pendingLoadCleanup = cleanup;
|
|
191
|
+
this.pendingLoadReject = reject;
|
|
192
|
+
audio.addEventListener("loadedmetadata", handleReady, { once: true });
|
|
193
|
+
audio.addEventListener("canplay", handleReady, { once: true });
|
|
194
|
+
audio.addEventListener("error", handleFailure, { once: true });
|
|
195
|
+
audio.load();
|
|
196
|
+
});
|
|
164
197
|
}
|
|
165
198
|
async play() {
|
|
166
199
|
const audio = this.audioEl;
|
|
167
200
|
if (!audio) return;
|
|
168
201
|
try {
|
|
169
202
|
await audio.play();
|
|
170
|
-
this.setState({
|
|
171
|
-
this.options.onPlay?.();
|
|
203
|
+
this.setState({ playbackError: null });
|
|
172
204
|
} catch (error) {
|
|
173
205
|
throw this.handleError("playback", error, "playback_error");
|
|
174
206
|
}
|
|
@@ -177,8 +209,6 @@ var AudioBands = class {
|
|
|
177
209
|
const audio = this.audioEl;
|
|
178
210
|
if (!audio || audio.paused) return;
|
|
179
211
|
audio.pause();
|
|
180
|
-
this.setState({ isPlaying: false });
|
|
181
|
-
this.options.onPause?.();
|
|
182
212
|
}
|
|
183
213
|
async togglePlayPause() {
|
|
184
214
|
const audio = this.audioEl;
|
|
@@ -367,6 +397,36 @@ var AudioBands = class {
|
|
|
367
397
|
this.options.onError?.(wrapped);
|
|
368
398
|
return wrapped;
|
|
369
399
|
}
|
|
400
|
+
bindMusicElementEvents(audio) {
|
|
401
|
+
this.musicEventCleanup?.();
|
|
402
|
+
const handlePlay = () => {
|
|
403
|
+
if (this.audioEl !== audio) return;
|
|
404
|
+
this.setState({ isPlaying: true, playbackError: null });
|
|
405
|
+
this.options.onPlay?.();
|
|
406
|
+
};
|
|
407
|
+
const handlePause = () => {
|
|
408
|
+
if (this.audioEl !== audio) return;
|
|
409
|
+
this.setState({ isPlaying: false });
|
|
410
|
+
this.options.onPause?.();
|
|
411
|
+
};
|
|
412
|
+
const handleEnded = () => {
|
|
413
|
+
if (this.audioEl !== audio) return;
|
|
414
|
+
this.setState({ isPlaying: false });
|
|
415
|
+
};
|
|
416
|
+
audio.addEventListener("play", handlePlay);
|
|
417
|
+
audio.addEventListener("pause", handlePause);
|
|
418
|
+
audio.addEventListener("ended", handleEnded);
|
|
419
|
+
this.musicEventCleanup = () => {
|
|
420
|
+
audio.removeEventListener("play", handlePlay);
|
|
421
|
+
audio.removeEventListener("pause", handlePause);
|
|
422
|
+
audio.removeEventListener("ended", handleEnded);
|
|
423
|
+
if (this.musicEventCleanup) this.musicEventCleanup = null;
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
handleLoadFailure(error) {
|
|
427
|
+
this.teardownMusic();
|
|
428
|
+
return this.handleError("load", error, "load_error");
|
|
429
|
+
}
|
|
370
430
|
setState(patch) {
|
|
371
431
|
let changed = false;
|
|
372
432
|
for (const [key, value] of Object.entries(patch)) {
|
|
@@ -378,6 +438,23 @@ var AudioBands = class {
|
|
|
378
438
|
if (changed) this.options.onStateChange?.(this.getState());
|
|
379
439
|
}
|
|
380
440
|
teardownMusic() {
|
|
441
|
+
this.musicEventCleanup?.();
|
|
442
|
+
if (this.pendingLoadReject) {
|
|
443
|
+
const reject = this.pendingLoadReject;
|
|
444
|
+
this.pendingLoadReject = null;
|
|
445
|
+
this.pendingLoadCleanup?.();
|
|
446
|
+
this.pendingLoadCleanup = null;
|
|
447
|
+
reject(
|
|
448
|
+
new AudioBandsError(
|
|
449
|
+
"load",
|
|
450
|
+
"load_error",
|
|
451
|
+
"Audio track loading was interrupted before completion"
|
|
452
|
+
)
|
|
453
|
+
);
|
|
454
|
+
} else if (this.pendingLoadCleanup) {
|
|
455
|
+
this.pendingLoadCleanup();
|
|
456
|
+
this.pendingLoadCleanup = null;
|
|
457
|
+
}
|
|
381
458
|
this.audioEl?.pause();
|
|
382
459
|
if (this.audioEl) {
|
|
383
460
|
this.audioEl.src = "";
|
|
@@ -402,4 +479,4 @@ export {
|
|
|
402
479
|
AudioBandsError,
|
|
403
480
|
AudioBands
|
|
404
481
|
};
|
|
405
|
-
//# sourceMappingURL=chunk-
|
|
482
|
+
//# sourceMappingURL=chunk-EGVVFGLZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/core.ts"],"sourcesContent":["import type { AudioBandsErrorCode, AudioBandsErrorKind } from './types';\n\nexport class AudioBandsError extends Error {\n readonly kind: AudioBandsErrorKind;\n readonly code: AudioBandsErrorCode;\n readonly cause?: unknown;\n\n constructor(\n kind: AudioBandsErrorKind,\n code: AudioBandsErrorCode,\n message: string,\n cause?: unknown,\n ) {\n super(message);\n this.name = 'AudioBandsError';\n this.kind = kind;\n this.code = code;\n this.cause = cause;\n }\n}\n","import { AudioBandsError } from './errors';\nimport type {\n AudioAnalyserConfig,\n AudioBandsOptions,\n AudioBandsSnapshot,\n AudioBandsState,\n AudioSource,\n BandRange,\n Bands,\n ClassicBandRanges,\n CustomBandRanges,\n} from './types';\n\nconst DEFAULT_MUSIC_ANALYSER: Required<AudioAnalyserConfig> = {\n fftSize: 256,\n smoothingTimeConstant: 0.85,\n};\n\nconst DEFAULT_MIC_ANALYSER: Required<AudioAnalyserConfig> = {\n fftSize: 256,\n smoothingTimeConstant: 0.8,\n};\n\nconst DEFAULT_CLASSIC_RANGES: Record<keyof Omit<Bands, 'overall'>, BandRange> = {\n bass: { from: 0, to: 0.08 },\n mid: { from: 0.08, to: 0.4 },\n high: { from: 0.4, to: 1 },\n};\n\nconst ZERO: Bands = { bass: 0, mid: 0, high: 0, overall: 0 };\n\nfunction avg(arr: Uint8Array<ArrayBuffer>, from: number, to: number): number {\n let sum = 0;\n for (let i = from; i < to; i++) sum += arr[i];\n return sum / (to - from);\n}\n\nfunction isPowerOfTwo(value: number): boolean {\n return (value & (value - 1)) === 0;\n}\n\nfunction normalizeAnalyserConfig(\n config: AudioAnalyserConfig | undefined,\n fallback: Required<AudioAnalyserConfig>,\n): Required<AudioAnalyserConfig> {\n const fftSize = config?.fftSize ?? fallback.fftSize;\n const smoothingTimeConstant =\n config?.smoothingTimeConstant ?? fallback.smoothingTimeConstant;\n\n if (\n !Number.isInteger(fftSize) ||\n fftSize < 32 ||\n fftSize > 32768 ||\n !isPowerOfTwo(fftSize)\n ) {\n throw new AudioBandsError(\n 'config',\n 'invalid_config',\n 'fftSize must be a power of two between 32 and 32768',\n );\n }\n\n if (\n typeof smoothingTimeConstant !== 'number' ||\n smoothingTimeConstant < 0 ||\n smoothingTimeConstant > 1\n ) {\n throw new AudioBandsError(\n 'config',\n 'invalid_config',\n 'smoothingTimeConstant must be between 0 and 1',\n );\n }\n\n return { fftSize, smoothingTimeConstant };\n}\n\nfunction normalizeRange(name: string, range: BandRange | undefined): BandRange {\n const normalized = range ?? DEFAULT_CLASSIC_RANGES[name as keyof typeof DEFAULT_CLASSIC_RANGES];\n\n if (\n typeof normalized?.from !== 'number' ||\n typeof normalized?.to !== 'number' ||\n normalized.from < 0 ||\n normalized.to > 1 ||\n normalized.from >= normalized.to\n ) {\n throw new AudioBandsError(\n 'config',\n 'invalid_config',\n `Band range \"${name}\" must satisfy 0 <= from < to <= 1`,\n );\n }\n\n return normalized;\n}\n\nfunction normalizeClassicRanges(\n ranges: ClassicBandRanges | undefined,\n): Record<keyof Omit<Bands, 'overall'>, BandRange> {\n return {\n bass: normalizeRange('bass', ranges?.bass),\n mid: normalizeRange('mid', ranges?.mid),\n high: normalizeRange('high', ranges?.high),\n };\n}\n\nfunction normalizeCustomBands(customBands: CustomBandRanges | undefined): CustomBandRanges {\n if (!customBands) return {};\n\n return Object.fromEntries(\n Object.entries(customBands).map(([name, range]) => [name, normalizeRange(name, range)]),\n );\n}\n\nfunction getIndexes(len: number, range: BandRange): [number, number] {\n const from = Math.max(0, Math.min(len - 1, Math.floor(len * range.from)));\n const to = Math.max(from + 1, Math.min(len, Math.floor(len * range.to)));\n return [from, to];\n}\n\nfunction getRangeValue(data: Uint8Array<ArrayBuffer>, range: BandRange): number {\n const [from, to] = getIndexes(data.length, range);\n return avg(data, from, to) / 255;\n}\n\nfunction fillFrequencyData(\n analyser: AnalyserNode,\n data: Uint8Array<ArrayBuffer>,\n): Uint8Array<ArrayBuffer> {\n analyser.getByteFrequencyData(data);\n return data;\n}\n\nfunction computeBands(\n data: Uint8Array<ArrayBuffer>,\n ranges: Record<keyof Omit<Bands, 'overall'>, BandRange>,\n): Bands {\n const bass = getRangeValue(data, ranges.bass);\n const mid = getRangeValue(data, ranges.mid);\n const high = getRangeValue(data, ranges.high);\n\n return {\n bass,\n mid,\n high,\n overall: bass * 0.5 + mid * 0.3 + high * 0.2,\n };\n}\n\nfunction computeCustomBands(\n data: Uint8Array<ArrayBuffer>,\n ranges: CustomBandRanges,\n): Record<string, number> {\n return Object.fromEntries(\n Object.entries(ranges).map(([name, range]) => [name, getRangeValue(data, range)]),\n );\n}\n\nfunction cloneState(state: AudioBandsState): AudioBandsState {\n return { ...state };\n}\n\n/**\n * Vanilla JS class — no framework dependency.\n * Works in React, Vue, Svelte, or plain HTML.\n */\nexport class AudioBands {\n private options: AudioBandsOptions;\n private readonly musicConfig: Required<AudioAnalyserConfig>;\n private readonly micConfig: Required<AudioAnalyserConfig>;\n private readonly classicRanges: Record<keyof Omit<Bands, 'overall'>, BandRange>;\n private readonly customBandRanges: CustomBandRanges;\n\n private readonly state: AudioBandsState = {\n isPlaying: false,\n micActive: false,\n hasTrack: false,\n loadError: null,\n playbackError: null,\n micError: null,\n };\n\n private ctx: AudioContext | null = null;\n private musicAnalyser: AnalyserNode | null = null;\n private musicData: Uint8Array<ArrayBuffer> | null = null;\n private musicWaveformData: Uint8Array<ArrayBuffer> | null = null;\n private micAnalyser: AnalyserNode | null = null;\n private micData: Uint8Array<ArrayBuffer> | null = null;\n private micWaveformData: Uint8Array<ArrayBuffer> | null = null;\n private audioEl: HTMLAudioElement | null = null;\n private musicSource: MediaElementAudioSourceNode | null = null;\n private micSource: MediaStreamAudioSourceNode | null = null;\n private micStream: MediaStream | null = null;\n private musicEventCleanup: (() => void) | null = null;\n private pendingLoadCleanup: (() => void) | null = null;\n private pendingLoadReject: ((error: AudioBandsError) => void) | null = null;\n private trackLoop = false;\n private destroyed = false;\n\n constructor(options: AudioBandsOptions = {}) {\n this.options = options;\n this.musicConfig = normalizeAnalyserConfig(options.music, DEFAULT_MUSIC_ANALYSER);\n this.micConfig = normalizeAnalyserConfig(options.mic, DEFAULT_MIC_ANALYSER);\n this.classicRanges = normalizeClassicRanges(options.bandRanges);\n this.customBandRanges = normalizeCustomBands(options.customBands);\n }\n\n getState(): AudioBandsState {\n return cloneState(this.state);\n }\n\n getCustomBands(source: AudioSource = 'music'): Record<string, number> {\n const data = this.readFrequencyData(source);\n if (!data) return computeCustomBands(new Uint8Array(1) as Uint8Array<ArrayBuffer>, this.customBandRanges);\n return computeCustomBands(data, this.customBandRanges);\n }\n\n async load(url: string): Promise<void> {\n let ctx: AudioContext;\n try {\n ctx = this.ensureCtx();\n } catch (error) {\n throw this.handleError('load', error, 'load_error');\n }\n\n this.teardownMusic();\n\n const audio = new Audio();\n audio.crossOrigin = 'anonymous';\n audio.preload = 'auto';\n audio.src = url;\n audio.loop = this.trackLoop;\n this.bindMusicElementEvents(audio);\n this.audioEl = audio;\n this.setState({ hasTrack: false, loadError: null, playbackError: null });\n\n const source = ctx.createMediaElementSource(audio);\n source.connect(this.musicAnalyser!);\n this.musicSource = source;\n\n await new Promise<void>((resolve, reject) => {\n const finalize = () => {\n cleanup();\n this.pendingLoadCleanup = null;\n this.pendingLoadReject = null;\n };\n\n const handleReady = () => {\n finalize();\n this.setState({ hasTrack: true, loadError: null });\n resolve();\n };\n\n const handleFailure = (event?: Event) => {\n finalize();\n const mediaError =\n (event?.target as HTMLMediaElement | null)?.error ??\n audio.error ??\n event;\n reject(this.handleLoadFailure(mediaError));\n };\n\n const cleanup = () => {\n audio.removeEventListener('loadedmetadata', handleReady);\n audio.removeEventListener('canplay', handleReady);\n audio.removeEventListener('error', handleFailure);\n };\n\n this.pendingLoadCleanup = cleanup;\n this.pendingLoadReject = reject;\n\n audio.addEventListener('loadedmetadata', handleReady, { once: true });\n audio.addEventListener('canplay', handleReady, { once: true });\n audio.addEventListener('error', handleFailure, { once: true });\n audio.load();\n });\n }\n\n async play(): Promise<void> {\n const audio = this.audioEl;\n if (!audio) return;\n\n try {\n await audio.play();\n this.setState({ playbackError: null });\n } catch (error) {\n throw this.handleError('playback', error, 'playback_error');\n }\n }\n\n pause(): void {\n const audio = this.audioEl;\n if (!audio || audio.paused) return;\n\n audio.pause();\n }\n\n async togglePlayPause(): Promise<void> {\n const audio = this.audioEl;\n if (!audio) return;\n\n if (audio.paused) {\n await this.play();\n return;\n }\n\n this.pause();\n }\n\n setLoop(loop: boolean): void {\n this.trackLoop = loop;\n if (this.audioEl) this.audioEl.loop = loop;\n }\n\n seek(seconds: number): void {\n if (!Number.isFinite(seconds) || seconds < 0) {\n throw new AudioBandsError(\n 'config',\n 'invalid_config',\n 'seek time must be a finite number greater than or equal to 0',\n );\n }\n\n if (!this.audioEl) return;\n\n const duration = Number.isFinite(this.audioEl.duration) ? this.audioEl.duration : null;\n this.audioEl.currentTime = duration === null ? seconds : Math.min(seconds, duration);\n }\n\n getDuration(): number | null {\n if (!this.audioEl) return null;\n return Number.isFinite(this.audioEl.duration) ? this.audioEl.duration : null;\n }\n\n getCurrentTime(): number | null {\n if (!this.audioEl) return null;\n return Number.isFinite(this.audioEl.currentTime) ? this.audioEl.currentTime : null;\n }\n\n async enableMic(): Promise<void> {\n let ctx: AudioContext;\n try {\n ctx = this.ensureCtx();\n } catch (error) {\n throw this.handleError('mic', error);\n }\n\n if (this.micStream) return;\n\n try {\n const stream = await navigator.mediaDevices.getUserMedia({\n audio: true,\n video: false,\n });\n this.micStream = stream;\n\n const analyser = this.createAnalyser(ctx, this.micConfig);\n this.micAnalyser = analyser;\n this.micData = new Uint8Array(\n analyser.frequencyBinCount,\n ) as Uint8Array<ArrayBuffer>;\n this.micWaveformData = new Uint8Array(\n analyser.fftSize,\n ) as Uint8Array<ArrayBuffer>;\n\n const source = ctx.createMediaStreamSource(stream);\n source.connect(analyser);\n this.micSource = source;\n\n this.setState({ micActive: true, micError: null });\n this.options.onMicStart?.();\n } catch (error) {\n throw this.handleError('mic', error, 'mic_error');\n }\n }\n\n disableMic(): void {\n const hadMic = Boolean(this.micStream || this.micSource || this.micAnalyser);\n this.micStream?.getTracks().forEach((track) => track.stop());\n this.micStream = null;\n\n try {\n this.micSource?.disconnect();\n } catch {\n /* already disconnected */\n }\n\n this.micSource = null;\n this.micAnalyser = null;\n this.micData = null;\n this.micWaveformData = null;\n this.setState({ micActive: false });\n\n if (hadMic) this.options.onMicStop?.();\n }\n\n getBands(source: AudioSource = 'music'): Bands {\n const data = this.readFrequencyData(source);\n if (!data) return { ...ZERO };\n return computeBands(data, this.classicRanges);\n }\n\n getFftData(source: AudioSource = 'music'): Uint8Array<ArrayBuffer> | null {\n return this.readFrequencyData(source);\n }\n\n getWaveform(source: AudioSource = 'music'): Uint8Array<ArrayBuffer> | null {\n return this.readWaveformData(source);\n }\n\n snapshot(source: AudioSource = 'music'): AudioBandsSnapshot {\n const fft = this.readFrequencyData(source);\n const waveform = this.readWaveformData(source);\n\n return {\n bands: fft ? computeBands(fft, this.classicRanges) : { ...ZERO },\n customBands: fft\n ? computeCustomBands(fft, this.customBandRanges)\n : computeCustomBands(new Uint8Array(1) as Uint8Array<ArrayBuffer>, this.customBandRanges),\n fft,\n waveform,\n };\n }\n\n destroy(): void {\n if (this.destroyed) return;\n\n this.teardownMusic();\n this.disableMic();\n void this.ctx?.close();\n this.ctx = null;\n this.musicAnalyser = null;\n this.musicData = null;\n this.musicWaveformData = null;\n this.setState({ isPlaying: false, micActive: false, hasTrack: false });\n this.options = {};\n this.destroyed = true;\n }\n\n private readFrequencyData(source: AudioSource): Uint8Array<ArrayBuffer> | null {\n if (source === 'mic') {\n if (!this.micAnalyser || !this.micData) return null;\n return fillFrequencyData(this.micAnalyser, this.micData);\n }\n\n if (!this.musicAnalyser || !this.musicData) return null;\n return fillFrequencyData(this.musicAnalyser, this.musicData);\n }\n\n private readWaveformData(source: AudioSource): Uint8Array<ArrayBuffer> | null {\n if (source === 'mic') {\n if (!this.micAnalyser || !this.micWaveformData) return null;\n this.micAnalyser.getByteTimeDomainData(this.micWaveformData);\n return this.micWaveformData;\n }\n\n if (!this.musicAnalyser || !this.musicWaveformData) return null;\n this.musicAnalyser.getByteTimeDomainData(this.musicWaveformData);\n return this.musicWaveformData;\n }\n\n private ensureCtx(): AudioContext {\n if (this.destroyed) {\n throw new AudioBandsError(\n 'lifecycle',\n 'destroyed',\n 'This AudioBands instance was destroyed',\n );\n }\n\n if (this.ctx) return this.ctx;\n\n const Ctx =\n window.AudioContext ||\n (window as unknown as { webkitAudioContext?: typeof AudioContext })\n .webkitAudioContext;\n\n if (!Ctx) {\n throw new AudioBandsError(\n 'lifecycle',\n 'unsupported_audio_context',\n 'AudioContext is not supported in this environment',\n );\n }\n\n const ctx = new Ctx();\n const analyser = this.createAnalyser(ctx, this.musicConfig);\n analyser.connect(ctx.destination);\n\n this.ctx = ctx;\n this.musicAnalyser = analyser;\n this.musicData = new Uint8Array(\n analyser.frequencyBinCount,\n ) as Uint8Array<ArrayBuffer>;\n this.musicWaveformData = new Uint8Array(\n analyser.fftSize,\n ) as Uint8Array<ArrayBuffer>;\n\n return ctx;\n }\n\n private createAnalyser(\n ctx: AudioContext,\n config: Required<AudioAnalyserConfig>,\n ): AnalyserNode {\n const analyser = ctx.createAnalyser();\n analyser.fftSize = config.fftSize;\n analyser.smoothingTimeConstant = config.smoothingTimeConstant;\n return analyser;\n }\n\n private handleError(\n kind: 'load' | 'playback' | 'mic',\n error: unknown,\n fallbackCode: 'load_error' | 'playback_error' | 'mic_error' = kind === 'mic'\n ? 'mic_error'\n : kind === 'playback'\n ? 'playback_error'\n : 'load_error',\n ): AudioBandsError {\n const wrapped =\n error instanceof AudioBandsError\n ? error\n : new AudioBandsError(\n kind,\n fallbackCode,\n kind === 'mic'\n ? 'Failed to access microphone input'\n : kind === 'playback'\n ? 'Failed to play audio track'\n : 'Failed to load audio track',\n error,\n );\n\n if (kind === 'load') {\n this.setState({ isPlaying: false, loadError: wrapped });\n this.options.onLoadError?.(wrapped);\n } else if (kind === 'playback') {\n this.setState({ isPlaying: false, playbackError: wrapped });\n this.options.onPlaybackError?.(wrapped);\n } else {\n this.setState({ micActive: false, micError: wrapped });\n this.options.onMicError?.(wrapped);\n }\n\n this.options.onError?.(wrapped);\n return wrapped;\n }\n\n private bindMusicElementEvents(audio: HTMLAudioElement): void {\n this.musicEventCleanup?.();\n\n const handlePlay = () => {\n if (this.audioEl !== audio) return;\n this.setState({ isPlaying: true, playbackError: null });\n this.options.onPlay?.();\n };\n\n const handlePause = () => {\n if (this.audioEl !== audio) return;\n this.setState({ isPlaying: false });\n this.options.onPause?.();\n };\n\n const handleEnded = () => {\n if (this.audioEl !== audio) return;\n this.setState({ isPlaying: false });\n };\n\n audio.addEventListener('play', handlePlay);\n audio.addEventListener('pause', handlePause);\n audio.addEventListener('ended', handleEnded);\n\n this.musicEventCleanup = () => {\n audio.removeEventListener('play', handlePlay);\n audio.removeEventListener('pause', handlePause);\n audio.removeEventListener('ended', handleEnded);\n if (this.musicEventCleanup) this.musicEventCleanup = null;\n };\n }\n\n private handleLoadFailure(error: unknown): AudioBandsError {\n this.teardownMusic();\n return this.handleError('load', error, 'load_error');\n }\n\n private setState(patch: Partial<AudioBandsState>): void {\n let changed = false;\n\n for (const [key, value] of Object.entries(patch) as Array<\n [keyof AudioBandsState, AudioBandsState[keyof AudioBandsState]]\n >) {\n if (this.state[key] !== value) {\n this.state[key] = value as never;\n changed = true;\n }\n }\n\n if (changed) this.options.onStateChange?.(this.getState());\n }\n\n private teardownMusic(): void {\n this.musicEventCleanup?.();\n if (this.pendingLoadReject) {\n const reject = this.pendingLoadReject;\n this.pendingLoadReject = null;\n this.pendingLoadCleanup?.();\n this.pendingLoadCleanup = null;\n reject(\n new AudioBandsError(\n 'load',\n 'load_error',\n 'Audio track loading was interrupted before completion',\n ),\n );\n } else if (this.pendingLoadCleanup) {\n this.pendingLoadCleanup();\n this.pendingLoadCleanup = null;\n }\n this.audioEl?.pause();\n if (this.audioEl) {\n this.audioEl.src = '';\n this.audioEl.load();\n }\n this.audioEl = null;\n\n try {\n this.musicSource?.disconnect();\n } catch {\n /* already disconnected */\n }\n\n this.musicSource = null;\n this.musicWaveformData = this.musicAnalyser\n ? new Uint8Array(this.musicAnalyser.fftSize) as Uint8Array<ArrayBuffer>\n : null;\n this.setState({\n isPlaying: false,\n hasTrack: false,\n playbackError: null,\n });\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/core-entry.cjs
CHANGED
|
@@ -155,6 +155,9 @@ var AudioBands = class {
|
|
|
155
155
|
this.musicSource = null;
|
|
156
156
|
this.micSource = null;
|
|
157
157
|
this.micStream = null;
|
|
158
|
+
this.musicEventCleanup = null;
|
|
159
|
+
this.pendingLoadCleanup = null;
|
|
160
|
+
this.pendingLoadReject = null;
|
|
158
161
|
this.trackLoop = false;
|
|
159
162
|
this.destroyed = false;
|
|
160
163
|
this.options = options;
|
|
@@ -181,21 +184,50 @@ var AudioBands = class {
|
|
|
181
184
|
this.teardownMusic();
|
|
182
185
|
const audio = new Audio();
|
|
183
186
|
audio.crossOrigin = "anonymous";
|
|
187
|
+
audio.preload = "auto";
|
|
184
188
|
audio.src = url;
|
|
185
189
|
audio.loop = this.trackLoop;
|
|
190
|
+
this.bindMusicElementEvents(audio);
|
|
186
191
|
this.audioEl = audio;
|
|
187
|
-
this.setState({ hasTrack:
|
|
192
|
+
this.setState({ hasTrack: false, loadError: null, playbackError: null });
|
|
188
193
|
const source = ctx.createMediaElementSource(audio);
|
|
189
194
|
source.connect(this.musicAnalyser);
|
|
190
195
|
this.musicSource = source;
|
|
196
|
+
await new Promise((resolve, reject) => {
|
|
197
|
+
const finalize = () => {
|
|
198
|
+
cleanup();
|
|
199
|
+
this.pendingLoadCleanup = null;
|
|
200
|
+
this.pendingLoadReject = null;
|
|
201
|
+
};
|
|
202
|
+
const handleReady = () => {
|
|
203
|
+
finalize();
|
|
204
|
+
this.setState({ hasTrack: true, loadError: null });
|
|
205
|
+
resolve();
|
|
206
|
+
};
|
|
207
|
+
const handleFailure = (event) => {
|
|
208
|
+
finalize();
|
|
209
|
+
const mediaError = event?.target?.error ?? audio.error ?? event;
|
|
210
|
+
reject(this.handleLoadFailure(mediaError));
|
|
211
|
+
};
|
|
212
|
+
const cleanup = () => {
|
|
213
|
+
audio.removeEventListener("loadedmetadata", handleReady);
|
|
214
|
+
audio.removeEventListener("canplay", handleReady);
|
|
215
|
+
audio.removeEventListener("error", handleFailure);
|
|
216
|
+
};
|
|
217
|
+
this.pendingLoadCleanup = cleanup;
|
|
218
|
+
this.pendingLoadReject = reject;
|
|
219
|
+
audio.addEventListener("loadedmetadata", handleReady, { once: true });
|
|
220
|
+
audio.addEventListener("canplay", handleReady, { once: true });
|
|
221
|
+
audio.addEventListener("error", handleFailure, { once: true });
|
|
222
|
+
audio.load();
|
|
223
|
+
});
|
|
191
224
|
}
|
|
192
225
|
async play() {
|
|
193
226
|
const audio = this.audioEl;
|
|
194
227
|
if (!audio) return;
|
|
195
228
|
try {
|
|
196
229
|
await audio.play();
|
|
197
|
-
this.setState({
|
|
198
|
-
this.options.onPlay?.();
|
|
230
|
+
this.setState({ playbackError: null });
|
|
199
231
|
} catch (error) {
|
|
200
232
|
throw this.handleError("playback", error, "playback_error");
|
|
201
233
|
}
|
|
@@ -204,8 +236,6 @@ var AudioBands = class {
|
|
|
204
236
|
const audio = this.audioEl;
|
|
205
237
|
if (!audio || audio.paused) return;
|
|
206
238
|
audio.pause();
|
|
207
|
-
this.setState({ isPlaying: false });
|
|
208
|
-
this.options.onPause?.();
|
|
209
239
|
}
|
|
210
240
|
async togglePlayPause() {
|
|
211
241
|
const audio = this.audioEl;
|
|
@@ -394,6 +424,36 @@ var AudioBands = class {
|
|
|
394
424
|
this.options.onError?.(wrapped);
|
|
395
425
|
return wrapped;
|
|
396
426
|
}
|
|
427
|
+
bindMusicElementEvents(audio) {
|
|
428
|
+
this.musicEventCleanup?.();
|
|
429
|
+
const handlePlay = () => {
|
|
430
|
+
if (this.audioEl !== audio) return;
|
|
431
|
+
this.setState({ isPlaying: true, playbackError: null });
|
|
432
|
+
this.options.onPlay?.();
|
|
433
|
+
};
|
|
434
|
+
const handlePause = () => {
|
|
435
|
+
if (this.audioEl !== audio) return;
|
|
436
|
+
this.setState({ isPlaying: false });
|
|
437
|
+
this.options.onPause?.();
|
|
438
|
+
};
|
|
439
|
+
const handleEnded = () => {
|
|
440
|
+
if (this.audioEl !== audio) return;
|
|
441
|
+
this.setState({ isPlaying: false });
|
|
442
|
+
};
|
|
443
|
+
audio.addEventListener("play", handlePlay);
|
|
444
|
+
audio.addEventListener("pause", handlePause);
|
|
445
|
+
audio.addEventListener("ended", handleEnded);
|
|
446
|
+
this.musicEventCleanup = () => {
|
|
447
|
+
audio.removeEventListener("play", handlePlay);
|
|
448
|
+
audio.removeEventListener("pause", handlePause);
|
|
449
|
+
audio.removeEventListener("ended", handleEnded);
|
|
450
|
+
if (this.musicEventCleanup) this.musicEventCleanup = null;
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
handleLoadFailure(error) {
|
|
454
|
+
this.teardownMusic();
|
|
455
|
+
return this.handleError("load", error, "load_error");
|
|
456
|
+
}
|
|
397
457
|
setState(patch) {
|
|
398
458
|
let changed = false;
|
|
399
459
|
for (const [key, value] of Object.entries(patch)) {
|
|
@@ -405,6 +465,23 @@ var AudioBands = class {
|
|
|
405
465
|
if (changed) this.options.onStateChange?.(this.getState());
|
|
406
466
|
}
|
|
407
467
|
teardownMusic() {
|
|
468
|
+
this.musicEventCleanup?.();
|
|
469
|
+
if (this.pendingLoadReject) {
|
|
470
|
+
const reject = this.pendingLoadReject;
|
|
471
|
+
this.pendingLoadReject = null;
|
|
472
|
+
this.pendingLoadCleanup?.();
|
|
473
|
+
this.pendingLoadCleanup = null;
|
|
474
|
+
reject(
|
|
475
|
+
new AudioBandsError(
|
|
476
|
+
"load",
|
|
477
|
+
"load_error",
|
|
478
|
+
"Audio track loading was interrupted before completion"
|
|
479
|
+
)
|
|
480
|
+
);
|
|
481
|
+
} else if (this.pendingLoadCleanup) {
|
|
482
|
+
this.pendingLoadCleanup();
|
|
483
|
+
this.pendingLoadCleanup = null;
|
|
484
|
+
}
|
|
408
485
|
this.audioEl?.pause();
|
|
409
486
|
if (this.audioEl) {
|
|
410
487
|
this.audioEl.src = "";
|
package/dist/core-entry.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core-entry.ts","../src/errors.ts","../src/core.ts"],"sourcesContent":["export { AudioBands } from './core';\nexport { AudioBandsError } from './errors';\nexport type {\n AudioAnalyserConfig,\n AudioBandsCallbacks,\n AudioBandsErrorCode,\n AudioBandsErrorKind,\n AudioBandsOptions,\n AudioBandsSnapshot,\n AudioBandsState,\n AudioSource,\n BandRange,\n Bands,\n ClassicBandRanges,\n CustomBandRanges,\n} from './types';\n","import type { AudioBandsErrorCode, AudioBandsErrorKind } from './types';\n\nexport class AudioBandsError extends Error {\n readonly kind: AudioBandsErrorKind;\n readonly code: AudioBandsErrorCode;\n readonly cause?: unknown;\n\n constructor(\n kind: AudioBandsErrorKind,\n code: AudioBandsErrorCode,\n message: string,\n cause?: unknown,\n ) {\n super(message);\n this.name = 'AudioBandsError';\n this.kind = kind;\n this.code = code;\n this.cause = cause;\n }\n}\n","import { AudioBandsError } from './errors';\nimport type {\n AudioAnalyserConfig,\n AudioBandsOptions,\n AudioBandsSnapshot,\n AudioBandsState,\n AudioSource,\n BandRange,\n Bands,\n ClassicBandRanges,\n CustomBandRanges,\n} from './types';\n\nconst DEFAULT_MUSIC_ANALYSER: Required<AudioAnalyserConfig> = {\n fftSize: 256,\n smoothingTimeConstant: 0.85,\n};\n\nconst DEFAULT_MIC_ANALYSER: Required<AudioAnalyserConfig> = {\n fftSize: 256,\n smoothingTimeConstant: 0.8,\n};\n\nconst DEFAULT_CLASSIC_RANGES: Record<keyof Omit<Bands, 'overall'>, BandRange> = {\n bass: { from: 0, to: 0.08 },\n mid: { from: 0.08, to: 0.4 },\n high: { from: 0.4, to: 1 },\n};\n\nconst ZERO: Bands = { bass: 0, mid: 0, high: 0, overall: 0 };\n\nfunction avg(arr: Uint8Array<ArrayBuffer>, from: number, to: number): number {\n let sum = 0;\n for (let i = from; i < to; i++) sum += arr[i];\n return sum / (to - from);\n}\n\nfunction isPowerOfTwo(value: number): boolean {\n return (value & (value - 1)) === 0;\n}\n\nfunction normalizeAnalyserConfig(\n config: AudioAnalyserConfig | undefined,\n fallback: Required<AudioAnalyserConfig>,\n): Required<AudioAnalyserConfig> {\n const fftSize = config?.fftSize ?? fallback.fftSize;\n const smoothingTimeConstant =\n config?.smoothingTimeConstant ?? fallback.smoothingTimeConstant;\n\n if (\n !Number.isInteger(fftSize) ||\n fftSize < 32 ||\n fftSize > 32768 ||\n !isPowerOfTwo(fftSize)\n ) {\n throw new AudioBandsError(\n 'config',\n 'invalid_config',\n 'fftSize must be a power of two between 32 and 32768',\n );\n }\n\n if (\n typeof smoothingTimeConstant !== 'number' ||\n smoothingTimeConstant < 0 ||\n smoothingTimeConstant > 1\n ) {\n throw new AudioBandsError(\n 'config',\n 'invalid_config',\n 'smoothingTimeConstant must be between 0 and 1',\n );\n }\n\n return { fftSize, smoothingTimeConstant };\n}\n\nfunction normalizeRange(name: string, range: BandRange | undefined): BandRange {\n const normalized = range ?? DEFAULT_CLASSIC_RANGES[name as keyof typeof DEFAULT_CLASSIC_RANGES];\n\n if (\n typeof normalized?.from !== 'number' ||\n typeof normalized?.to !== 'number' ||\n normalized.from < 0 ||\n normalized.to > 1 ||\n normalized.from >= normalized.to\n ) {\n throw new AudioBandsError(\n 'config',\n 'invalid_config',\n `Band range \"${name}\" must satisfy 0 <= from < to <= 1`,\n );\n }\n\n return normalized;\n}\n\nfunction normalizeClassicRanges(\n ranges: ClassicBandRanges | undefined,\n): Record<keyof Omit<Bands, 'overall'>, BandRange> {\n return {\n bass: normalizeRange('bass', ranges?.bass),\n mid: normalizeRange('mid', ranges?.mid),\n high: normalizeRange('high', ranges?.high),\n };\n}\n\nfunction normalizeCustomBands(customBands: CustomBandRanges | undefined): CustomBandRanges {\n if (!customBands) return {};\n\n return Object.fromEntries(\n Object.entries(customBands).map(([name, range]) => [name, normalizeRange(name, range)]),\n );\n}\n\nfunction getIndexes(len: number, range: BandRange): [number, number] {\n const from = Math.max(0, Math.min(len - 1, Math.floor(len * range.from)));\n const to = Math.max(from + 1, Math.min(len, Math.floor(len * range.to)));\n return [from, to];\n}\n\nfunction getRangeValue(data: Uint8Array<ArrayBuffer>, range: BandRange): number {\n const [from, to] = getIndexes(data.length, range);\n return avg(data, from, to) / 255;\n}\n\nfunction fillFrequencyData(\n analyser: AnalyserNode,\n data: Uint8Array<ArrayBuffer>,\n): Uint8Array<ArrayBuffer> {\n analyser.getByteFrequencyData(data);\n return data;\n}\n\nfunction computeBands(\n data: Uint8Array<ArrayBuffer>,\n ranges: Record<keyof Omit<Bands, 'overall'>, BandRange>,\n): Bands {\n const bass = getRangeValue(data, ranges.bass);\n const mid = getRangeValue(data, ranges.mid);\n const high = getRangeValue(data, ranges.high);\n\n return {\n bass,\n mid,\n high,\n overall: bass * 0.5 + mid * 0.3 + high * 0.2,\n };\n}\n\nfunction computeCustomBands(\n data: Uint8Array<ArrayBuffer>,\n ranges: CustomBandRanges,\n): Record<string, number> {\n return Object.fromEntries(\n Object.entries(ranges).map(([name, range]) => [name, getRangeValue(data, range)]),\n );\n}\n\nfunction cloneState(state: AudioBandsState): AudioBandsState {\n return { ...state };\n}\n\n/**\n * Vanilla JS class — no framework dependency.\n * Works in React, Vue, Svelte, or plain HTML.\n */\nexport class AudioBands {\n private options: AudioBandsOptions;\n private readonly musicConfig: Required<AudioAnalyserConfig>;\n private readonly micConfig: Required<AudioAnalyserConfig>;\n private readonly classicRanges: Record<keyof Omit<Bands, 'overall'>, BandRange>;\n private readonly customBandRanges: CustomBandRanges;\n\n private readonly state: AudioBandsState = {\n isPlaying: false,\n micActive: false,\n hasTrack: false,\n loadError: null,\n playbackError: null,\n micError: null,\n };\n\n private ctx: AudioContext | null = null;\n private musicAnalyser: AnalyserNode | null = null;\n private musicData: Uint8Array<ArrayBuffer> | null = null;\n private musicWaveformData: Uint8Array<ArrayBuffer> | null = null;\n private micAnalyser: AnalyserNode | null = null;\n private micData: Uint8Array<ArrayBuffer> | null = null;\n private micWaveformData: Uint8Array<ArrayBuffer> | null = null;\n private audioEl: HTMLAudioElement | null = null;\n private musicSource: MediaElementAudioSourceNode | null = null;\n private micSource: MediaStreamAudioSourceNode | null = null;\n private micStream: MediaStream | null = null;\n private trackLoop = false;\n private destroyed = false;\n\n constructor(options: AudioBandsOptions = {}) {\n this.options = options;\n this.musicConfig = normalizeAnalyserConfig(options.music, DEFAULT_MUSIC_ANALYSER);\n this.micConfig = normalizeAnalyserConfig(options.mic, DEFAULT_MIC_ANALYSER);\n this.classicRanges = normalizeClassicRanges(options.bandRanges);\n this.customBandRanges = normalizeCustomBands(options.customBands);\n }\n\n getState(): AudioBandsState {\n return cloneState(this.state);\n }\n\n getCustomBands(source: AudioSource = 'music'): Record<string, number> {\n const data = this.readFrequencyData(source);\n if (!data) return computeCustomBands(new Uint8Array(1) as Uint8Array<ArrayBuffer>, this.customBandRanges);\n return computeCustomBands(data, this.customBandRanges);\n }\n\n async load(url: string): Promise<void> {\n let ctx: AudioContext;\n try {\n ctx = this.ensureCtx();\n } catch (error) {\n throw this.handleError('load', error, 'load_error');\n }\n\n this.teardownMusic();\n\n const audio = new Audio();\n audio.crossOrigin = 'anonymous';\n audio.src = url;\n audio.loop = this.trackLoop;\n this.audioEl = audio;\n this.setState({ hasTrack: true, loadError: null, playbackError: null });\n\n const source = ctx.createMediaElementSource(audio);\n source.connect(this.musicAnalyser!);\n this.musicSource = source;\n }\n\n async play(): Promise<void> {\n const audio = this.audioEl;\n if (!audio) return;\n\n try {\n await audio.play();\n this.setState({ isPlaying: true, playbackError: null });\n this.options.onPlay?.();\n } catch (error) {\n throw this.handleError('playback', error, 'playback_error');\n }\n }\n\n pause(): void {\n const audio = this.audioEl;\n if (!audio || audio.paused) return;\n\n audio.pause();\n this.setState({ isPlaying: false });\n this.options.onPause?.();\n }\n\n async togglePlayPause(): Promise<void> {\n const audio = this.audioEl;\n if (!audio) return;\n\n if (audio.paused) {\n await this.play();\n return;\n }\n\n this.pause();\n }\n\n setLoop(loop: boolean): void {\n this.trackLoop = loop;\n if (this.audioEl) this.audioEl.loop = loop;\n }\n\n seek(seconds: number): void {\n if (!Number.isFinite(seconds) || seconds < 0) {\n throw new AudioBandsError(\n 'config',\n 'invalid_config',\n 'seek time must be a finite number greater than or equal to 0',\n );\n }\n\n if (!this.audioEl) return;\n\n const duration = Number.isFinite(this.audioEl.duration) ? this.audioEl.duration : null;\n this.audioEl.currentTime = duration === null ? seconds : Math.min(seconds, duration);\n }\n\n getDuration(): number | null {\n if (!this.audioEl) return null;\n return Number.isFinite(this.audioEl.duration) ? this.audioEl.duration : null;\n }\n\n getCurrentTime(): number | null {\n if (!this.audioEl) return null;\n return Number.isFinite(this.audioEl.currentTime) ? this.audioEl.currentTime : null;\n }\n\n async enableMic(): Promise<void> {\n let ctx: AudioContext;\n try {\n ctx = this.ensureCtx();\n } catch (error) {\n throw this.handleError('mic', error);\n }\n\n if (this.micStream) return;\n\n try {\n const stream = await navigator.mediaDevices.getUserMedia({\n audio: true,\n video: false,\n });\n this.micStream = stream;\n\n const analyser = this.createAnalyser(ctx, this.micConfig);\n this.micAnalyser = analyser;\n this.micData = new Uint8Array(\n analyser.frequencyBinCount,\n ) as Uint8Array<ArrayBuffer>;\n this.micWaveformData = new Uint8Array(\n analyser.fftSize,\n ) as Uint8Array<ArrayBuffer>;\n\n const source = ctx.createMediaStreamSource(stream);\n source.connect(analyser);\n this.micSource = source;\n\n this.setState({ micActive: true, micError: null });\n this.options.onMicStart?.();\n } catch (error) {\n throw this.handleError('mic', error, 'mic_error');\n }\n }\n\n disableMic(): void {\n const hadMic = Boolean(this.micStream || this.micSource || this.micAnalyser);\n this.micStream?.getTracks().forEach((track) => track.stop());\n this.micStream = null;\n\n try {\n this.micSource?.disconnect();\n } catch {\n /* already disconnected */\n }\n\n this.micSource = null;\n this.micAnalyser = null;\n this.micData = null;\n this.micWaveformData = null;\n this.setState({ micActive: false });\n\n if (hadMic) this.options.onMicStop?.();\n }\n\n getBands(source: AudioSource = 'music'): Bands {\n const data = this.readFrequencyData(source);\n if (!data) return { ...ZERO };\n return computeBands(data, this.classicRanges);\n }\n\n getFftData(source: AudioSource = 'music'): Uint8Array<ArrayBuffer> | null {\n return this.readFrequencyData(source);\n }\n\n getWaveform(source: AudioSource = 'music'): Uint8Array<ArrayBuffer> | null {\n return this.readWaveformData(source);\n }\n\n snapshot(source: AudioSource = 'music'): AudioBandsSnapshot {\n const fft = this.readFrequencyData(source);\n const waveform = this.readWaveformData(source);\n\n return {\n bands: fft ? computeBands(fft, this.classicRanges) : { ...ZERO },\n customBands: fft\n ? computeCustomBands(fft, this.customBandRanges)\n : computeCustomBands(new Uint8Array(1) as Uint8Array<ArrayBuffer>, this.customBandRanges),\n fft,\n waveform,\n };\n }\n\n destroy(): void {\n if (this.destroyed) return;\n\n this.teardownMusic();\n this.disableMic();\n void this.ctx?.close();\n this.ctx = null;\n this.musicAnalyser = null;\n this.musicData = null;\n this.musicWaveformData = null;\n this.setState({ isPlaying: false, micActive: false, hasTrack: false });\n this.options = {};\n this.destroyed = true;\n }\n\n private readFrequencyData(source: AudioSource): Uint8Array<ArrayBuffer> | null {\n if (source === 'mic') {\n if (!this.micAnalyser || !this.micData) return null;\n return fillFrequencyData(this.micAnalyser, this.micData);\n }\n\n if (!this.musicAnalyser || !this.musicData) return null;\n return fillFrequencyData(this.musicAnalyser, this.musicData);\n }\n\n private readWaveformData(source: AudioSource): Uint8Array<ArrayBuffer> | null {\n if (source === 'mic') {\n if (!this.micAnalyser || !this.micWaveformData) return null;\n this.micAnalyser.getByteTimeDomainData(this.micWaveformData);\n return this.micWaveformData;\n }\n\n if (!this.musicAnalyser || !this.musicWaveformData) return null;\n this.musicAnalyser.getByteTimeDomainData(this.musicWaveformData);\n return this.musicWaveformData;\n }\n\n private ensureCtx(): AudioContext {\n if (this.destroyed) {\n throw new AudioBandsError(\n 'lifecycle',\n 'destroyed',\n 'This AudioBands instance was destroyed',\n );\n }\n\n if (this.ctx) return this.ctx;\n\n const Ctx =\n window.AudioContext ||\n (window as unknown as { webkitAudioContext?: typeof AudioContext })\n .webkitAudioContext;\n\n if (!Ctx) {\n throw new AudioBandsError(\n 'lifecycle',\n 'unsupported_audio_context',\n 'AudioContext is not supported in this environment',\n );\n }\n\n const ctx = new Ctx();\n const analyser = this.createAnalyser(ctx, this.musicConfig);\n analyser.connect(ctx.destination);\n\n this.ctx = ctx;\n this.musicAnalyser = analyser;\n this.musicData = new Uint8Array(\n analyser.frequencyBinCount,\n ) as Uint8Array<ArrayBuffer>;\n this.musicWaveformData = new Uint8Array(\n analyser.fftSize,\n ) as Uint8Array<ArrayBuffer>;\n\n return ctx;\n }\n\n private createAnalyser(\n ctx: AudioContext,\n config: Required<AudioAnalyserConfig>,\n ): AnalyserNode {\n const analyser = ctx.createAnalyser();\n analyser.fftSize = config.fftSize;\n analyser.smoothingTimeConstant = config.smoothingTimeConstant;\n return analyser;\n }\n\n private handleError(\n kind: 'load' | 'playback' | 'mic',\n error: unknown,\n fallbackCode: 'load_error' | 'playback_error' | 'mic_error' = kind === 'mic'\n ? 'mic_error'\n : kind === 'playback'\n ? 'playback_error'\n : 'load_error',\n ): AudioBandsError {\n const wrapped =\n error instanceof AudioBandsError\n ? error\n : new AudioBandsError(\n kind,\n fallbackCode,\n kind === 'mic'\n ? 'Failed to access microphone input'\n : kind === 'playback'\n ? 'Failed to play audio track'\n : 'Failed to load audio track',\n error,\n );\n\n if (kind === 'load') {\n this.setState({ isPlaying: false, loadError: wrapped });\n this.options.onLoadError?.(wrapped);\n } else if (kind === 'playback') {\n this.setState({ isPlaying: false, playbackError: wrapped });\n this.options.onPlaybackError?.(wrapped);\n } else {\n this.setState({ micActive: false, micError: wrapped });\n this.options.onMicError?.(wrapped);\n }\n\n this.options.onError?.(wrapped);\n return wrapped;\n }\n\n private setState(patch: Partial<AudioBandsState>): void {\n let changed = false;\n\n for (const [key, value] of Object.entries(patch) as Array<\n [keyof AudioBandsState, AudioBandsState[keyof AudioBandsState]]\n >) {\n if (this.state[key] !== value) {\n this.state[key] = value as never;\n changed = true;\n }\n }\n\n if (changed) this.options.onStateChange?.(this.getState());\n }\n\n private teardownMusic(): void {\n this.audioEl?.pause();\n if (this.audioEl) {\n this.audioEl.src = '';\n this.audioEl.load();\n }\n this.audioEl = null;\n\n try {\n this.musicSource?.disconnect();\n } catch {\n /* already disconnected */\n }\n\n this.musicSource = null;\n this.musicWaveformData = this.musicAnalyser\n ? new Uint8Array(this.musicAnalyser.fftSize) as Uint8Array<ArrayBuffer>\n : null;\n this.setState({\n isPlaying: false,\n hasTrack: false,\n playbackError: null,\n });\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/core-entry.ts","../src/errors.ts","../src/core.ts"],"sourcesContent":["export { AudioBands } from './core';\nexport { AudioBandsError } from './errors';\nexport type {\n AudioAnalyserConfig,\n AudioBandsCallbacks,\n AudioBandsErrorCode,\n AudioBandsErrorKind,\n AudioBandsOptions,\n AudioBandsSnapshot,\n AudioBandsState,\n AudioSource,\n BandRange,\n Bands,\n ClassicBandRanges,\n CustomBandRanges,\n} from './types';\n","import type { AudioBandsErrorCode, AudioBandsErrorKind } from './types';\n\nexport class AudioBandsError extends Error {\n readonly kind: AudioBandsErrorKind;\n readonly code: AudioBandsErrorCode;\n readonly cause?: unknown;\n\n constructor(\n kind: AudioBandsErrorKind,\n code: AudioBandsErrorCode,\n message: string,\n cause?: unknown,\n ) {\n super(message);\n this.name = 'AudioBandsError';\n this.kind = kind;\n this.code = code;\n this.cause = cause;\n }\n}\n","import { AudioBandsError } from './errors';\nimport type {\n AudioAnalyserConfig,\n AudioBandsOptions,\n AudioBandsSnapshot,\n AudioBandsState,\n AudioSource,\n BandRange,\n Bands,\n ClassicBandRanges,\n CustomBandRanges,\n} from './types';\n\nconst DEFAULT_MUSIC_ANALYSER: Required<AudioAnalyserConfig> = {\n fftSize: 256,\n smoothingTimeConstant: 0.85,\n};\n\nconst DEFAULT_MIC_ANALYSER: Required<AudioAnalyserConfig> = {\n fftSize: 256,\n smoothingTimeConstant: 0.8,\n};\n\nconst DEFAULT_CLASSIC_RANGES: Record<keyof Omit<Bands, 'overall'>, BandRange> = {\n bass: { from: 0, to: 0.08 },\n mid: { from: 0.08, to: 0.4 },\n high: { from: 0.4, to: 1 },\n};\n\nconst ZERO: Bands = { bass: 0, mid: 0, high: 0, overall: 0 };\n\nfunction avg(arr: Uint8Array<ArrayBuffer>, from: number, to: number): number {\n let sum = 0;\n for (let i = from; i < to; i++) sum += arr[i];\n return sum / (to - from);\n}\n\nfunction isPowerOfTwo(value: number): boolean {\n return (value & (value - 1)) === 0;\n}\n\nfunction normalizeAnalyserConfig(\n config: AudioAnalyserConfig | undefined,\n fallback: Required<AudioAnalyserConfig>,\n): Required<AudioAnalyserConfig> {\n const fftSize = config?.fftSize ?? fallback.fftSize;\n const smoothingTimeConstant =\n config?.smoothingTimeConstant ?? fallback.smoothingTimeConstant;\n\n if (\n !Number.isInteger(fftSize) ||\n fftSize < 32 ||\n fftSize > 32768 ||\n !isPowerOfTwo(fftSize)\n ) {\n throw new AudioBandsError(\n 'config',\n 'invalid_config',\n 'fftSize must be a power of two between 32 and 32768',\n );\n }\n\n if (\n typeof smoothingTimeConstant !== 'number' ||\n smoothingTimeConstant < 0 ||\n smoothingTimeConstant > 1\n ) {\n throw new AudioBandsError(\n 'config',\n 'invalid_config',\n 'smoothingTimeConstant must be between 0 and 1',\n );\n }\n\n return { fftSize, smoothingTimeConstant };\n}\n\nfunction normalizeRange(name: string, range: BandRange | undefined): BandRange {\n const normalized = range ?? DEFAULT_CLASSIC_RANGES[name as keyof typeof DEFAULT_CLASSIC_RANGES];\n\n if (\n typeof normalized?.from !== 'number' ||\n typeof normalized?.to !== 'number' ||\n normalized.from < 0 ||\n normalized.to > 1 ||\n normalized.from >= normalized.to\n ) {\n throw new AudioBandsError(\n 'config',\n 'invalid_config',\n `Band range \"${name}\" must satisfy 0 <= from < to <= 1`,\n );\n }\n\n return normalized;\n}\n\nfunction normalizeClassicRanges(\n ranges: ClassicBandRanges | undefined,\n): Record<keyof Omit<Bands, 'overall'>, BandRange> {\n return {\n bass: normalizeRange('bass', ranges?.bass),\n mid: normalizeRange('mid', ranges?.mid),\n high: normalizeRange('high', ranges?.high),\n };\n}\n\nfunction normalizeCustomBands(customBands: CustomBandRanges | undefined): CustomBandRanges {\n if (!customBands) return {};\n\n return Object.fromEntries(\n Object.entries(customBands).map(([name, range]) => [name, normalizeRange(name, range)]),\n );\n}\n\nfunction getIndexes(len: number, range: BandRange): [number, number] {\n const from = Math.max(0, Math.min(len - 1, Math.floor(len * range.from)));\n const to = Math.max(from + 1, Math.min(len, Math.floor(len * range.to)));\n return [from, to];\n}\n\nfunction getRangeValue(data: Uint8Array<ArrayBuffer>, range: BandRange): number {\n const [from, to] = getIndexes(data.length, range);\n return avg(data, from, to) / 255;\n}\n\nfunction fillFrequencyData(\n analyser: AnalyserNode,\n data: Uint8Array<ArrayBuffer>,\n): Uint8Array<ArrayBuffer> {\n analyser.getByteFrequencyData(data);\n return data;\n}\n\nfunction computeBands(\n data: Uint8Array<ArrayBuffer>,\n ranges: Record<keyof Omit<Bands, 'overall'>, BandRange>,\n): Bands {\n const bass = getRangeValue(data, ranges.bass);\n const mid = getRangeValue(data, ranges.mid);\n const high = getRangeValue(data, ranges.high);\n\n return {\n bass,\n mid,\n high,\n overall: bass * 0.5 + mid * 0.3 + high * 0.2,\n };\n}\n\nfunction computeCustomBands(\n data: Uint8Array<ArrayBuffer>,\n ranges: CustomBandRanges,\n): Record<string, number> {\n return Object.fromEntries(\n Object.entries(ranges).map(([name, range]) => [name, getRangeValue(data, range)]),\n );\n}\n\nfunction cloneState(state: AudioBandsState): AudioBandsState {\n return { ...state };\n}\n\n/**\n * Vanilla JS class — no framework dependency.\n * Works in React, Vue, Svelte, or plain HTML.\n */\nexport class AudioBands {\n private options: AudioBandsOptions;\n private readonly musicConfig: Required<AudioAnalyserConfig>;\n private readonly micConfig: Required<AudioAnalyserConfig>;\n private readonly classicRanges: Record<keyof Omit<Bands, 'overall'>, BandRange>;\n private readonly customBandRanges: CustomBandRanges;\n\n private readonly state: AudioBandsState = {\n isPlaying: false,\n micActive: false,\n hasTrack: false,\n loadError: null,\n playbackError: null,\n micError: null,\n };\n\n private ctx: AudioContext | null = null;\n private musicAnalyser: AnalyserNode | null = null;\n private musicData: Uint8Array<ArrayBuffer> | null = null;\n private musicWaveformData: Uint8Array<ArrayBuffer> | null = null;\n private micAnalyser: AnalyserNode | null = null;\n private micData: Uint8Array<ArrayBuffer> | null = null;\n private micWaveformData: Uint8Array<ArrayBuffer> | null = null;\n private audioEl: HTMLAudioElement | null = null;\n private musicSource: MediaElementAudioSourceNode | null = null;\n private micSource: MediaStreamAudioSourceNode | null = null;\n private micStream: MediaStream | null = null;\n private musicEventCleanup: (() => void) | null = null;\n private pendingLoadCleanup: (() => void) | null = null;\n private pendingLoadReject: ((error: AudioBandsError) => void) | null = null;\n private trackLoop = false;\n private destroyed = false;\n\n constructor(options: AudioBandsOptions = {}) {\n this.options = options;\n this.musicConfig = normalizeAnalyserConfig(options.music, DEFAULT_MUSIC_ANALYSER);\n this.micConfig = normalizeAnalyserConfig(options.mic, DEFAULT_MIC_ANALYSER);\n this.classicRanges = normalizeClassicRanges(options.bandRanges);\n this.customBandRanges = normalizeCustomBands(options.customBands);\n }\n\n getState(): AudioBandsState {\n return cloneState(this.state);\n }\n\n getCustomBands(source: AudioSource = 'music'): Record<string, number> {\n const data = this.readFrequencyData(source);\n if (!data) return computeCustomBands(new Uint8Array(1) as Uint8Array<ArrayBuffer>, this.customBandRanges);\n return computeCustomBands(data, this.customBandRanges);\n }\n\n async load(url: string): Promise<void> {\n let ctx: AudioContext;\n try {\n ctx = this.ensureCtx();\n } catch (error) {\n throw this.handleError('load', error, 'load_error');\n }\n\n this.teardownMusic();\n\n const audio = new Audio();\n audio.crossOrigin = 'anonymous';\n audio.preload = 'auto';\n audio.src = url;\n audio.loop = this.trackLoop;\n this.bindMusicElementEvents(audio);\n this.audioEl = audio;\n this.setState({ hasTrack: false, loadError: null, playbackError: null });\n\n const source = ctx.createMediaElementSource(audio);\n source.connect(this.musicAnalyser!);\n this.musicSource = source;\n\n await new Promise<void>((resolve, reject) => {\n const finalize = () => {\n cleanup();\n this.pendingLoadCleanup = null;\n this.pendingLoadReject = null;\n };\n\n const handleReady = () => {\n finalize();\n this.setState({ hasTrack: true, loadError: null });\n resolve();\n };\n\n const handleFailure = (event?: Event) => {\n finalize();\n const mediaError =\n (event?.target as HTMLMediaElement | null)?.error ??\n audio.error ??\n event;\n reject(this.handleLoadFailure(mediaError));\n };\n\n const cleanup = () => {\n audio.removeEventListener('loadedmetadata', handleReady);\n audio.removeEventListener('canplay', handleReady);\n audio.removeEventListener('error', handleFailure);\n };\n\n this.pendingLoadCleanup = cleanup;\n this.pendingLoadReject = reject;\n\n audio.addEventListener('loadedmetadata', handleReady, { once: true });\n audio.addEventListener('canplay', handleReady, { once: true });\n audio.addEventListener('error', handleFailure, { once: true });\n audio.load();\n });\n }\n\n async play(): Promise<void> {\n const audio = this.audioEl;\n if (!audio) return;\n\n try {\n await audio.play();\n this.setState({ playbackError: null });\n } catch (error) {\n throw this.handleError('playback', error, 'playback_error');\n }\n }\n\n pause(): void {\n const audio = this.audioEl;\n if (!audio || audio.paused) return;\n\n audio.pause();\n }\n\n async togglePlayPause(): Promise<void> {\n const audio = this.audioEl;\n if (!audio) return;\n\n if (audio.paused) {\n await this.play();\n return;\n }\n\n this.pause();\n }\n\n setLoop(loop: boolean): void {\n this.trackLoop = loop;\n if (this.audioEl) this.audioEl.loop = loop;\n }\n\n seek(seconds: number): void {\n if (!Number.isFinite(seconds) || seconds < 0) {\n throw new AudioBandsError(\n 'config',\n 'invalid_config',\n 'seek time must be a finite number greater than or equal to 0',\n );\n }\n\n if (!this.audioEl) return;\n\n const duration = Number.isFinite(this.audioEl.duration) ? this.audioEl.duration : null;\n this.audioEl.currentTime = duration === null ? seconds : Math.min(seconds, duration);\n }\n\n getDuration(): number | null {\n if (!this.audioEl) return null;\n return Number.isFinite(this.audioEl.duration) ? this.audioEl.duration : null;\n }\n\n getCurrentTime(): number | null {\n if (!this.audioEl) return null;\n return Number.isFinite(this.audioEl.currentTime) ? this.audioEl.currentTime : null;\n }\n\n async enableMic(): Promise<void> {\n let ctx: AudioContext;\n try {\n ctx = this.ensureCtx();\n } catch (error) {\n throw this.handleError('mic', error);\n }\n\n if (this.micStream) return;\n\n try {\n const stream = await navigator.mediaDevices.getUserMedia({\n audio: true,\n video: false,\n });\n this.micStream = stream;\n\n const analyser = this.createAnalyser(ctx, this.micConfig);\n this.micAnalyser = analyser;\n this.micData = new Uint8Array(\n analyser.frequencyBinCount,\n ) as Uint8Array<ArrayBuffer>;\n this.micWaveformData = new Uint8Array(\n analyser.fftSize,\n ) as Uint8Array<ArrayBuffer>;\n\n const source = ctx.createMediaStreamSource(stream);\n source.connect(analyser);\n this.micSource = source;\n\n this.setState({ micActive: true, micError: null });\n this.options.onMicStart?.();\n } catch (error) {\n throw this.handleError('mic', error, 'mic_error');\n }\n }\n\n disableMic(): void {\n const hadMic = Boolean(this.micStream || this.micSource || this.micAnalyser);\n this.micStream?.getTracks().forEach((track) => track.stop());\n this.micStream = null;\n\n try {\n this.micSource?.disconnect();\n } catch {\n /* already disconnected */\n }\n\n this.micSource = null;\n this.micAnalyser = null;\n this.micData = null;\n this.micWaveformData = null;\n this.setState({ micActive: false });\n\n if (hadMic) this.options.onMicStop?.();\n }\n\n getBands(source: AudioSource = 'music'): Bands {\n const data = this.readFrequencyData(source);\n if (!data) return { ...ZERO };\n return computeBands(data, this.classicRanges);\n }\n\n getFftData(source: AudioSource = 'music'): Uint8Array<ArrayBuffer> | null {\n return this.readFrequencyData(source);\n }\n\n getWaveform(source: AudioSource = 'music'): Uint8Array<ArrayBuffer> | null {\n return this.readWaveformData(source);\n }\n\n snapshot(source: AudioSource = 'music'): AudioBandsSnapshot {\n const fft = this.readFrequencyData(source);\n const waveform = this.readWaveformData(source);\n\n return {\n bands: fft ? computeBands(fft, this.classicRanges) : { ...ZERO },\n customBands: fft\n ? computeCustomBands(fft, this.customBandRanges)\n : computeCustomBands(new Uint8Array(1) as Uint8Array<ArrayBuffer>, this.customBandRanges),\n fft,\n waveform,\n };\n }\n\n destroy(): void {\n if (this.destroyed) return;\n\n this.teardownMusic();\n this.disableMic();\n void this.ctx?.close();\n this.ctx = null;\n this.musicAnalyser = null;\n this.musicData = null;\n this.musicWaveformData = null;\n this.setState({ isPlaying: false, micActive: false, hasTrack: false });\n this.options = {};\n this.destroyed = true;\n }\n\n private readFrequencyData(source: AudioSource): Uint8Array<ArrayBuffer> | null {\n if (source === 'mic') {\n if (!this.micAnalyser || !this.micData) return null;\n return fillFrequencyData(this.micAnalyser, this.micData);\n }\n\n if (!this.musicAnalyser || !this.musicData) return null;\n return fillFrequencyData(this.musicAnalyser, this.musicData);\n }\n\n private readWaveformData(source: AudioSource): Uint8Array<ArrayBuffer> | null {\n if (source === 'mic') {\n if (!this.micAnalyser || !this.micWaveformData) return null;\n this.micAnalyser.getByteTimeDomainData(this.micWaveformData);\n return this.micWaveformData;\n }\n\n if (!this.musicAnalyser || !this.musicWaveformData) return null;\n this.musicAnalyser.getByteTimeDomainData(this.musicWaveformData);\n return this.musicWaveformData;\n }\n\n private ensureCtx(): AudioContext {\n if (this.destroyed) {\n throw new AudioBandsError(\n 'lifecycle',\n 'destroyed',\n 'This AudioBands instance was destroyed',\n );\n }\n\n if (this.ctx) return this.ctx;\n\n const Ctx =\n window.AudioContext ||\n (window as unknown as { webkitAudioContext?: typeof AudioContext })\n .webkitAudioContext;\n\n if (!Ctx) {\n throw new AudioBandsError(\n 'lifecycle',\n 'unsupported_audio_context',\n 'AudioContext is not supported in this environment',\n );\n }\n\n const ctx = new Ctx();\n const analyser = this.createAnalyser(ctx, this.musicConfig);\n analyser.connect(ctx.destination);\n\n this.ctx = ctx;\n this.musicAnalyser = analyser;\n this.musicData = new Uint8Array(\n analyser.frequencyBinCount,\n ) as Uint8Array<ArrayBuffer>;\n this.musicWaveformData = new Uint8Array(\n analyser.fftSize,\n ) as Uint8Array<ArrayBuffer>;\n\n return ctx;\n }\n\n private createAnalyser(\n ctx: AudioContext,\n config: Required<AudioAnalyserConfig>,\n ): AnalyserNode {\n const analyser = ctx.createAnalyser();\n analyser.fftSize = config.fftSize;\n analyser.smoothingTimeConstant = config.smoothingTimeConstant;\n return analyser;\n }\n\n private handleError(\n kind: 'load' | 'playback' | 'mic',\n error: unknown,\n fallbackCode: 'load_error' | 'playback_error' | 'mic_error' = kind === 'mic'\n ? 'mic_error'\n : kind === 'playback'\n ? 'playback_error'\n : 'load_error',\n ): AudioBandsError {\n const wrapped =\n error instanceof AudioBandsError\n ? error\n : new AudioBandsError(\n kind,\n fallbackCode,\n kind === 'mic'\n ? 'Failed to access microphone input'\n : kind === 'playback'\n ? 'Failed to play audio track'\n : 'Failed to load audio track',\n error,\n );\n\n if (kind === 'load') {\n this.setState({ isPlaying: false, loadError: wrapped });\n this.options.onLoadError?.(wrapped);\n } else if (kind === 'playback') {\n this.setState({ isPlaying: false, playbackError: wrapped });\n this.options.onPlaybackError?.(wrapped);\n } else {\n this.setState({ micActive: false, micError: wrapped });\n this.options.onMicError?.(wrapped);\n }\n\n this.options.onError?.(wrapped);\n return wrapped;\n }\n\n private bindMusicElementEvents(audio: HTMLAudioElement): void {\n this.musicEventCleanup?.();\n\n const handlePlay = () => {\n if (this.audioEl !== audio) return;\n this.setState({ isPlaying: true, playbackError: null });\n this.options.onPlay?.();\n };\n\n const handlePause = () => {\n if (this.audioEl !== audio) return;\n this.setState({ isPlaying: false });\n this.options.onPause?.();\n };\n\n const handleEnded = () => {\n if (this.audioEl !== audio) return;\n this.setState({ isPlaying: false });\n };\n\n audio.addEventListener('play', handlePlay);\n audio.addEventListener('pause', handlePause);\n audio.addEventListener('ended', handleEnded);\n\n this.musicEventCleanup = () => {\n audio.removeEventListener('play', handlePlay);\n audio.removeEventListener('pause', handlePause);\n audio.removeEventListener('ended', handleEnded);\n if (this.musicEventCleanup) this.musicEventCleanup = null;\n };\n }\n\n private handleLoadFailure(error: unknown): AudioBandsError {\n this.teardownMusic();\n return this.handleError('load', error, 'load_error');\n }\n\n private setState(patch: Partial<AudioBandsState>): void {\n let changed = false;\n\n for (const [key, value] of Object.entries(patch) as Array<\n [keyof AudioBandsState, AudioBandsState[keyof AudioBandsState]]\n >) {\n if (this.state[key] !== value) {\n this.state[key] = value as never;\n changed = true;\n }\n }\n\n if (changed) this.options.onStateChange?.(this.getState());\n }\n\n private teardownMusic(): void {\n this.musicEventCleanup?.();\n if (this.pendingLoadReject) {\n const reject = this.pendingLoadReject;\n this.pendingLoadReject = null;\n this.pendingLoadCleanup?.();\n this.pendingLoadCleanup = null;\n reject(\n new AudioBandsError(\n 'load',\n 'load_error',\n 'Audio track loading was interrupted before completion',\n ),\n );\n } else if (this.pendingLoadCleanup) {\n this.pendingLoadCleanup();\n this.pendingLoadCleanup = null;\n }\n this.audioEl?.pause();\n if (this.audioEl) {\n this.audioEl.src = '';\n this.audioEl.load();\n }\n this.audioEl = null;\n\n try {\n this.musicSource?.disconnect();\n } catch {\n /* already disconnected */\n }\n\n this.musicSource = null;\n this.musicWaveformData = this.musicAnalyser\n ? new Uint8Array(this.musicAnalyser.fftSize) as Uint8Array<ArrayBuffer>\n : null;\n this.setState({\n isPlaying: false,\n hasTrack: false,\n playbackError: null,\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EAKzC,YACE,MACA,MACA,SACA,OACA;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,QAAQ;AAAA,EACf;AACF;;;ACNA,IAAM,yBAAwD;AAAA,EAC5D,SAAS;AAAA,EACT,uBAAuB;AACzB;AAEA,IAAM,uBAAsD;AAAA,EAC1D,SAAS;AAAA,EACT,uBAAuB;AACzB;AAEA,IAAM,yBAA0E;AAAA,EAC9E,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK;AAAA,EAC1B,KAAK,EAAE,MAAM,MAAM,IAAI,IAAI;AAAA,EAC3B,MAAM,EAAE,MAAM,KAAK,IAAI,EAAE;AAC3B;AAEA,IAAM,OAAc,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,SAAS,EAAE;AAE3D,SAAS,IAAI,KAA8B,MAAc,IAAoB;AAC3E,MAAI,MAAM;AACV,WAAS,IAAI,MAAM,IAAI,IAAI,IAAK,QAAO,IAAI,CAAC;AAC5C,SAAO,OAAO,KAAK;AACrB;AAEA,SAAS,aAAa,OAAwB;AAC5C,UAAQ,QAAS,QAAQ,OAAQ;AACnC;AAEA,SAAS,wBACP,QACA,UAC+B;AAC/B,QAAM,UAAU,QAAQ,WAAW,SAAS;AAC5C,QAAM,wBACJ,QAAQ,yBAAyB,SAAS;AAE5C,MACE,CAAC,OAAO,UAAU,OAAO,KACzB,UAAU,MACV,UAAU,SACV,CAAC,aAAa,OAAO,GACrB;AACA,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,MACE,OAAO,0BAA0B,YACjC,wBAAwB,KACxB,wBAAwB,GACxB;AACA,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,sBAAsB;AAC1C;AAEA,SAAS,eAAe,MAAc,OAAyC;AAC7E,QAAM,aAAa,SAAS,uBAAuB,IAA2C;AAE9F,MACE,OAAO,YAAY,SAAS,YAC5B,OAAO,YAAY,OAAO,YAC1B,WAAW,OAAO,KAClB,WAAW,KAAK,KAChB,WAAW,QAAQ,WAAW,IAC9B;AACA,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA,eAAe,IAAI;AAAA,IACrB;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,uBACP,QACiD;AACjD,SAAO;AAAA,IACL,MAAM,eAAe,QAAQ,QAAQ,IAAI;AAAA,IACzC,KAAK,eAAe,OAAO,QAAQ,GAAG;AAAA,IACtC,MAAM,eAAe,QAAQ,QAAQ,IAAI;AAAA,EAC3C;AACF;AAEA,SAAS,qBAAqB,aAA6D;AACzF,MAAI,CAAC,YAAa,QAAO,CAAC;AAE1B,SAAO,OAAO;AAAA,IACZ,OAAO,QAAQ,WAAW,EAAE,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,eAAe,MAAM,KAAK,CAAC,CAAC;AAAA,EACxF;AACF;AAEA,SAAS,WAAW,KAAa,OAAoC;AACnE,QAAM,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,MAAM,GAAG,KAAK,MAAM,MAAM,MAAM,IAAI,CAAC,CAAC;AACxE,QAAM,KAAK,KAAK,IAAI,OAAO,GAAG,KAAK,IAAI,KAAK,KAAK,MAAM,MAAM,MAAM,EAAE,CAAC,CAAC;AACvE,SAAO,CAAC,MAAM,EAAE;AAClB;AAEA,SAAS,cAAc,MAA+B,OAA0B;AAC9E,QAAM,CAAC,MAAM,EAAE,IAAI,WAAW,KAAK,QAAQ,KAAK;AAChD,SAAO,IAAI,MAAM,MAAM,EAAE,IAAI;AAC/B;AAEA,SAAS,kBACP,UACA,MACyB;AACzB,WAAS,qBAAqB,IAAI;AAClC,SAAO;AACT;AAEA,SAAS,aACP,MACA,QACO;AACP,QAAM,OAAO,cAAc,MAAM,OAAO,IAAI;AAC5C,QAAM,MAAM,cAAc,MAAM,OAAO,GAAG;AAC1C,QAAM,OAAO,cAAc,MAAM,OAAO,IAAI;AAE5C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,OAAO,MAAM,MAAM,MAAM,OAAO;AAAA,EAC3C;AACF;AAEA,SAAS,mBACP,MACA,QACwB;AACxB,SAAO,OAAO;AAAA,IACZ,OAAO,QAAQ,MAAM,EAAE,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,cAAc,MAAM,KAAK,CAAC,CAAC;AAAA,EAClF;AACF;AAEA,SAAS,WAAW,OAAyC;AAC3D,SAAO,EAAE,GAAG,MAAM;AACpB;AAMO,IAAM,aAAN,MAAiB;AAAA,EAiCtB,YAAY,UAA6B,CAAC,GAAG;AA1B7C,SAAiB,QAAyB;AAAA,MACxC,WAAW;AAAA,MACX,WAAW;AAAA,MACX,UAAU;AAAA,MACV,WAAW;AAAA,MACX,eAAe;AAAA,MACf,UAAU;AAAA,IACZ;AAEA,SAAQ,MAA2B;AACnC,SAAQ,gBAAqC;AAC7C,SAAQ,YAA4C;AACpD,SAAQ,oBAAoD;AAC5D,SAAQ,cAAmC;AAC3C,SAAQ,UAA0C;AAClD,SAAQ,kBAAkD;AAC1D,SAAQ,UAAmC;AAC3C,SAAQ,cAAkD;AAC1D,SAAQ,YAA+C;AACvD,SAAQ,YAAgC;AACxC,SAAQ,oBAAyC;AACjD,SAAQ,qBAA0C;AAClD,SAAQ,oBAA+D;AACvE,SAAQ,YAAY;AACpB,SAAQ,YAAY;AAGlB,SAAK,UAAU;AACf,SAAK,cAAc,wBAAwB,QAAQ,OAAO,sBAAsB;AAChF,SAAK,YAAY,wBAAwB,QAAQ,KAAK,oBAAoB;AAC1E,SAAK,gBAAgB,uBAAuB,QAAQ,UAAU;AAC9D,SAAK,mBAAmB,qBAAqB,QAAQ,WAAW;AAAA,EAClE;AAAA,EAEA,WAA4B;AAC1B,WAAO,WAAW,KAAK,KAAK;AAAA,EAC9B;AAAA,EAEA,eAAe,SAAsB,SAAiC;AACpE,UAAM,OAAO,KAAK,kBAAkB,MAAM;AAC1C,QAAI,CAAC,KAAM,QAAO,mBAAmB,IAAI,WAAW,CAAC,GAA8B,KAAK,gBAAgB;AACxG,WAAO,mBAAmB,MAAM,KAAK,gBAAgB;AAAA,EACvD;AAAA,EAEA,MAAM,KAAK,KAA4B;AACrC,QAAI;AACJ,QAAI;AACF,YAAM,KAAK,UAAU;AAAA,IACvB,SAAS,OAAO;AACd,YAAM,KAAK,YAAY,QAAQ,OAAO,YAAY;AAAA,IACpD;AAEA,SAAK,cAAc;AAEnB,UAAM,QAAQ,IAAI,MAAM;AACxB,UAAM,cAAc;AACpB,UAAM,UAAU;AAChB,UAAM,MAAM;AACZ,UAAM,OAAO,KAAK;AAClB,SAAK,uBAAuB,KAAK;AACjC,SAAK,UAAU;AACf,SAAK,SAAS,EAAE,UAAU,OAAO,WAAW,MAAM,eAAe,KAAK,CAAC;AAEvE,UAAM,SAAS,IAAI,yBAAyB,KAAK;AACjD,WAAO,QAAQ,KAAK,aAAc;AAClC,SAAK,cAAc;AAEnB,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,YAAM,WAAW,MAAM;AACrB,gBAAQ;AACR,aAAK,qBAAqB;AAC1B,aAAK,oBAAoB;AAAA,MAC3B;AAEA,YAAM,cAAc,MAAM;AACxB,iBAAS;AACT,aAAK,SAAS,EAAE,UAAU,MAAM,WAAW,KAAK,CAAC;AACjD,gBAAQ;AAAA,MACV;AAEA,YAAM,gBAAgB,CAAC,UAAkB;AACvC,iBAAS;AACT,cAAM,aACH,OAAO,QAAoC,SAC5C,MAAM,SACN;AACF,eAAO,KAAK,kBAAkB,UAAU,CAAC;AAAA,MAC3C;AAEA,YAAM,UAAU,MAAM;AACpB,cAAM,oBAAoB,kBAAkB,WAAW;AACvD,cAAM,oBAAoB,WAAW,WAAW;AAChD,cAAM,oBAAoB,SAAS,aAAa;AAAA,MAClD;AAEA,WAAK,qBAAqB;AAC1B,WAAK,oBAAoB;AAEzB,YAAM,iBAAiB,kBAAkB,aAAa,EAAE,MAAM,KAAK,CAAC;AACpE,YAAM,iBAAiB,WAAW,aAAa,EAAE,MAAM,KAAK,CAAC;AAC7D,YAAM,iBAAiB,SAAS,eAAe,EAAE,MAAM,KAAK,CAAC;AAC7D,YAAM,KAAK;AAAA,IACb,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,OAAsB;AAC1B,UAAM,QAAQ,KAAK;AACnB,QAAI,CAAC,MAAO;AAEZ,QAAI;AACF,YAAM,MAAM,KAAK;AACjB,WAAK,SAAS,EAAE,eAAe,KAAK,CAAC;AAAA,IACvC,SAAS,OAAO;AACd,YAAM,KAAK,YAAY,YAAY,OAAO,gBAAgB;AAAA,IAC5D;AAAA,EACF;AAAA,EAEA,QAAc;AACZ,UAAM,QAAQ,KAAK;AACnB,QAAI,CAAC,SAAS,MAAM,OAAQ;AAE5B,UAAM,MAAM;AAAA,EACd;AAAA,EAEA,MAAM,kBAAiC;AACrC,UAAM,QAAQ,KAAK;AACnB,QAAI,CAAC,MAAO;AAEZ,QAAI,MAAM,QAAQ;AAChB,YAAM,KAAK,KAAK;AAChB;AAAA,IACF;AAEA,SAAK,MAAM;AAAA,EACb;AAAA,EAEA,QAAQ,MAAqB;AAC3B,SAAK,YAAY;AACjB,QAAI,KAAK,QAAS,MAAK,QAAQ,OAAO;AAAA,EACxC;AAAA,EAEA,KAAK,SAAuB;AAC1B,QAAI,CAAC,OAAO,SAAS,OAAO,KAAK,UAAU,GAAG;AAC5C,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,QAAI,CAAC,KAAK,QAAS;AAEnB,UAAM,WAAW,OAAO,SAAS,KAAK,QAAQ,QAAQ,IAAI,KAAK,QAAQ,WAAW;AAClF,SAAK,QAAQ,cAAc,aAAa,OAAO,UAAU,KAAK,IAAI,SAAS,QAAQ;AAAA,EACrF;AAAA,EAEA,cAA6B;AAC3B,QAAI,CAAC,KAAK,QAAS,QAAO;AAC1B,WAAO,OAAO,SAAS,KAAK,QAAQ,QAAQ,IAAI,KAAK,QAAQ,WAAW;AAAA,EAC1E;AAAA,EAEA,iBAAgC;AAC9B,QAAI,CAAC,KAAK,QAAS,QAAO;AAC1B,WAAO,OAAO,SAAS,KAAK,QAAQ,WAAW,IAAI,KAAK,QAAQ,cAAc;AAAA,EAChF;AAAA,EAEA,MAAM,YAA2B;AAC/B,QAAI;AACJ,QAAI;AACF,YAAM,KAAK,UAAU;AAAA,IACvB,SAAS,OAAO;AACd,YAAM,KAAK,YAAY,OAAO,KAAK;AAAA,IACrC;AAEA,QAAI,KAAK,UAAW;AAEpB,QAAI;AACF,YAAM,SAAS,MAAM,UAAU,aAAa,aAAa;AAAA,QACvD,OAAO;AAAA,QACP,OAAO;AAAA,MACT,CAAC;AACD,WAAK,YAAY;AAEjB,YAAM,WAAW,KAAK,eAAe,KAAK,KAAK,SAAS;AACxD,WAAK,cAAc;AACnB,WAAK,UAAU,IAAI;AAAA,QACjB,SAAS;AAAA,MACX;AACA,WAAK,kBAAkB,IAAI;AAAA,QACzB,SAAS;AAAA,MACX;AAEA,YAAM,SAAS,IAAI,wBAAwB,MAAM;AACjD,aAAO,QAAQ,QAAQ;AACvB,WAAK,YAAY;AAEjB,WAAK,SAAS,EAAE,WAAW,MAAM,UAAU,KAAK,CAAC;AACjD,WAAK,QAAQ,aAAa;AAAA,IAC5B,SAAS,OAAO;AACd,YAAM,KAAK,YAAY,OAAO,OAAO,WAAW;AAAA,IAClD;AAAA,EACF;AAAA,EAEA,aAAmB;AACjB,UAAM,SAAS,QAAQ,KAAK,aAAa,KAAK,aAAa,KAAK,WAAW;AAC3E,SAAK,WAAW,UAAU,EAAE,QAAQ,CAAC,UAAU,MAAM,KAAK,CAAC;AAC3D,SAAK,YAAY;AAEjB,QAAI;AACF,WAAK,WAAW,WAAW;AAAA,IAC7B,QAAQ;AAAA,IAER;AAEA,SAAK,YAAY;AACjB,SAAK,cAAc;AACnB,SAAK,UAAU;AACf,SAAK,kBAAkB;AACvB,SAAK,SAAS,EAAE,WAAW,MAAM,CAAC;AAElC,QAAI,OAAQ,MAAK,QAAQ,YAAY;AAAA,EACvC;AAAA,EAEA,SAAS,SAAsB,SAAgB;AAC7C,UAAM,OAAO,KAAK,kBAAkB,MAAM;AAC1C,QAAI,CAAC,KAAM,QAAO,EAAE,GAAG,KAAK;AAC5B,WAAO,aAAa,MAAM,KAAK,aAAa;AAAA,EAC9C;AAAA,EAEA,WAAW,SAAsB,SAAyC;AACxE,WAAO,KAAK,kBAAkB,MAAM;AAAA,EACtC;AAAA,EAEA,YAAY,SAAsB,SAAyC;AACzE,WAAO,KAAK,iBAAiB,MAAM;AAAA,EACrC;AAAA,EAEA,SAAS,SAAsB,SAA6B;AAC1D,UAAM,MAAM,KAAK,kBAAkB,MAAM;AACzC,UAAM,WAAW,KAAK,iBAAiB,MAAM;AAE7C,WAAO;AAAA,MACL,OAAO,MAAM,aAAa,KAAK,KAAK,aAAa,IAAI,EAAE,GAAG,KAAK;AAAA,MAC/D,aAAa,MACT,mBAAmB,KAAK,KAAK,gBAAgB,IAC7C,mBAAmB,IAAI,WAAW,CAAC,GAA8B,KAAK,gBAAgB;AAAA,MAC1F;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,UAAgB;AACd,QAAI,KAAK,UAAW;AAEpB,SAAK,cAAc;AACnB,SAAK,WAAW;AAChB,SAAK,KAAK,KAAK,MAAM;AACrB,SAAK,MAAM;AACX,SAAK,gBAAgB;AACrB,SAAK,YAAY;AACjB,SAAK,oBAAoB;AACzB,SAAK,SAAS,EAAE,WAAW,OAAO,WAAW,OAAO,UAAU,MAAM,CAAC;AACrE,SAAK,UAAU,CAAC;AAChB,SAAK,YAAY;AAAA,EACnB;AAAA,EAEQ,kBAAkB,QAAqD;AAC7E,QAAI,WAAW,OAAO;AACpB,UAAI,CAAC,KAAK,eAAe,CAAC,KAAK,QAAS,QAAO;AAC/C,aAAO,kBAAkB,KAAK,aAAa,KAAK,OAAO;AAAA,IACzD;AAEA,QAAI,CAAC,KAAK,iBAAiB,CAAC,KAAK,UAAW,QAAO;AACnD,WAAO,kBAAkB,KAAK,eAAe,KAAK,SAAS;AAAA,EAC7D;AAAA,EAEQ,iBAAiB,QAAqD;AAC5E,QAAI,WAAW,OAAO;AACpB,UAAI,CAAC,KAAK,eAAe,CAAC,KAAK,gBAAiB,QAAO;AACvD,WAAK,YAAY,sBAAsB,KAAK,eAAe;AAC3D,aAAO,KAAK;AAAA,IACd;AAEA,QAAI,CAAC,KAAK,iBAAiB,CAAC,KAAK,kBAAmB,QAAO;AAC3D,SAAK,cAAc,sBAAsB,KAAK,iBAAiB;AAC/D,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,YAA0B;AAChC,QAAI,KAAK,WAAW;AAClB,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,QAAI,KAAK,IAAK,QAAO,KAAK;AAE1B,UAAM,MACJ,OAAO,gBACN,OACE;AAEL,QAAI,CAAC,KAAK;AACR,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,MAAM,IAAI,IAAI;AACpB,UAAM,WAAW,KAAK,eAAe,KAAK,KAAK,WAAW;AAC1D,aAAS,QAAQ,IAAI,WAAW;AAEhC,SAAK,MAAM;AACX,SAAK,gBAAgB;AACrB,SAAK,YAAY,IAAI;AAAA,MACnB,SAAS;AAAA,IACX;AACA,SAAK,oBAAoB,IAAI;AAAA,MAC3B,SAAS;AAAA,IACX;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,eACN,KACA,QACc;AACd,UAAM,WAAW,IAAI,eAAe;AACpC,aAAS,UAAU,OAAO;AAC1B,aAAS,wBAAwB,OAAO;AACxC,WAAO;AAAA,EACT;AAAA,EAEQ,YACN,MACA,OACA,eAA8D,SAAS,QACnE,cACA,SAAS,aACP,mBACA,cACW;AACjB,UAAM,UACJ,iBAAiB,kBACb,QACA,IAAI;AAAA,MACF;AAAA,MACA;AAAA,MACA,SAAS,QACL,sCACA,SAAS,aACP,+BACA;AAAA,MACN;AAAA,IACF;AAEN,QAAI,SAAS,QAAQ;AACnB,WAAK,SAAS,EAAE,WAAW,OAAO,WAAW,QAAQ,CAAC;AACtD,WAAK,QAAQ,cAAc,OAAO;AAAA,IACpC,WAAW,SAAS,YAAY;AAC9B,WAAK,SAAS,EAAE,WAAW,OAAO,eAAe,QAAQ,CAAC;AAC1D,WAAK,QAAQ,kBAAkB,OAAO;AAAA,IACxC,OAAO;AACL,WAAK,SAAS,EAAE,WAAW,OAAO,UAAU,QAAQ,CAAC;AACrD,WAAK,QAAQ,aAAa,OAAO;AAAA,IACnC;AAEA,SAAK,QAAQ,UAAU,OAAO;AAC9B,WAAO;AAAA,EACT;AAAA,EAEQ,uBAAuB,OAA+B;AAC5D,SAAK,oBAAoB;AAEzB,UAAM,aAAa,MAAM;AACvB,UAAI,KAAK,YAAY,MAAO;AAC5B,WAAK,SAAS,EAAE,WAAW,MAAM,eAAe,KAAK,CAAC;AACtD,WAAK,QAAQ,SAAS;AAAA,IACxB;AAEA,UAAM,cAAc,MAAM;AACxB,UAAI,KAAK,YAAY,MAAO;AAC5B,WAAK,SAAS,EAAE,WAAW,MAAM,CAAC;AAClC,WAAK,QAAQ,UAAU;AAAA,IACzB;AAEA,UAAM,cAAc,MAAM;AACxB,UAAI,KAAK,YAAY,MAAO;AAC5B,WAAK,SAAS,EAAE,WAAW,MAAM,CAAC;AAAA,IACpC;AAEA,UAAM,iBAAiB,QAAQ,UAAU;AACzC,UAAM,iBAAiB,SAAS,WAAW;AAC3C,UAAM,iBAAiB,SAAS,WAAW;AAE3C,SAAK,oBAAoB,MAAM;AAC7B,YAAM,oBAAoB,QAAQ,UAAU;AAC5C,YAAM,oBAAoB,SAAS,WAAW;AAC9C,YAAM,oBAAoB,SAAS,WAAW;AAC9C,UAAI,KAAK,kBAAmB,MAAK,oBAAoB;AAAA,IACvD;AAAA,EACF;AAAA,EAEQ,kBAAkB,OAAiC;AACzD,SAAK,cAAc;AACnB,WAAO,KAAK,YAAY,QAAQ,OAAO,YAAY;AAAA,EACrD;AAAA,EAEQ,SAAS,OAAuC;AACtD,QAAI,UAAU;AAEd,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAE5C;AACD,UAAI,KAAK,MAAM,GAAG,MAAM,OAAO;AAC7B,aAAK,MAAM,GAAG,IAAI;AAClB,kBAAU;AAAA,MACZ;AAAA,IACF;AAEA,QAAI,QAAS,MAAK,QAAQ,gBAAgB,KAAK,SAAS,CAAC;AAAA,EAC3D;AAAA,EAEQ,gBAAsB;AAC5B,SAAK,oBAAoB;AACzB,QAAI,KAAK,mBAAmB;AAC1B,YAAM,SAAS,KAAK;AACpB,WAAK,oBAAoB;AACzB,WAAK,qBAAqB;AAC1B,WAAK,qBAAqB;AAC1B;AAAA,QACE,IAAI;AAAA,UACF;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF,WAAW,KAAK,oBAAoB;AAClC,WAAK,mBAAmB;AACxB,WAAK,qBAAqB;AAAA,IAC5B;AACA,SAAK,SAAS,MAAM;AACpB,QAAI,KAAK,SAAS;AAChB,WAAK,QAAQ,MAAM;AACnB,WAAK,QAAQ,KAAK;AAAA,IACpB;AACA,SAAK,UAAU;AAEf,QAAI;AACF,WAAK,aAAa,WAAW;AAAA,IAC/B,QAAQ;AAAA,IAER;AAEA,SAAK,cAAc;AACnB,SAAK,oBAAoB,KAAK,gBAC1B,IAAI,WAAW,KAAK,cAAc,OAAO,IACzC;AACJ,SAAK,SAAS;AAAA,MACZ,WAAW;AAAA,MACX,UAAU;AAAA,MACV,eAAe;AAAA,IACjB,CAAC;AAAA,EACH;AACF;","names":[]}
|
package/dist/core-entry.d.cts
CHANGED
|
@@ -23,6 +23,9 @@ declare class AudioBands {
|
|
|
23
23
|
private musicSource;
|
|
24
24
|
private micSource;
|
|
25
25
|
private micStream;
|
|
26
|
+
private musicEventCleanup;
|
|
27
|
+
private pendingLoadCleanup;
|
|
28
|
+
private pendingLoadReject;
|
|
26
29
|
private trackLoop;
|
|
27
30
|
private destroyed;
|
|
28
31
|
constructor(options?: AudioBandsOptions);
|
|
@@ -48,6 +51,8 @@ declare class AudioBands {
|
|
|
48
51
|
private ensureCtx;
|
|
49
52
|
private createAnalyser;
|
|
50
53
|
private handleError;
|
|
54
|
+
private bindMusicElementEvents;
|
|
55
|
+
private handleLoadFailure;
|
|
51
56
|
private setState;
|
|
52
57
|
private teardownMusic;
|
|
53
58
|
}
|
package/dist/core-entry.d.ts
CHANGED
|
@@ -23,6 +23,9 @@ declare class AudioBands {
|
|
|
23
23
|
private musicSource;
|
|
24
24
|
private micSource;
|
|
25
25
|
private micStream;
|
|
26
|
+
private musicEventCleanup;
|
|
27
|
+
private pendingLoadCleanup;
|
|
28
|
+
private pendingLoadReject;
|
|
26
29
|
private trackLoop;
|
|
27
30
|
private destroyed;
|
|
28
31
|
constructor(options?: AudioBandsOptions);
|
|
@@ -48,6 +51,8 @@ declare class AudioBands {
|
|
|
48
51
|
private ensureCtx;
|
|
49
52
|
private createAnalyser;
|
|
50
53
|
private handleError;
|
|
54
|
+
private bindMusicElementEvents;
|
|
55
|
+
private handleLoadFailure;
|
|
51
56
|
private setState;
|
|
52
57
|
private teardownMusic;
|
|
53
58
|
}
|