@juandinella/audio-bands 0.4.7 → 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.
@@ -1,5 +1,5 @@
1
- import { A as AudioBandsError, a as AudioBandsState, b as AudioSource, B as Bands, c as AudioBandsOptions } from './types-CiYwsfgy.js';
2
- export { d as AudioAnalyserConfig, e as AudioBandsCallbacks, f as AudioBandsErrorCode, g as AudioBandsErrorKind, h as BandRange, C as ClassicBandRanges, i as CustomBandRanges } from './types-CiYwsfgy.js';
1
+ import { A as AudioBandsError, a as AudioBandsState, b as AudioSource, c as AudioBandsSnapshot, B as Bands, d as AudioBandsOptions } from './types-0KQJLMV2.js';
2
+ export { e as AudioAnalyserConfig, f as AudioBandsCallbacks, g as AudioBandsErrorCode, h as AudioBandsErrorKind, i as BandRange, C as ClassicBandRanges, j as CustomBandRanges } from './types-0KQJLMV2.js';
3
3
 
4
4
  type UseAudioBandsReturn = {
5
5
  isPlaying: boolean;
@@ -7,11 +7,19 @@ type UseAudioBandsReturn = {
7
7
  hasTrack: boolean;
8
8
  audioError: boolean;
9
9
  loadError: AudioBandsError | null;
10
+ playbackError: AudioBandsError | null;
10
11
  micError: AudioBandsError | null;
11
12
  state: AudioBandsState;
12
13
  loadTrack: (url: string) => Promise<void>;
13
- togglePlayPause: () => void;
14
+ play: () => Promise<void>;
15
+ pause: () => void;
16
+ setLoop: (loop: boolean) => void;
17
+ seek: (seconds: number) => void;
18
+ getDuration: () => number | null;
19
+ getCurrentTime: () => number | null;
20
+ togglePlayPause: () => Promise<void>;
14
21
  toggleMic: () => Promise<void>;
22
+ snapshot: (source?: AudioSource) => AudioBandsSnapshot;
15
23
  getBands: (source?: AudioSource) => Bands;
16
24
  getCustomBands: (source?: AudioSource) => Record<string, number>;
17
25
  getFftData: (source?: AudioSource) => Uint8Array<ArrayBuffer> | null;
@@ -23,4 +31,4 @@ type UseAudioBandsReturn = {
23
31
  */
24
32
  declare function useAudioBands(options?: AudioBandsOptions): UseAudioBandsReturn;
25
33
 
26
- export { AudioBandsError, AudioBandsOptions, AudioBandsState, AudioSource, Bands, type UseAudioBandsReturn, useAudioBands };
34
+ export { AudioBandsError, AudioBandsOptions, AudioBandsSnapshot, AudioBandsState, AudioSource, Bands, type UseAudioBandsReturn, useAudioBands };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  AudioBands,
3
3
  AudioBandsError
4
- } from "./chunk-33JHLQZJ.js";
4
+ } from "./chunk-EGVVFGLZ.js";
5
5
 
6
6
  // src/react.ts
7
7
  import { useRef, useState, useCallback, useEffect } from "react";
@@ -10,83 +10,168 @@ var INITIAL_STATE = {
10
10
  micActive: false,
11
11
  hasTrack: false,
12
12
  loadError: null,
13
+ playbackError: null,
13
14
  micError: null
14
15
  };
16
+ function stableStringify(value) {
17
+ if (Array.isArray(value)) {
18
+ return `[${value.map((item) => stableStringify(item)).join(",")}]`;
19
+ }
20
+ if (value && typeof value === "object") {
21
+ return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, nestedValue]) => `${JSON.stringify(key)}:${stableStringify(nestedValue)}`).join(",")}}`;
22
+ }
23
+ return JSON.stringify(value) ?? "null";
24
+ }
25
+ function getStructuralOptionsKey(options) {
26
+ return stableStringify({
27
+ music: options.music,
28
+ mic: options.mic,
29
+ bandRanges: options.bandRanges,
30
+ customBands: options.customBands
31
+ });
32
+ }
33
+ function createAudioBandsInstance(options, latestOptions, setState, instanceRef) {
34
+ const next = new AudioBands({
35
+ ...options,
36
+ onPlay: () => {
37
+ if (instanceRef.current !== next) return;
38
+ latestOptions.current.onPlay?.();
39
+ },
40
+ onPause: () => {
41
+ if (instanceRef.current !== next) return;
42
+ latestOptions.current.onPause?.();
43
+ },
44
+ onError: (error) => {
45
+ if (instanceRef.current !== next) return;
46
+ latestOptions.current.onError?.(error);
47
+ },
48
+ onLoadError: (error) => {
49
+ if (instanceRef.current !== next) return;
50
+ latestOptions.current.onLoadError?.(error);
51
+ },
52
+ onPlaybackError: (error) => {
53
+ if (instanceRef.current !== next) return;
54
+ latestOptions.current.onPlaybackError?.(error);
55
+ },
56
+ onMicError: (error) => {
57
+ if (instanceRef.current !== next) return;
58
+ latestOptions.current.onMicError?.(error);
59
+ },
60
+ onMicStart: () => {
61
+ if (instanceRef.current !== next) return;
62
+ latestOptions.current.onMicStart?.();
63
+ },
64
+ onMicStop: () => {
65
+ if (instanceRef.current !== next) return;
66
+ latestOptions.current.onMicStop?.();
67
+ },
68
+ onStateChange: (nextState) => {
69
+ if (instanceRef.current !== next) return;
70
+ setState(nextState);
71
+ latestOptions.current.onStateChange?.(nextState);
72
+ }
73
+ });
74
+ return next;
75
+ }
15
76
  function useAudioBands(options = {}) {
16
77
  const [state, setState] = useState(INITIAL_STATE);
17
78
  const latestOptions = useRef(options);
18
79
  const instance = useRef(null);
80
+ const structuralOptionsKey = getStructuralOptionsKey(options);
81
+ const structuralOptionsKeyRef = useRef(structuralOptionsKey);
19
82
  latestOptions.current = options;
20
- if (!instance.current) {
21
- instance.current = new AudioBands({
22
- ...options,
23
- onPlay: () => {
24
- latestOptions.current.onPlay?.();
25
- },
26
- onPause: () => {
27
- latestOptions.current.onPause?.();
28
- },
29
- onError: (error) => {
30
- latestOptions.current.onError?.(error);
31
- },
32
- onLoadError: (error) => {
33
- latestOptions.current.onLoadError?.(error);
34
- },
35
- onMicError: (error) => {
36
- latestOptions.current.onMicError?.(error);
37
- },
38
- onMicStart: () => {
39
- latestOptions.current.onMicStart?.();
40
- },
41
- onMicStop: () => {
42
- latestOptions.current.onMicStop?.();
43
- },
44
- onStateChange: (nextState) => {
45
- setState(nextState);
46
- latestOptions.current.onStateChange?.(nextState);
47
- }
48
- });
49
- }
83
+ const getOrCreateInstance = () => {
84
+ if (instance.current) return instance.current;
85
+ const next = createAudioBandsInstance(options, latestOptions, setState, instance);
86
+ instance.current = next;
87
+ structuralOptionsKeyRef.current = structuralOptionsKey;
88
+ return next;
89
+ };
90
+ getOrCreateInstance();
50
91
  useEffect(() => {
51
- setState(instance.current.getState());
52
- return () => instance.current?.destroy();
92
+ const current = getOrCreateInstance();
93
+ setState(current.getState());
94
+ return () => {
95
+ if (instance.current !== current) return;
96
+ current.destroy();
97
+ instance.current = null;
98
+ };
53
99
  }, []);
100
+ useEffect(() => {
101
+ if (structuralOptionsKeyRef.current === structuralOptionsKey) return;
102
+ const previous = instance.current;
103
+ const next = createAudioBandsInstance(options, latestOptions, setState, instance);
104
+ instance.current = next;
105
+ structuralOptionsKeyRef.current = structuralOptionsKey;
106
+ setState(next.getState());
107
+ previous?.destroy();
108
+ }, [options, structuralOptionsKey]);
54
109
  const loadTrack = useCallback(async (url) => {
55
- await instance.current.load(url);
110
+ await getOrCreateInstance().load(url);
111
+ }, []);
112
+ const play = useCallback(async () => {
113
+ await getOrCreateInstance().play();
114
+ }, []);
115
+ const pause = useCallback(() => {
116
+ getOrCreateInstance().pause();
117
+ }, []);
118
+ const setLoop = useCallback((loop) => {
119
+ getOrCreateInstance().setLoop(loop);
56
120
  }, []);
57
- const togglePlayPause = useCallback(() => {
58
- instance.current.togglePlayPause();
121
+ const seek = useCallback((seconds) => {
122
+ getOrCreateInstance().seek(seconds);
123
+ }, []);
124
+ const getDuration = useCallback(() => {
125
+ return getOrCreateInstance().getDuration();
126
+ }, []);
127
+ const getCurrentTime = useCallback(() => {
128
+ return getOrCreateInstance().getCurrentTime();
129
+ }, []);
130
+ const togglePlayPause = useCallback(async () => {
131
+ await getOrCreateInstance().togglePlayPause();
59
132
  }, []);
60
133
  const toggleMic = useCallback(async () => {
61
- if (instance.current.getState().micActive) {
62
- instance.current.disableMic();
134
+ const current = getOrCreateInstance();
135
+ if (current.getState().micActive) {
136
+ current.disableMic();
63
137
  } else {
64
- await instance.current.enableMic();
138
+ await current.enableMic();
65
139
  }
66
140
  }, []);
141
+ const snapshot = useCallback((source) => {
142
+ return getOrCreateInstance().snapshot(source);
143
+ }, []);
67
144
  const getBands = useCallback((source) => {
68
- return instance.current.getBands(source);
145
+ return getOrCreateInstance().getBands(source);
69
146
  }, []);
70
147
  const getCustomBands = useCallback((source) => {
71
- return instance.current.getCustomBands(source);
148
+ return getOrCreateInstance().getCustomBands(source);
72
149
  }, []);
73
150
  const getFftData = useCallback((source) => {
74
- return instance.current.getFftData(source);
151
+ return getOrCreateInstance().getFftData(source);
75
152
  }, []);
76
153
  const getWaveform = useCallback((source) => {
77
- return instance.current.getWaveform(source);
154
+ return getOrCreateInstance().getWaveform(source);
78
155
  }, []);
79
156
  return {
80
157
  isPlaying: state.isPlaying,
81
158
  micActive: state.micActive,
82
159
  hasTrack: state.hasTrack,
83
- audioError: Boolean(state.loadError || state.micError),
160
+ audioError: Boolean(state.loadError || state.playbackError || state.micError),
84
161
  loadError: state.loadError,
162
+ playbackError: state.playbackError,
85
163
  micError: state.micError,
86
164
  state,
87
165
  loadTrack,
166
+ play,
167
+ pause,
168
+ setLoop,
169
+ seek,
170
+ getDuration,
171
+ getCurrentTime,
88
172
  togglePlayPause,
89
173
  toggleMic,
174
+ snapshot,
90
175
  getBands,
91
176
  getCustomBands,
92
177
  getFftData,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react.ts"],"sourcesContent":["'use client';\n\nimport { useRef, useState, useCallback, useEffect } from 'react';\nimport { AudioBands } from './core';\nimport type { AudioBandsOptions, AudioBandsState, AudioSource, Bands } from './types';\nimport type { AudioBandsError } from './errors';\n\nexport type UseAudioBandsReturn = {\n isPlaying: boolean;\n micActive: boolean;\n hasTrack: boolean;\n audioError: boolean;\n loadError: AudioBandsError | null;\n micError: AudioBandsError | null;\n state: AudioBandsState;\n loadTrack: (url: string) => Promise<void>;\n togglePlayPause: () => void;\n toggleMic: () => Promise<void>;\n getBands: (source?: AudioSource) => Bands;\n getCustomBands: (source?: AudioSource) => Record<string, number>;\n getFftData: (source?: AudioSource) => Uint8Array<ArrayBuffer> | null;\n getWaveform: (source?: AudioSource) => Uint8Array<ArrayBuffer> | null;\n};\n\nconst INITIAL_STATE: AudioBandsState = {\n isPlaying: false,\n micActive: false,\n hasTrack: false,\n loadError: null,\n micError: null,\n};\n\n/**\n * React hook — thin wrapper over AudioBands.\n * Handles lifecycle (destroy on unmount) and exposes state for re-renders.\n */\nexport function useAudioBands(options: AudioBandsOptions = {}): UseAudioBandsReturn {\n const [state, setState] = useState<AudioBandsState>(INITIAL_STATE);\n const latestOptions = useRef(options);\n const instance = useRef<AudioBands | null>(null);\n\n latestOptions.current = options;\n\n if (!instance.current) {\n instance.current = new AudioBands({\n ...options,\n onPlay: () => {\n latestOptions.current.onPlay?.();\n },\n onPause: () => {\n latestOptions.current.onPause?.();\n },\n onError: (error) => {\n latestOptions.current.onError?.(error);\n },\n onLoadError: (error) => {\n latestOptions.current.onLoadError?.(error);\n },\n onMicError: (error) => {\n latestOptions.current.onMicError?.(error);\n },\n onMicStart: () => {\n latestOptions.current.onMicStart?.();\n },\n onMicStop: () => {\n latestOptions.current.onMicStop?.();\n },\n onStateChange: (nextState) => {\n setState(nextState);\n latestOptions.current.onStateChange?.(nextState);\n },\n });\n }\n\n useEffect(() => {\n setState(instance.current!.getState());\n return () => instance.current?.destroy();\n }, []);\n\n const loadTrack = useCallback(async (url: string) => {\n await instance.current!.load(url);\n }, []);\n\n const togglePlayPause = useCallback(() => {\n instance.current!.togglePlayPause();\n }, []);\n\n const toggleMic = useCallback(async () => {\n if (instance.current!.getState().micActive) {\n instance.current!.disableMic();\n } else {\n await instance.current!.enableMic();\n }\n }, []);\n\n const getBands = useCallback((source?: AudioSource) => {\n return instance.current!.getBands(source);\n }, []);\n\n const getCustomBands = useCallback((source?: AudioSource) => {\n return instance.current!.getCustomBands(source);\n }, []);\n\n const getFftData = useCallback((source?: AudioSource) => {\n return instance.current!.getFftData(source);\n }, []);\n\n const getWaveform = useCallback((source?: AudioSource) => {\n return instance.current!.getWaveform(source);\n }, []);\n\n return {\n isPlaying: state.isPlaying,\n micActive: state.micActive,\n hasTrack: state.hasTrack,\n audioError: Boolean(state.loadError || state.micError),\n loadError: state.loadError,\n micError: state.micError,\n state,\n loadTrack,\n togglePlayPause,\n toggleMic,\n getBands,\n getCustomBands,\n getFftData,\n getWaveform,\n };\n}\n"],"mappings":";;;;;;AAEA,SAAS,QAAQ,UAAU,aAAa,iBAAiB;AAsBzD,IAAM,gBAAiC;AAAA,EACrC,WAAW;AAAA,EACX,WAAW;AAAA,EACX,UAAU;AAAA,EACV,WAAW;AAAA,EACX,UAAU;AACZ;AAMO,SAAS,cAAc,UAA6B,CAAC,GAAwB;AAClF,QAAM,CAAC,OAAO,QAAQ,IAAI,SAA0B,aAAa;AACjE,QAAM,gBAAgB,OAAO,OAAO;AACpC,QAAM,WAAW,OAA0B,IAAI;AAE/C,gBAAc,UAAU;AAExB,MAAI,CAAC,SAAS,SAAS;AACrB,aAAS,UAAU,IAAI,WAAW;AAAA,MAChC,GAAG;AAAA,MACH,QAAQ,MAAM;AACZ,sBAAc,QAAQ,SAAS;AAAA,MACjC;AAAA,MACA,SAAS,MAAM;AACb,sBAAc,QAAQ,UAAU;AAAA,MAClC;AAAA,MACA,SAAS,CAAC,UAAU;AAClB,sBAAc,QAAQ,UAAU,KAAK;AAAA,MACvC;AAAA,MACA,aAAa,CAAC,UAAU;AACtB,sBAAc,QAAQ,cAAc,KAAK;AAAA,MAC3C;AAAA,MACA,YAAY,CAAC,UAAU;AACrB,sBAAc,QAAQ,aAAa,KAAK;AAAA,MAC1C;AAAA,MACA,YAAY,MAAM;AAChB,sBAAc,QAAQ,aAAa;AAAA,MACrC;AAAA,MACA,WAAW,MAAM;AACf,sBAAc,QAAQ,YAAY;AAAA,MACpC;AAAA,MACA,eAAe,CAAC,cAAc;AAC5B,iBAAS,SAAS;AAClB,sBAAc,QAAQ,gBAAgB,SAAS;AAAA,MACjD;AAAA,IACF,CAAC;AAAA,EACH;AAEA,YAAU,MAAM;AACd,aAAS,SAAS,QAAS,SAAS,CAAC;AACrC,WAAO,MAAM,SAAS,SAAS,QAAQ;AAAA,EACzC,GAAG,CAAC,CAAC;AAEL,QAAM,YAAY,YAAY,OAAO,QAAgB;AACnD,UAAM,SAAS,QAAS,KAAK,GAAG;AAAA,EAClC,GAAG,CAAC,CAAC;AAEL,QAAM,kBAAkB,YAAY,MAAM;AACxC,aAAS,QAAS,gBAAgB;AAAA,EACpC,GAAG,CAAC,CAAC;AAEL,QAAM,YAAY,YAAY,YAAY;AACxC,QAAI,SAAS,QAAS,SAAS,EAAE,WAAW;AAC1C,eAAS,QAAS,WAAW;AAAA,IAC/B,OAAO;AACL,YAAM,SAAS,QAAS,UAAU;AAAA,IACpC;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,WAAW,YAAY,CAAC,WAAyB;AACrD,WAAO,SAAS,QAAS,SAAS,MAAM;AAAA,EAC1C,GAAG,CAAC,CAAC;AAEL,QAAM,iBAAiB,YAAY,CAAC,WAAyB;AAC3D,WAAO,SAAS,QAAS,eAAe,MAAM;AAAA,EAChD,GAAG,CAAC,CAAC;AAEL,QAAM,aAAa,YAAY,CAAC,WAAyB;AACvD,WAAO,SAAS,QAAS,WAAW,MAAM;AAAA,EAC5C,GAAG,CAAC,CAAC;AAEL,QAAM,cAAc,YAAY,CAAC,WAAyB;AACxD,WAAO,SAAS,QAAS,YAAY,MAAM;AAAA,EAC7C,GAAG,CAAC,CAAC;AAEL,SAAO;AAAA,IACL,WAAW,MAAM;AAAA,IACjB,WAAW,MAAM;AAAA,IACjB,UAAU,MAAM;AAAA,IAChB,YAAY,QAAQ,MAAM,aAAa,MAAM,QAAQ;AAAA,IACrD,WAAW,MAAM;AAAA,IACjB,UAAU,MAAM;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/react.ts"],"sourcesContent":["'use client';\n\nimport { useRef, useState, useCallback, useEffect } from 'react';\nimport type { Dispatch, MutableRefObject, SetStateAction } from 'react';\nimport { AudioBands } from './core';\nimport type {\n AudioBandsOptions,\n AudioBandsSnapshot,\n AudioBandsState,\n AudioSource,\n Bands,\n} from './types';\nimport type { AudioBandsError } from './errors';\n\nexport type UseAudioBandsReturn = {\n isPlaying: boolean;\n micActive: boolean;\n hasTrack: boolean;\n audioError: boolean;\n loadError: AudioBandsError | null;\n playbackError: AudioBandsError | null;\n micError: AudioBandsError | null;\n state: AudioBandsState;\n loadTrack: (url: string) => Promise<void>;\n play: () => Promise<void>;\n pause: () => void;\n setLoop: (loop: boolean) => void;\n seek: (seconds: number) => void;\n getDuration: () => number | null;\n getCurrentTime: () => number | null;\n togglePlayPause: () => Promise<void>;\n toggleMic: () => Promise<void>;\n snapshot: (source?: AudioSource) => AudioBandsSnapshot;\n getBands: (source?: AudioSource) => Bands;\n getCustomBands: (source?: AudioSource) => Record<string, number>;\n getFftData: (source?: AudioSource) => Uint8Array<ArrayBuffer> | null;\n getWaveform: (source?: AudioSource) => Uint8Array<ArrayBuffer> | null;\n};\n\nconst INITIAL_STATE: AudioBandsState = {\n isPlaying: false,\n micActive: false,\n hasTrack: false,\n loadError: null,\n playbackError: null,\n micError: null,\n};\n\nfunction stableStringify(value: unknown): string {\n if (Array.isArray(value)) {\n return `[${value.map((item) => stableStringify(item)).join(',')}]`;\n }\n\n if (value && typeof value === 'object') {\n return `{${Object.entries(value as Record<string, unknown>)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([key, nestedValue]) => `${JSON.stringify(key)}:${stableStringify(nestedValue)}`)\n .join(',')}}`;\n }\n\n return JSON.stringify(value) ?? 'null';\n}\n\nfunction getStructuralOptionsKey(options: AudioBandsOptions): string {\n return stableStringify({\n music: options.music,\n mic: options.mic,\n bandRanges: options.bandRanges,\n customBands: options.customBands,\n });\n}\n\nfunction createAudioBandsInstance(\n options: AudioBandsOptions,\n latestOptions: MutableRefObject<AudioBandsOptions>,\n setState: Dispatch<SetStateAction<AudioBandsState>>,\n instanceRef: MutableRefObject<AudioBands | null>,\n): AudioBands {\n const next = new AudioBands({\n ...options,\n onPlay: () => {\n if (instanceRef.current !== next) return;\n latestOptions.current.onPlay?.();\n },\n onPause: () => {\n if (instanceRef.current !== next) return;\n latestOptions.current.onPause?.();\n },\n onError: (error) => {\n if (instanceRef.current !== next) return;\n latestOptions.current.onError?.(error);\n },\n onLoadError: (error) => {\n if (instanceRef.current !== next) return;\n latestOptions.current.onLoadError?.(error);\n },\n onPlaybackError: (error) => {\n if (instanceRef.current !== next) return;\n latestOptions.current.onPlaybackError?.(error);\n },\n onMicError: (error) => {\n if (instanceRef.current !== next) return;\n latestOptions.current.onMicError?.(error);\n },\n onMicStart: () => {\n if (instanceRef.current !== next) return;\n latestOptions.current.onMicStart?.();\n },\n onMicStop: () => {\n if (instanceRef.current !== next) return;\n latestOptions.current.onMicStop?.();\n },\n onStateChange: (nextState) => {\n if (instanceRef.current !== next) return;\n setState(nextState);\n latestOptions.current.onStateChange?.(nextState);\n },\n });\n\n return next;\n}\n\n/**\n * React hook — thin wrapper over AudioBands.\n * Handles lifecycle (destroy on unmount) and exposes state for re-renders.\n */\nexport function useAudioBands(options: AudioBandsOptions = {}): UseAudioBandsReturn {\n const [state, setState] = useState<AudioBandsState>(INITIAL_STATE);\n const latestOptions = useRef(options);\n const instance = useRef<AudioBands | null>(null);\n const structuralOptionsKey = getStructuralOptionsKey(options);\n const structuralOptionsKeyRef = useRef(structuralOptionsKey);\n\n latestOptions.current = options;\n\n const getOrCreateInstance = (): AudioBands => {\n if (instance.current) return instance.current;\n\n const next = createAudioBandsInstance(options, latestOptions, setState, instance);\n instance.current = next;\n structuralOptionsKeyRef.current = structuralOptionsKey;\n return next;\n };\n\n getOrCreateInstance();\n\n useEffect(() => {\n const current = getOrCreateInstance();\n setState(current.getState());\n\n return () => {\n if (instance.current !== current) return;\n current.destroy();\n instance.current = null;\n };\n }, []);\n\n useEffect(() => {\n if (structuralOptionsKeyRef.current === structuralOptionsKey) return;\n\n const previous = instance.current;\n const next = createAudioBandsInstance(options, latestOptions, setState, instance);\n instance.current = next;\n structuralOptionsKeyRef.current = structuralOptionsKey;\n setState(next.getState());\n previous?.destroy();\n }, [options, structuralOptionsKey]);\n\n const loadTrack = useCallback(async (url: string) => {\n await getOrCreateInstance().load(url);\n }, []);\n\n const play = useCallback(async () => {\n await getOrCreateInstance().play();\n }, []);\n\n const pause = useCallback(() => {\n getOrCreateInstance().pause();\n }, []);\n\n const setLoop = useCallback((loop: boolean) => {\n getOrCreateInstance().setLoop(loop);\n }, []);\n\n const seek = useCallback((seconds: number) => {\n getOrCreateInstance().seek(seconds);\n }, []);\n\n const getDuration = useCallback(() => {\n return getOrCreateInstance().getDuration();\n }, []);\n\n const getCurrentTime = useCallback(() => {\n return getOrCreateInstance().getCurrentTime();\n }, []);\n\n const togglePlayPause = useCallback(async () => {\n await getOrCreateInstance().togglePlayPause();\n }, []);\n\n const toggleMic = useCallback(async () => {\n const current = getOrCreateInstance();\n if (current.getState().micActive) {\n current.disableMic();\n } else {\n await current.enableMic();\n }\n }, []);\n\n const snapshot = useCallback((source?: AudioSource) => {\n return getOrCreateInstance().snapshot(source);\n }, []);\n\n const getBands = useCallback((source?: AudioSource) => {\n return getOrCreateInstance().getBands(source);\n }, []);\n\n const getCustomBands = useCallback((source?: AudioSource) => {\n return getOrCreateInstance().getCustomBands(source);\n }, []);\n\n const getFftData = useCallback((source?: AudioSource) => {\n return getOrCreateInstance().getFftData(source);\n }, []);\n\n const getWaveform = useCallback((source?: AudioSource) => {\n return getOrCreateInstance().getWaveform(source);\n }, []);\n\n return {\n isPlaying: state.isPlaying,\n micActive: state.micActive,\n hasTrack: state.hasTrack,\n audioError: Boolean(state.loadError || state.playbackError || state.micError),\n loadError: state.loadError,\n playbackError: state.playbackError,\n micError: state.micError,\n state,\n loadTrack,\n play,\n pause,\n setLoop,\n seek,\n getDuration,\n getCurrentTime,\n togglePlayPause,\n toggleMic,\n snapshot,\n getBands,\n getCustomBands,\n getFftData,\n getWaveform,\n };\n}\n"],"mappings":";;;;;;AAEA,SAAS,QAAQ,UAAU,aAAa,iBAAiB;AAqCzD,IAAM,gBAAiC;AAAA,EACrC,WAAW;AAAA,EACX,WAAW;AAAA,EACX,UAAU;AAAA,EACV,WAAW;AAAA,EACX,eAAe;AAAA,EACf,UAAU;AACZ;AAEA,SAAS,gBAAgB,OAAwB;AAC/C,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,IAAI,MAAM,IAAI,CAAC,SAAS,gBAAgB,IAAI,CAAC,EAAE,KAAK,GAAG,CAAC;AAAA,EACjE;AAEA,MAAI,SAAS,OAAO,UAAU,UAAU;AACtC,WAAO,IAAI,OAAO,QAAQ,KAAgC,EACvD,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,EACrC,IAAI,CAAC,CAAC,KAAK,WAAW,MAAM,GAAG,KAAK,UAAU,GAAG,CAAC,IAAI,gBAAgB,WAAW,CAAC,EAAE,EACpF,KAAK,GAAG,CAAC;AAAA,EACd;AAEA,SAAO,KAAK,UAAU,KAAK,KAAK;AAClC;AAEA,SAAS,wBAAwB,SAAoC;AACnE,SAAO,gBAAgB;AAAA,IACrB,OAAO,QAAQ;AAAA,IACf,KAAK,QAAQ;AAAA,IACb,YAAY,QAAQ;AAAA,IACpB,aAAa,QAAQ;AAAA,EACvB,CAAC;AACH;AAEA,SAAS,yBACP,SACA,eACA,UACA,aACY;AACZ,QAAM,OAAO,IAAI,WAAW;AAAA,IAC1B,GAAG;AAAA,IACH,QAAQ,MAAM;AACZ,UAAI,YAAY,YAAY,KAAM;AAClC,oBAAc,QAAQ,SAAS;AAAA,IACjC;AAAA,IACA,SAAS,MAAM;AACb,UAAI,YAAY,YAAY,KAAM;AAClC,oBAAc,QAAQ,UAAU;AAAA,IAClC;AAAA,IACA,SAAS,CAAC,UAAU;AAClB,UAAI,YAAY,YAAY,KAAM;AAClC,oBAAc,QAAQ,UAAU,KAAK;AAAA,IACvC;AAAA,IACA,aAAa,CAAC,UAAU;AACtB,UAAI,YAAY,YAAY,KAAM;AAClC,oBAAc,QAAQ,cAAc,KAAK;AAAA,IAC3C;AAAA,IACA,iBAAiB,CAAC,UAAU;AAC1B,UAAI,YAAY,YAAY,KAAM;AAClC,oBAAc,QAAQ,kBAAkB,KAAK;AAAA,IAC/C;AAAA,IACA,YAAY,CAAC,UAAU;AACrB,UAAI,YAAY,YAAY,KAAM;AAClC,oBAAc,QAAQ,aAAa,KAAK;AAAA,IAC1C;AAAA,IACA,YAAY,MAAM;AAChB,UAAI,YAAY,YAAY,KAAM;AAClC,oBAAc,QAAQ,aAAa;AAAA,IACrC;AAAA,IACA,WAAW,MAAM;AACf,UAAI,YAAY,YAAY,KAAM;AAClC,oBAAc,QAAQ,YAAY;AAAA,IACpC;AAAA,IACA,eAAe,CAAC,cAAc;AAC5B,UAAI,YAAY,YAAY,KAAM;AAClC,eAAS,SAAS;AAClB,oBAAc,QAAQ,gBAAgB,SAAS;AAAA,IACjD;AAAA,EACF,CAAC;AAED,SAAO;AACT;AAMO,SAAS,cAAc,UAA6B,CAAC,GAAwB;AAClF,QAAM,CAAC,OAAO,QAAQ,IAAI,SAA0B,aAAa;AACjE,QAAM,gBAAgB,OAAO,OAAO;AACpC,QAAM,WAAW,OAA0B,IAAI;AAC/C,QAAM,uBAAuB,wBAAwB,OAAO;AAC5D,QAAM,0BAA0B,OAAO,oBAAoB;AAE3D,gBAAc,UAAU;AAExB,QAAM,sBAAsB,MAAkB;AAC5C,QAAI,SAAS,QAAS,QAAO,SAAS;AAEtC,UAAM,OAAO,yBAAyB,SAAS,eAAe,UAAU,QAAQ;AAChF,aAAS,UAAU;AACnB,4BAAwB,UAAU;AAClC,WAAO;AAAA,EACT;AAEA,sBAAoB;AAEpB,YAAU,MAAM;AACd,UAAM,UAAU,oBAAoB;AACpC,aAAS,QAAQ,SAAS,CAAC;AAE3B,WAAO,MAAM;AACX,UAAI,SAAS,YAAY,QAAS;AAClC,cAAQ,QAAQ;AAChB,eAAS,UAAU;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,YAAU,MAAM;AACd,QAAI,wBAAwB,YAAY,qBAAsB;AAE9D,UAAM,WAAW,SAAS;AAC1B,UAAM,OAAO,yBAAyB,SAAS,eAAe,UAAU,QAAQ;AAChF,aAAS,UAAU;AACnB,4BAAwB,UAAU;AAClC,aAAS,KAAK,SAAS,CAAC;AACxB,cAAU,QAAQ;AAAA,EACpB,GAAG,CAAC,SAAS,oBAAoB,CAAC;AAElC,QAAM,YAAY,YAAY,OAAO,QAAgB;AACnD,UAAM,oBAAoB,EAAE,KAAK,GAAG;AAAA,EACtC,GAAG,CAAC,CAAC;AAEL,QAAM,OAAO,YAAY,YAAY;AACnC,UAAM,oBAAoB,EAAE,KAAK;AAAA,EACnC,GAAG,CAAC,CAAC;AAEL,QAAM,QAAQ,YAAY,MAAM;AAC9B,wBAAoB,EAAE,MAAM;AAAA,EAC9B,GAAG,CAAC,CAAC;AAEL,QAAM,UAAU,YAAY,CAAC,SAAkB;AAC7C,wBAAoB,EAAE,QAAQ,IAAI;AAAA,EACpC,GAAG,CAAC,CAAC;AAEL,QAAM,OAAO,YAAY,CAAC,YAAoB;AAC5C,wBAAoB,EAAE,KAAK,OAAO;AAAA,EACpC,GAAG,CAAC,CAAC;AAEL,QAAM,cAAc,YAAY,MAAM;AACpC,WAAO,oBAAoB,EAAE,YAAY;AAAA,EAC3C,GAAG,CAAC,CAAC;AAEL,QAAM,iBAAiB,YAAY,MAAM;AACvC,WAAO,oBAAoB,EAAE,eAAe;AAAA,EAC9C,GAAG,CAAC,CAAC;AAEL,QAAM,kBAAkB,YAAY,YAAY;AAC9C,UAAM,oBAAoB,EAAE,gBAAgB;AAAA,EAC9C,GAAG,CAAC,CAAC;AAEL,QAAM,YAAY,YAAY,YAAY;AACxC,UAAM,UAAU,oBAAoB;AACpC,QAAI,QAAQ,SAAS,EAAE,WAAW;AAChC,cAAQ,WAAW;AAAA,IACrB,OAAO;AACL,YAAM,QAAQ,UAAU;AAAA,IAC1B;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,WAAW,YAAY,CAAC,WAAyB;AACrD,WAAO,oBAAoB,EAAE,SAAS,MAAM;AAAA,EAC9C,GAAG,CAAC,CAAC;AAEL,QAAM,WAAW,YAAY,CAAC,WAAyB;AACrD,WAAO,oBAAoB,EAAE,SAAS,MAAM;AAAA,EAC9C,GAAG,CAAC,CAAC;AAEL,QAAM,iBAAiB,YAAY,CAAC,WAAyB;AAC3D,WAAO,oBAAoB,EAAE,eAAe,MAAM;AAAA,EACpD,GAAG,CAAC,CAAC;AAEL,QAAM,aAAa,YAAY,CAAC,WAAyB;AACvD,WAAO,oBAAoB,EAAE,WAAW,MAAM;AAAA,EAChD,GAAG,CAAC,CAAC;AAEL,QAAM,cAAc,YAAY,CAAC,WAAyB;AACxD,WAAO,oBAAoB,EAAE,YAAY,MAAM;AAAA,EACjD,GAAG,CAAC,CAAC;AAEL,SAAO;AAAA,IACL,WAAW,MAAM;AAAA,IACjB,WAAW,MAAM;AAAA,IACjB,UAAU,MAAM;AAAA,IAChB,YAAY,QAAQ,MAAM,aAAa,MAAM,iBAAiB,MAAM,QAAQ;AAAA,IAC5E,WAAW,MAAM;AAAA,IACjB,eAAe,MAAM;AAAA,IACrB,UAAU,MAAM;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
@@ -11,8 +11,14 @@ type Bands = {
11
11
  high: number;
12
12
  overall: number;
13
13
  };
14
+ type AudioBandsSnapshot = {
15
+ bands: Bands;
16
+ customBands: Record<string, number>;
17
+ fft: Uint8Array<ArrayBuffer> | null;
18
+ waveform: Uint8Array<ArrayBuffer> | null;
19
+ };
14
20
  type AudioSource = 'music' | 'mic';
15
- type AudioBandsErrorKind = 'load' | 'mic' | 'lifecycle' | 'config';
21
+ type AudioBandsErrorKind = 'load' | 'playback' | 'mic' | 'lifecycle' | 'config';
16
22
  type AudioBandsErrorCode = 'load_error' | 'playback_error' | 'mic_error' | 'destroyed' | 'unsupported_audio_context' | 'invalid_config';
17
23
  type AudioAnalyserConfig = {
18
24
  fftSize?: number;
@@ -33,6 +39,7 @@ type AudioBandsState = {
33
39
  micActive: boolean;
34
40
  hasTrack: boolean;
35
41
  loadError: AudioBandsError | null;
42
+ playbackError: AudioBandsError | null;
36
43
  micError: AudioBandsError | null;
37
44
  };
38
45
  type AudioBandsCallbacks = {
@@ -40,6 +47,7 @@ type AudioBandsCallbacks = {
40
47
  onPause?: () => void;
41
48
  onError?: (error: AudioBandsError) => void;
42
49
  onLoadError?: (error: AudioBandsError) => void;
50
+ onPlaybackError?: (error: AudioBandsError) => void;
43
51
  onMicError?: (error: AudioBandsError) => void;
44
52
  onMicStart?: () => void;
45
53
  onMicStop?: () => void;
@@ -52,4 +60,4 @@ type AudioBandsOptions = AudioBandsCallbacks & {
52
60
  customBands?: CustomBandRanges;
53
61
  };
54
62
 
55
- export { AudioBandsError as A, type Bands as B, type ClassicBandRanges as C, type AudioBandsState as a, type AudioSource as b, type AudioBandsOptions as c, type AudioAnalyserConfig as d, type AudioBandsCallbacks as e, type AudioBandsErrorCode as f, type AudioBandsErrorKind as g, type BandRange as h, type CustomBandRanges as i };
63
+ export { AudioBandsError as A, type Bands as B, type ClassicBandRanges as C, type AudioBandsState as a, type AudioSource as b, type AudioBandsSnapshot as c, type AudioBandsOptions as d, type AudioAnalyserConfig as e, type AudioBandsCallbacks as f, type AudioBandsErrorCode as g, type AudioBandsErrorKind as h, type BandRange as i, type CustomBandRanges as j };
@@ -11,8 +11,14 @@ type Bands = {
11
11
  high: number;
12
12
  overall: number;
13
13
  };
14
+ type AudioBandsSnapshot = {
15
+ bands: Bands;
16
+ customBands: Record<string, number>;
17
+ fft: Uint8Array<ArrayBuffer> | null;
18
+ waveform: Uint8Array<ArrayBuffer> | null;
19
+ };
14
20
  type AudioSource = 'music' | 'mic';
15
- type AudioBandsErrorKind = 'load' | 'mic' | 'lifecycle' | 'config';
21
+ type AudioBandsErrorKind = 'load' | 'playback' | 'mic' | 'lifecycle' | 'config';
16
22
  type AudioBandsErrorCode = 'load_error' | 'playback_error' | 'mic_error' | 'destroyed' | 'unsupported_audio_context' | 'invalid_config';
17
23
  type AudioAnalyserConfig = {
18
24
  fftSize?: number;
@@ -33,6 +39,7 @@ type AudioBandsState = {
33
39
  micActive: boolean;
34
40
  hasTrack: boolean;
35
41
  loadError: AudioBandsError | null;
42
+ playbackError: AudioBandsError | null;
36
43
  micError: AudioBandsError | null;
37
44
  };
38
45
  type AudioBandsCallbacks = {
@@ -40,6 +47,7 @@ type AudioBandsCallbacks = {
40
47
  onPause?: () => void;
41
48
  onError?: (error: AudioBandsError) => void;
42
49
  onLoadError?: (error: AudioBandsError) => void;
50
+ onPlaybackError?: (error: AudioBandsError) => void;
43
51
  onMicError?: (error: AudioBandsError) => void;
44
52
  onMicStart?: () => void;
45
53
  onMicStop?: () => void;
@@ -52,4 +60,4 @@ type AudioBandsOptions = AudioBandsCallbacks & {
52
60
  customBands?: CustomBandRanges;
53
61
  };
54
62
 
55
- export { AudioBandsError as A, type Bands as B, type ClassicBandRanges as C, type AudioBandsState as a, type AudioSource as b, type AudioBandsOptions as c, type AudioAnalyserConfig as d, type AudioBandsCallbacks as e, type AudioBandsErrorCode as f, type AudioBandsErrorKind as g, type BandRange as h, type CustomBandRanges as i };
63
+ export { AudioBandsError as A, type Bands as B, type ClassicBandRanges as C, type AudioBandsState as a, type AudioSource as b, type AudioBandsSnapshot as c, type AudioBandsOptions as d, type AudioAnalyserConfig as e, type AudioBandsCallbacks as f, type AudioBandsErrorCode as g, type AudioBandsErrorKind as h, type BandRange as i, type CustomBandRanges as j };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juandinella/audio-bands",
3
- "version": "0.4.7",
3
+ "version": "0.6.2",
4
4
  "description": "Headless browser audio analysis. Get bass/mid/high bands from music or mic, with an optional React hook.",
5
5
  "keywords": [
6
6
  "react",
@@ -19,6 +19,10 @@
19
19
  "type": "git",
20
20
  "url": "https://github.com/juandinella/audio-bands"
21
21
  },
22
+ "homepage": "https://github.com/juandinella/audio-bands#readme",
23
+ "bugs": {
24
+ "url": "https://github.com/juandinella/audio-bands/issues"
25
+ },
22
26
  "type": "module",
23
27
  "main": "./dist/index.cjs",
24
28
  "module": "./dist/index.js",
@@ -46,7 +50,9 @@
46
50
  "scripts": {
47
51
  "build": "tsup",
48
52
  "dev": "tsup --watch",
49
- "test": "npm run build && vitest run",
53
+ "test": "npm run build && vitest run tests/package.test.ts tests/core.test.ts tests/react.test.ts",
54
+ "test:browser": "npm run build && playwright test",
55
+ "test:browser:serve": "vite --config tests/browser/vite.config.ts --host 127.0.0.1 --port 4173",
50
56
  "prepack": "npm run build"
51
57
  },
52
58
  "peerDependencies": {
@@ -58,10 +64,17 @@
58
64
  }
59
65
  },
60
66
  "devDependencies": {
67
+ "@playwright/test": "^1.59.1",
68
+ "@testing-library/react": "^16.3.2",
61
69
  "@types/react": "^19.0.0",
70
+ "@types/react-dom": "^19.2.3",
71
+ "@vitejs/plugin-react": "^5.0.4",
62
72
  "jsdom": "^27.0.1",
73
+ "react": "^19.2.4",
74
+ "react-dom": "^19.2.4",
63
75
  "tsup": "^8.0.0",
64
76
  "typescript": "^5.0.0",
77
+ "vite": "^7.3.1",
65
78
  "vitest": "^3.2.4"
66
79
  }
67
80
  }
@@ -1 +0,0 @@
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 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 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 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);\n }\n\n this.teardownMusic();\n\n const audio = new Audio();\n audio.crossOrigin = 'anonymous';\n audio.src = url;\n audio.loop = true;\n this.audioEl = audio;\n this.setState({ hasTrack: true, loadError: null });\n\n const source = ctx.createMediaElementSource(audio);\n source.connect(this.musicAnalyser!);\n this.musicSource = source;\n\n try {\n await audio.play();\n this.setState({ isPlaying: true, loadError: null });\n this.options.onPlay?.();\n } catch (error) {\n throw this.handleError('load', error, 'load_error');\n }\n }\n\n togglePlayPause(): void {\n const audio = this.audioEl;\n if (!audio) return;\n\n if (audio.paused) {\n void audio\n .play()\n .then(() => {\n this.setState({ isPlaying: true, loadError: null });\n this.options.onPlay?.();\n })\n .catch((error) => {\n this.handleError('load', error, 'playback_error');\n });\n return;\n }\n\n audio.pause();\n this.setState({ isPlaying: false });\n this.options.onPause?.();\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 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' | 'mic',\n error: unknown,\n fallbackCode: 'load_error' | 'playback_error' | 'mic_error' = kind === 'mic'\n ? 'mic_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 : 'Failed to load or play audio track',\n error,\n );\n\n if (kind === 'load') {\n this.setState({ isPlaying: false, loadError: wrapped });\n this.options.onLoadError?.(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({ isPlaying: false, hasTrack: false });\n }\n}\n"],"mappings":"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","names":[]}