@lucaismyname/ginger 0.0.29 → 0.0.31

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.
Files changed (67) hide show
  1. package/README.md +140 -8
  2. package/dist/analyzer/liveAudioGraph.d.ts +14 -1
  3. package/dist/analyzer/liveAudioGraph.d.ts.map +1 -1
  4. package/dist/analyzer/useGingerLiveAnalyzer.d.ts +6 -0
  5. package/dist/analyzer/useGingerLiveAnalyzer.d.ts.map +1 -1
  6. package/dist/client.cjs +1 -1
  7. package/dist/client.js +36 -29
  8. package/dist/client.js.map +1 -1
  9. package/dist/components/playlist/GingerPlaylist.d.ts.map +1 -1
  10. package/dist/context/GingerProvider.d.ts +1 -1
  11. package/dist/context/GingerProvider.d.ts.map +1 -1
  12. package/dist/core/playbackReducer.d.ts.map +1 -1
  13. package/dist/equalizer/index.cjs +2 -0
  14. package/dist/equalizer/index.cjs.map +1 -0
  15. package/dist/equalizer/index.d.ts +3 -0
  16. package/dist/equalizer/index.d.ts.map +1 -0
  17. package/dist/equalizer/index.js +51 -0
  18. package/dist/equalizer/index.js.map +1 -0
  19. package/dist/equalizer/useGingerEqualizer.d.ts +41 -0
  20. package/dist/equalizer/useGingerEqualizer.d.ts.map +1 -0
  21. package/dist/ginger-L2ZFgzH4.js +2223 -0
  22. package/dist/ginger-L2ZFgzH4.js.map +1 -0
  23. package/dist/ginger-NEcOSSJD.cjs +2 -0
  24. package/dist/ginger-NEcOSSJD.cjs.map +1 -0
  25. package/dist/hooks/useGingerChapterProgress.d.ts +14 -0
  26. package/dist/hooks/useGingerChapterProgress.d.ts.map +1 -0
  27. package/dist/hooks/useGingerKeyboardShortcuts.d.ts +6 -0
  28. package/dist/hooks/useGingerKeyboardShortcuts.d.ts.map +1 -1
  29. package/dist/hooks/useGingerPlaybackHistory.d.ts +26 -0
  30. package/dist/hooks/useGingerPlaybackHistory.d.ts.map +1 -0
  31. package/dist/hooks/useGingerSleepTimer.d.ts.map +1 -1
  32. package/dist/hooks/useGingerVolumeFade.d.ts +22 -0
  33. package/dist/hooks/useGingerVolumeFade.d.ts.map +1 -0
  34. package/dist/index.cjs +1 -1
  35. package/dist/index.d.ts +18 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +36 -29
  38. package/dist/index.js.map +1 -1
  39. package/dist/liveAudioGraph-CmEsdLgZ.js +150 -0
  40. package/dist/liveAudioGraph-CmEsdLgZ.js.map +1 -0
  41. package/dist/liveAudioGraph-D1BXMv_u.cjs +2 -0
  42. package/dist/liveAudioGraph-D1BXMv_u.cjs.map +1 -0
  43. package/dist/selectors-BalBCc7X.js +127 -0
  44. package/dist/selectors-BalBCc7X.js.map +1 -0
  45. package/dist/selectors-YXnP8Y8g.cjs +2 -0
  46. package/dist/selectors-YXnP8Y8g.cjs.map +1 -0
  47. package/dist/store.d.ts +46 -0
  48. package/dist/store.d.ts.map +1 -0
  49. package/dist/testing/index.cjs +1 -1
  50. package/dist/testing/index.js +1 -1
  51. package/dist/testing/mockWebAudio.d.ts +15 -1
  52. package/dist/testing/mockWebAudio.d.ts.map +1 -1
  53. package/dist/types.d.ts +30 -8
  54. package/dist/types.d.ts.map +1 -1
  55. package/dist/useGingerChapterProgress-BOqUimE7.cjs +2 -0
  56. package/dist/useGingerChapterProgress-BOqUimE7.cjs.map +1 -0
  57. package/dist/useGingerChapterProgress-DLYdGytK.js +321 -0
  58. package/dist/useGingerChapterProgress-DLYdGytK.js.map +1 -0
  59. package/package.json +7 -2
  60. package/dist/ginger-B26HM2Ja.cjs +0 -2
  61. package/dist/ginger-B26HM2Ja.cjs.map +0 -1
  62. package/dist/ginger-DlNYfHbV.js +0 -2278
  63. package/dist/ginger-DlNYfHbV.js.map +0 -1
  64. package/dist/useNextTrackPrefetch-Y_fs2JEx.js +0 -265
  65. package/dist/useNextTrackPrefetch-Y_fs2JEx.js.map +0 -1
  66. package/dist/useNextTrackPrefetch-wSILz6TL.cjs +0 -2
  67. package/dist/useNextTrackPrefetch-wSILz6TL.cjs.map +0 -1
package/dist/index.js CHANGED
@@ -1,43 +1,50 @@
1
- import { C as s, G as r, L as i, P as u, a as n, R as t, S as g, b as l, c, V as o, d as p, W as G, e as S, f as y, g as m, h as d, p as h, u as k, i as L, j as f, k as P, l as b, m as x } from "./ginger-DlNYfHbV.js";
2
- import { a as C, d as v, u as B, b as A, c as F, e as R, f as D, g as T, h as W } from "./useNextTrackPrefetch-Y_fs2JEx.js";
3
- import { g as w, a as z, u as I, b as K, c as M } from "./GingerSplitContexts-BzBExb95.js";
1
+ import { C as s, G as r, L as i, P as u, a as t, R as n, S as g, b as o, c, V as l, d as p, W as G, e as y, f as S, g as d, p as m, u as h, h as P, i as f, j as k, k as L, l as b } from "./ginger-L2ZFgzH4.js";
2
+ import { a as C, d as V, s as v, u as B } from "./liveAudioGraph-CmEsdLgZ.js";
3
+ import { c as A, u as R, a as D, b as T, d as W, e as j, f as w, g as z, h as H, i as I } from "./useGingerChapterProgress-DLYdGytK.js";
4
+ import { d as M } from "./selectors-BalBCc7X.js";
5
+ import { g as U, a as X, u as q, b as E, c as J } from "./GingerSplitContexts-BzBExb95.js";
4
6
  export {
5
7
  s as Chapters,
6
8
  r as Ginger,
7
9
  i as LyricsSynced,
8
10
  u as Pause,
9
- n as Play,
10
- t as RepeatGlyph,
11
+ t as Play,
12
+ n as RepeatGlyph,
11
13
  g as ShuffleIcon,
12
- l as SkipBack,
14
+ o as SkipBack,
13
15
  c as SkipForward,
14
- o as Volume2,
16
+ l as Volume2,
15
17
  p as VolumeX,
16
18
  G as Wrapper,
17
19
  C as attachLiveAnalyser,
18
- S as clampPlaybackRate,
19
- y as clampVolume,
20
- m as defaultGingerLocale,
21
- d as derivePlaybackUiState,
22
- v as detachLiveAnalyser,
23
- w as gingerStateFromContextValues,
24
- z as gingerStateFromContexts,
25
- h as parseLrc,
20
+ y as clampPlaybackRate,
21
+ S as clampVolume,
22
+ A as createGingerStore,
23
+ d as defaultGingerLocale,
24
+ M as derivePlaybackUiState,
25
+ V as detachLiveAnalyser,
26
+ U as gingerStateFromContextValues,
27
+ X as gingerStateFromContexts,
28
+ m as parseLrc,
29
+ v as setProcessingChain,
26
30
  B as useGinger,
27
- k as useGingerChapters,
28
- A as useGingerDebugLog,
29
- F as useGingerKeyboardShortcuts,
30
- R as useGingerLiveAnalyzer,
31
- L as useGingerLocale,
31
+ R as useGingerChapterProgress,
32
+ h as useGingerChapters,
33
+ D as useGingerDebugLog,
34
+ T as useGingerKeyboardShortcuts,
35
+ W as useGingerLiveAnalyzer,
36
+ P as useGingerLocale,
32
37
  f as useGingerLyricsSync,
33
- I as useGingerMedia,
34
- K as useGingerPlayback,
35
- D as useGingerSleepTimer,
36
- M as useGingerState,
37
- T as useNextTrackPrefetch,
38
- P as usePlayPauseBinding,
39
- b as useSeekBarBinding,
40
- W as useSeekDrag,
41
- x as useVolumeSlider
38
+ q as useGingerMedia,
39
+ E as useGingerPlayback,
40
+ j as useGingerPlaybackHistory,
41
+ w as useGingerSleepTimer,
42
+ J as useGingerState,
43
+ z as useGingerVolumeFade,
44
+ H as useNextTrackPrefetch,
45
+ k as usePlayPauseBinding,
46
+ L as useSeekBarBinding,
47
+ I as useSeekDrag,
48
+ b as useVolumeSlider
42
49
  };
43
50
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;"}
@@ -0,0 +1,150 @@
1
+ import { useMemo as g } from "react";
2
+ import { b as d, u as m, g as p } from "./GingerSplitContexts-BzBExb95.js";
3
+ import { r as y, a as h, p as k, e as v, b as A, d as b, g as x } from "./selectors-BalBCc7X.js";
4
+ function R() {
5
+ const e = d(), t = m();
6
+ return g(() => {
7
+ const n = p(e, t);
8
+ return {
9
+ state: n,
10
+ currentTrack: x(n),
11
+ playbackUi: b(n),
12
+ duration: A(n),
13
+ remaining: v(n),
14
+ progress: k(n),
15
+ artworkUrl: h(n),
16
+ albumLine: y(n),
17
+ play: e.play,
18
+ pause: e.pause,
19
+ togglePlayPause: e.togglePlayPause,
20
+ seek: t.seek,
21
+ setVolume: t.setVolume,
22
+ setMuted: t.setMuted,
23
+ toggleMute: t.toggleMute,
24
+ setPlaybackRate: t.setPlaybackRate,
25
+ next: e.next,
26
+ prev: e.prev,
27
+ setRepeatMode: e.setRepeatMode,
28
+ cycleRepeat: e.cycleRepeat,
29
+ toggleShuffle: e.toggleShuffle,
30
+ setQueue: e.setQueue,
31
+ insertTrackAt: e.insertTrackAt,
32
+ removeTrackAt: e.removeTrackAt,
33
+ moveTrack: e.moveTrack,
34
+ enqueueNext: e.enqueueNext,
35
+ playTrackAt: e.playTrackAt,
36
+ selectTrackAt: e.selectTrackAt,
37
+ setPlaylistMeta: e.setPlaylistMeta,
38
+ setPlaybackMode: e.setPlaybackMode,
39
+ init: e.init,
40
+ audioRef: t.audioRef,
41
+ dispatch: e.dispatch
42
+ };
43
+ }, [e, t]);
44
+ }
45
+ const r = /* @__PURE__ */ new WeakMap();
46
+ function M(e) {
47
+ const t = 2 ** Math.round(Math.log2(e));
48
+ return Math.min(32768, Math.max(32, t));
49
+ }
50
+ function C(e) {
51
+ const { processingChain: t } = e;
52
+ return t.length > 0 ? t[t.length - 1] : e.source;
53
+ }
54
+ function i(e) {
55
+ const { source: t, processingChain: n, consumers: s, context: a } = e;
56
+ try {
57
+ t.disconnect();
58
+ } catch {
59
+ }
60
+ for (const c of n)
61
+ try {
62
+ c.disconnect();
63
+ } catch {
64
+ }
65
+ for (const { analyser: c } of s.values())
66
+ try {
67
+ c.disconnect(a.destination);
68
+ } catch {
69
+ }
70
+ if (n.length > 0) {
71
+ t.connect(n[0]);
72
+ for (let c = 0; c < n.length - 1; c++)
73
+ n[c].connect(n[c + 1]);
74
+ }
75
+ const o = C(e);
76
+ if (s.size === 0)
77
+ o.connect(a.destination);
78
+ else {
79
+ let c = !0;
80
+ for (const { analyser: u } of s.values())
81
+ o.connect(u), c && (u.connect(a.destination), c = !1);
82
+ }
83
+ }
84
+ function l(e) {
85
+ let t = r.get(e);
86
+ if (!t) {
87
+ const n = window.AudioContext ?? window.webkitAudioContext;
88
+ if (!n)
89
+ throw new Error("Web Audio API is not available");
90
+ const s = new n(), a = s.createMediaElementSource(e);
91
+ t = {
92
+ context: s,
93
+ source: a,
94
+ consumers: /* @__PURE__ */ new Map(),
95
+ nextId: 0,
96
+ processingChain: [],
97
+ processingActive: !1
98
+ }, r.set(e, t);
99
+ }
100
+ return t;
101
+ }
102
+ function f(e, t) {
103
+ if (t.consumers.size === 0 && !t.processingActive) {
104
+ try {
105
+ t.source.disconnect();
106
+ } catch {
107
+ }
108
+ t.context.close(), r.delete(e);
109
+ }
110
+ }
111
+ function S(e, t) {
112
+ const n = l(e), { context: s } = n, a = s.createAnalyser();
113
+ a.fftSize = M(t.fftSize), a.smoothingTimeConstant = t.smoothingTimeConstant, a.minDecibels = t.minDecibels, a.maxDecibels = t.maxDecibels;
114
+ const o = n.nextId;
115
+ return n.nextId += 1, n.consumers.set(o, { analyser: a }), i(n), { id: o, context: s, analyser: a };
116
+ }
117
+ function z(e, t) {
118
+ const n = r.get(e);
119
+ if (!n) return;
120
+ const s = n.consumers.get(t);
121
+ if (s) {
122
+ try {
123
+ s.analyser.disconnect();
124
+ } catch {
125
+ }
126
+ if (n.consumers.delete(t), n.consumers.size === 0 && !n.processingActive) {
127
+ f(e, n);
128
+ return;
129
+ }
130
+ i(n);
131
+ }
132
+ }
133
+ function D(e, t) {
134
+ if (typeof window > "u") return;
135
+ if (t.length === 0) {
136
+ const s = r.get(e);
137
+ if (!s) return;
138
+ s.processingChain = [], s.processingActive = !1, s.consumers.size === 0 ? f(e, s) : i(s);
139
+ return;
140
+ }
141
+ const n = l(e);
142
+ n.processingChain = t, n.processingActive = !0, i(n);
143
+ }
144
+ export {
145
+ S as a,
146
+ z as d,
147
+ D as s,
148
+ R as u
149
+ };
150
+ //# sourceMappingURL=liveAudioGraph-CmEsdLgZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"liveAudioGraph-CmEsdLgZ.js","sources":["../src/hooks/useGinger.ts","../src/analyzer/liveAudioGraph.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport {\n gingerStateFromContextValues,\n useGingerMedia,\n useGingerPlayback,\n} from \"../context/GingerSplitContexts\";\nimport {\n derivePlaybackUiState,\n effectiveDuration,\n effectiveRemaining,\n getCurrentTrack,\n progressFraction,\n resolvedAlbumLine,\n resolvedArtwork,\n} from \"../internal/selectors\";\n\nexport function useGinger() {\n const pb = useGingerPlayback();\n const md = useGingerMedia();\n\n return useMemo(() => {\n const state = gingerStateFromContextValues(pb, md);\n return {\n state,\n currentTrack: getCurrentTrack(state),\n playbackUi: derivePlaybackUiState(state),\n duration: effectiveDuration(state),\n remaining: effectiveRemaining(state),\n progress: progressFraction(state),\n artworkUrl: resolvedArtwork(state),\n albumLine: resolvedAlbumLine(state),\n play: pb.play,\n pause: pb.pause,\n togglePlayPause: pb.togglePlayPause,\n seek: md.seek,\n setVolume: md.setVolume,\n setMuted: md.setMuted,\n toggleMute: md.toggleMute,\n setPlaybackRate: md.setPlaybackRate,\n next: pb.next,\n prev: pb.prev,\n setRepeatMode: pb.setRepeatMode,\n cycleRepeat: pb.cycleRepeat,\n toggleShuffle: pb.toggleShuffle,\n setQueue: pb.setQueue,\n insertTrackAt: pb.insertTrackAt,\n removeTrackAt: pb.removeTrackAt,\n moveTrack: pb.moveTrack,\n enqueueNext: pb.enqueueNext,\n playTrackAt: pb.playTrackAt,\n selectTrackAt: pb.selectTrackAt,\n setPlaylistMeta: pb.setPlaylistMeta,\n setPlaybackMode: pb.setPlaybackMode,\n init: pb.init,\n audioRef: md.audioRef,\n dispatch: pb.dispatch,\n };\n }, [pb, md]);\n}\n","/**\n * One MediaElementAudioSourceNode per HTMLAudioElement; multiple AnalyserNodes may tap the source.\n * An optional processing chain (e.g. EQ filters) can be inserted between the source and the\n * analysers via `setProcessingChain`.\n */\n\nexport type LiveAnalyserOptions = {\n fftSize: number;\n smoothingTimeConstant: number;\n minDecibels: number;\n maxDecibels: number;\n};\n\ntype Consumer = {\n analyser: AnalyserNode;\n};\n\ntype ElementEntry = {\n context: AudioContext;\n source: MediaElementAudioSourceNode;\n consumers: Map<number, Consumer>;\n nextId: number;\n /** Ordered processing nodes (e.g. BiquadFilterNode[]) inserted between source and analysers. */\n processingChain: AudioNode[];\n /** True while a processing chain is installed; prevents context teardown when no consumers exist. */\n processingActive: boolean;\n};\n\nconst entries = new WeakMap<HTMLAudioElement, ElementEntry>();\n\nfunction clampFftSize(n: number): number {\n const p = 2 ** Math.round(Math.log2(n));\n return Math.min(32768, Math.max(32, p));\n}\n\nfunction getChainTail(entry: ElementEntry): AudioNode {\n const { processingChain } = entry;\n return processingChain.length > 0 ? processingChain[processingChain.length - 1]! : entry.source;\n}\n\n/**\n * Rebuild all graph connections from scratch.\n * Call after any structural change (add/remove consumer, change processing chain).\n */\nfunction rebuildGraph(entry: ElementEntry): void {\n const { source, processingChain, consumers, context } = entry;\n\n // Disconnect source outputs\n try {\n source.disconnect();\n } catch {\n // ignore\n }\n\n // Disconnect processing chain node outputs\n for (const node of processingChain) {\n try {\n node.disconnect();\n } catch {\n // ignore\n }\n }\n\n // Disconnect all analysers from destination (we will reconnect selectively below)\n for (const { analyser } of consumers.values()) {\n try {\n analyser.disconnect(context.destination);\n } catch {\n // ignore\n }\n }\n\n // Build processing chain: source → node[0] → ... → node[N]\n if (processingChain.length > 0) {\n source.connect(processingChain[0]!);\n for (let i = 0; i < processingChain.length - 1; i++) {\n processingChain[i]!.connect(processingChain[i + 1]!);\n }\n }\n\n const tail = getChainTail(entry);\n\n if (consumers.size === 0) {\n // No analyser consumers: route tail directly to destination so audio is audible\n tail.connect(context.destination);\n } else {\n // Connect tail to all analysers; first one routes to destination (playback sink)\n let isFirst = true;\n for (const { analyser } of consumers.values()) {\n tail.connect(analyser);\n if (isFirst) {\n analyser.connect(context.destination);\n isFirst = false;\n }\n }\n }\n}\n\nfunction getOrCreateEntry(element: HTMLAudioElement): ElementEntry {\n let entry = entries.get(element);\n if (!entry) {\n const Context =\n window.AudioContext ??\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Context) {\n throw new Error(\"Web Audio API is not available\");\n }\n const context = new Context();\n const source = context.createMediaElementSource(element);\n entry = {\n context,\n source,\n consumers: new Map(),\n nextId: 0,\n processingChain: [],\n processingActive: false,\n };\n entries.set(element, entry);\n }\n return entry;\n}\n\nfunction maybeCloseEntry(element: HTMLAudioElement, entry: ElementEntry): void {\n if (entry.consumers.size === 0 && !entry.processingActive) {\n try {\n entry.source.disconnect();\n } catch {\n // ignore\n }\n void entry.context.close();\n entries.delete(element);\n }\n}\n\nexport function attachLiveAnalyser(\n element: HTMLAudioElement,\n options: LiveAnalyserOptions,\n): { id: number; context: AudioContext; analyser: AnalyserNode } {\n const entry = getOrCreateEntry(element);\n const { context } = entry;\n\n const analyser = context.createAnalyser();\n analyser.fftSize = clampFftSize(options.fftSize);\n analyser.smoothingTimeConstant = options.smoothingTimeConstant;\n analyser.minDecibels = options.minDecibels;\n analyser.maxDecibels = options.maxDecibels;\n\n const id = entry.nextId;\n entry.nextId += 1;\n entry.consumers.set(id, { analyser });\n\n rebuildGraph(entry);\n\n return { id, context, analyser };\n}\n\nexport function detachLiveAnalyser(element: HTMLAudioElement, id: number): void {\n const entry = entries.get(element);\n if (!entry) return;\n\n const consumer = entry.consumers.get(id);\n if (!consumer) return;\n\n try {\n consumer.analyser.disconnect();\n } catch {\n // ignore\n }\n entry.consumers.delete(id);\n\n if (entry.consumers.size === 0 && !entry.processingActive) {\n maybeCloseEntry(element, entry);\n return;\n }\n\n rebuildGraph(entry);\n}\n\n/**\n * Insert an ordered processing chain (e.g. BiquadFilterNode[]) between the audio source and the\n * analyser consumers. Pass an empty array to clear the chain.\n *\n * Safe to call while analyser consumers are active; the graph is rebuilt immediately.\n * Note: because `createMediaElementSource` can only be called once per element, the EQ and live\n * analyser share the same AudioContext. Calling both for the same element is supported.\n */\nexport function setProcessingChain(element: HTMLAudioElement, nodes: AudioNode[]): void {\n if (typeof window === \"undefined\") return;\n\n if (nodes.length === 0) {\n const entry = entries.get(element);\n if (!entry) return;\n entry.processingChain = [];\n entry.processingActive = false;\n if (entry.consumers.size === 0) {\n maybeCloseEntry(element, entry);\n } else {\n rebuildGraph(entry);\n }\n return;\n }\n\n const entry = getOrCreateEntry(element);\n entry.processingChain = nodes;\n entry.processingActive = true;\n rebuildGraph(entry);\n}\n"],"names":["useGinger","pb","useGingerPlayback","md","useGingerMedia","useMemo","state","gingerStateFromContextValues","getCurrentTrack","derivePlaybackUiState","effectiveDuration","effectiveRemaining","progressFraction","resolvedArtwork","resolvedAlbumLine","entries","clampFftSize","n","p","getChainTail","entry","processingChain","rebuildGraph","source","consumers","context","node","analyser","i","tail","isFirst","getOrCreateEntry","element","Context","maybeCloseEntry","attachLiveAnalyser","options","id","detachLiveAnalyser","consumer","setProcessingChain","nodes"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use strict";const y=require("react"),l=require("./GingerSplitContexts-C7puo0M7.cjs"),i=require("./selectors-YXnP8Y8g.cjs");function h(){const e=l.useGingerPlayback(),t=l.useGingerMedia();return y.useMemo(()=>{const n=l.gingerStateFromContextValues(e,t);return{state:n,currentTrack:i.getCurrentTrack(n),playbackUi:i.derivePlaybackUiState(n),duration:i.effectiveDuration(n),remaining:i.effectiveRemaining(n),progress:i.progressFraction(n),artworkUrl:i.resolvedArtwork(n),albumLine:i.resolvedAlbumLine(n),play:e.play,pause:e.pause,togglePlayPause:e.togglePlayPause,seek:t.seek,setVolume:t.setVolume,setMuted:t.setMuted,toggleMute:t.toggleMute,setPlaybackRate:t.setPlaybackRate,next:e.next,prev:e.prev,setRepeatMode:e.setRepeatMode,cycleRepeat:e.cycleRepeat,toggleShuffle:e.toggleShuffle,setQueue:e.setQueue,insertTrackAt:e.insertTrackAt,removeTrackAt:e.removeTrackAt,moveTrack:e.moveTrack,enqueueNext:e.enqueueNext,playTrackAt:e.playTrackAt,selectTrackAt:e.selectTrackAt,setPlaylistMeta:e.setPlaylistMeta,setPlaybackMode:e.setPlaybackMode,init:e.init,audioRef:t.audioRef,dispatch:e.dispatch}},[e,t])}const a=new WeakMap;function p(e){const t=2**Math.round(Math.log2(e));return Math.min(32768,Math.max(32,t))}function m(e){const{processingChain:t}=e;return t.length>0?t[t.length-1]:e.source}function u(e){const{source:t,processingChain:n,consumers:s,context:c}=e;try{t.disconnect()}catch{}for(const r of n)try{r.disconnect()}catch{}for(const{analyser:r}of s.values())try{r.disconnect(c.destination)}catch{}if(n.length>0){t.connect(n[0]);for(let r=0;r<n.length-1;r++)n[r].connect(n[r+1])}const o=m(e);if(s.size===0)o.connect(c.destination);else{let r=!0;for(const{analyser:g}of s.values())o.connect(g),r&&(g.connect(c.destination),r=!1)}}function f(e){let t=a.get(e);if(!t){const n=window.AudioContext??window.webkitAudioContext;if(!n)throw new Error("Web Audio API is not available");const s=new n,c=s.createMediaElementSource(e);t={context:s,source:c,consumers:new Map,nextId:0,processingChain:[],processingActive:!1},a.set(e,t)}return t}function d(e,t){if(t.consumers.size===0&&!t.processingActive){try{t.source.disconnect()}catch{}t.context.close(),a.delete(e)}}function k(e,t){const n=f(e),{context:s}=n,c=s.createAnalyser();c.fftSize=p(t.fftSize),c.smoothingTimeConstant=t.smoothingTimeConstant,c.minDecibels=t.minDecibels,c.maxDecibels=t.maxDecibels;const o=n.nextId;return n.nextId+=1,n.consumers.set(o,{analyser:c}),u(n),{id:o,context:s,analyser:c}}function v(e,t){const n=a.get(e);if(!n)return;const s=n.consumers.get(t);if(s){try{s.analyser.disconnect()}catch{}if(n.consumers.delete(t),n.consumers.size===0&&!n.processingActive){d(e,n);return}u(n)}}function A(e,t){if(typeof window>"u")return;if(t.length===0){const s=a.get(e);if(!s)return;s.processingChain=[],s.processingActive=!1,s.consumers.size===0?d(e,s):u(s);return}const n=f(e);n.processingChain=t,n.processingActive=!0,u(n)}exports.attachLiveAnalyser=k;exports.detachLiveAnalyser=v;exports.setProcessingChain=A;exports.useGinger=h;
2
+ //# sourceMappingURL=liveAudioGraph-D1BXMv_u.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"liveAudioGraph-D1BXMv_u.cjs","sources":["../src/hooks/useGinger.ts","../src/analyzer/liveAudioGraph.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport {\n gingerStateFromContextValues,\n useGingerMedia,\n useGingerPlayback,\n} from \"../context/GingerSplitContexts\";\nimport {\n derivePlaybackUiState,\n effectiveDuration,\n effectiveRemaining,\n getCurrentTrack,\n progressFraction,\n resolvedAlbumLine,\n resolvedArtwork,\n} from \"../internal/selectors\";\n\nexport function useGinger() {\n const pb = useGingerPlayback();\n const md = useGingerMedia();\n\n return useMemo(() => {\n const state = gingerStateFromContextValues(pb, md);\n return {\n state,\n currentTrack: getCurrentTrack(state),\n playbackUi: derivePlaybackUiState(state),\n duration: effectiveDuration(state),\n remaining: effectiveRemaining(state),\n progress: progressFraction(state),\n artworkUrl: resolvedArtwork(state),\n albumLine: resolvedAlbumLine(state),\n play: pb.play,\n pause: pb.pause,\n togglePlayPause: pb.togglePlayPause,\n seek: md.seek,\n setVolume: md.setVolume,\n setMuted: md.setMuted,\n toggleMute: md.toggleMute,\n setPlaybackRate: md.setPlaybackRate,\n next: pb.next,\n prev: pb.prev,\n setRepeatMode: pb.setRepeatMode,\n cycleRepeat: pb.cycleRepeat,\n toggleShuffle: pb.toggleShuffle,\n setQueue: pb.setQueue,\n insertTrackAt: pb.insertTrackAt,\n removeTrackAt: pb.removeTrackAt,\n moveTrack: pb.moveTrack,\n enqueueNext: pb.enqueueNext,\n playTrackAt: pb.playTrackAt,\n selectTrackAt: pb.selectTrackAt,\n setPlaylistMeta: pb.setPlaylistMeta,\n setPlaybackMode: pb.setPlaybackMode,\n init: pb.init,\n audioRef: md.audioRef,\n dispatch: pb.dispatch,\n };\n }, [pb, md]);\n}\n","/**\n * One MediaElementAudioSourceNode per HTMLAudioElement; multiple AnalyserNodes may tap the source.\n * An optional processing chain (e.g. EQ filters) can be inserted between the source and the\n * analysers via `setProcessingChain`.\n */\n\nexport type LiveAnalyserOptions = {\n fftSize: number;\n smoothingTimeConstant: number;\n minDecibels: number;\n maxDecibels: number;\n};\n\ntype Consumer = {\n analyser: AnalyserNode;\n};\n\ntype ElementEntry = {\n context: AudioContext;\n source: MediaElementAudioSourceNode;\n consumers: Map<number, Consumer>;\n nextId: number;\n /** Ordered processing nodes (e.g. BiquadFilterNode[]) inserted between source and analysers. */\n processingChain: AudioNode[];\n /** True while a processing chain is installed; prevents context teardown when no consumers exist. */\n processingActive: boolean;\n};\n\nconst entries = new WeakMap<HTMLAudioElement, ElementEntry>();\n\nfunction clampFftSize(n: number): number {\n const p = 2 ** Math.round(Math.log2(n));\n return Math.min(32768, Math.max(32, p));\n}\n\nfunction getChainTail(entry: ElementEntry): AudioNode {\n const { processingChain } = entry;\n return processingChain.length > 0 ? processingChain[processingChain.length - 1]! : entry.source;\n}\n\n/**\n * Rebuild all graph connections from scratch.\n * Call after any structural change (add/remove consumer, change processing chain).\n */\nfunction rebuildGraph(entry: ElementEntry): void {\n const { source, processingChain, consumers, context } = entry;\n\n // Disconnect source outputs\n try {\n source.disconnect();\n } catch {\n // ignore\n }\n\n // Disconnect processing chain node outputs\n for (const node of processingChain) {\n try {\n node.disconnect();\n } catch {\n // ignore\n }\n }\n\n // Disconnect all analysers from destination (we will reconnect selectively below)\n for (const { analyser } of consumers.values()) {\n try {\n analyser.disconnect(context.destination);\n } catch {\n // ignore\n }\n }\n\n // Build processing chain: source → node[0] → ... → node[N]\n if (processingChain.length > 0) {\n source.connect(processingChain[0]!);\n for (let i = 0; i < processingChain.length - 1; i++) {\n processingChain[i]!.connect(processingChain[i + 1]!);\n }\n }\n\n const tail = getChainTail(entry);\n\n if (consumers.size === 0) {\n // No analyser consumers: route tail directly to destination so audio is audible\n tail.connect(context.destination);\n } else {\n // Connect tail to all analysers; first one routes to destination (playback sink)\n let isFirst = true;\n for (const { analyser } of consumers.values()) {\n tail.connect(analyser);\n if (isFirst) {\n analyser.connect(context.destination);\n isFirst = false;\n }\n }\n }\n}\n\nfunction getOrCreateEntry(element: HTMLAudioElement): ElementEntry {\n let entry = entries.get(element);\n if (!entry) {\n const Context =\n window.AudioContext ??\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Context) {\n throw new Error(\"Web Audio API is not available\");\n }\n const context = new Context();\n const source = context.createMediaElementSource(element);\n entry = {\n context,\n source,\n consumers: new Map(),\n nextId: 0,\n processingChain: [],\n processingActive: false,\n };\n entries.set(element, entry);\n }\n return entry;\n}\n\nfunction maybeCloseEntry(element: HTMLAudioElement, entry: ElementEntry): void {\n if (entry.consumers.size === 0 && !entry.processingActive) {\n try {\n entry.source.disconnect();\n } catch {\n // ignore\n }\n void entry.context.close();\n entries.delete(element);\n }\n}\n\nexport function attachLiveAnalyser(\n element: HTMLAudioElement,\n options: LiveAnalyserOptions,\n): { id: number; context: AudioContext; analyser: AnalyserNode } {\n const entry = getOrCreateEntry(element);\n const { context } = entry;\n\n const analyser = context.createAnalyser();\n analyser.fftSize = clampFftSize(options.fftSize);\n analyser.smoothingTimeConstant = options.smoothingTimeConstant;\n analyser.minDecibels = options.minDecibels;\n analyser.maxDecibels = options.maxDecibels;\n\n const id = entry.nextId;\n entry.nextId += 1;\n entry.consumers.set(id, { analyser });\n\n rebuildGraph(entry);\n\n return { id, context, analyser };\n}\n\nexport function detachLiveAnalyser(element: HTMLAudioElement, id: number): void {\n const entry = entries.get(element);\n if (!entry) return;\n\n const consumer = entry.consumers.get(id);\n if (!consumer) return;\n\n try {\n consumer.analyser.disconnect();\n } catch {\n // ignore\n }\n entry.consumers.delete(id);\n\n if (entry.consumers.size === 0 && !entry.processingActive) {\n maybeCloseEntry(element, entry);\n return;\n }\n\n rebuildGraph(entry);\n}\n\n/**\n * Insert an ordered processing chain (e.g. BiquadFilterNode[]) between the audio source and the\n * analyser consumers. Pass an empty array to clear the chain.\n *\n * Safe to call while analyser consumers are active; the graph is rebuilt immediately.\n * Note: because `createMediaElementSource` can only be called once per element, the EQ and live\n * analyser share the same AudioContext. Calling both for the same element is supported.\n */\nexport function setProcessingChain(element: HTMLAudioElement, nodes: AudioNode[]): void {\n if (typeof window === \"undefined\") return;\n\n if (nodes.length === 0) {\n const entry = entries.get(element);\n if (!entry) return;\n entry.processingChain = [];\n entry.processingActive = false;\n if (entry.consumers.size === 0) {\n maybeCloseEntry(element, entry);\n } else {\n rebuildGraph(entry);\n }\n return;\n }\n\n const entry = getOrCreateEntry(element);\n entry.processingChain = nodes;\n entry.processingActive = true;\n rebuildGraph(entry);\n}\n"],"names":["useGinger","pb","useGingerPlayback","md","useGingerMedia","useMemo","state","gingerStateFromContextValues","getCurrentTrack","derivePlaybackUiState","effectiveDuration","effectiveRemaining","progressFraction","resolvedArtwork","resolvedAlbumLine","entries","clampFftSize","n","p","getChainTail","entry","processingChain","rebuildGraph","source","consumers","context","node","analyser","i","tail","isFirst","getOrCreateEntry","element","Context","maybeCloseEntry","attachLiveAnalyser","options","id","detachLiveAnalyser","consumer","setProcessingChain","nodes"],"mappings":"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"}
@@ -0,0 +1,127 @@
1
+ function l(n, e) {
2
+ return e <= 0 ? 0 : Math.max(0, Math.min(e - 1, n));
3
+ }
4
+ function g(n, e) {
5
+ if (n.length <= 1) return [...n];
6
+ const r = n[e];
7
+ if (!r) return [...n];
8
+ const t = n.filter((i, u) => u !== e);
9
+ for (let i = t.length - 1; i > 0; i--) {
10
+ const u = Math.floor(Math.random() * (i + 1));
11
+ [t[i], t[u]] = [t[u], t[i]];
12
+ }
13
+ return [r, ...t];
14
+ }
15
+ function c(n) {
16
+ return n ? n.id != null && n.id !== "" ? `id:${n.id}` : `file:${n.fileUrl}` : "";
17
+ }
18
+ function x(n, e) {
19
+ var f, s;
20
+ if (!e) return 0;
21
+ const r = n.findIndex((o) => o === e);
22
+ if (r !== -1) return r;
23
+ const t = c(e);
24
+ if (!t) return 0;
25
+ const i = [];
26
+ for (let o = 0; o < n.length; o += 1)
27
+ c(n[o]) === t && i.push(o);
28
+ if (i.length === 0) return 0;
29
+ if (i.length === 1) return i[0];
30
+ const u = typeof globalThis < "u" && "process" in globalThis ? (s = (f = globalThis.process) == null ? void 0 : f.env) == null ? void 0 : s.NODE_ENV : void 0;
31
+ return u != null && u !== "production" && console.warn(
32
+ "[@lucaismyname/ginger] Ambiguous track identity: multiple queue rows share the same fileUrl without a unique `id`. Resolving to the first match."
33
+ ), i[0];
34
+ }
35
+ function h(n, e, r) {
36
+ const t = [...n], i = Math.max(0, Math.min(t.length, r ?? t.length));
37
+ return t.splice(i, 0, e), t;
38
+ }
39
+ function M(n, e) {
40
+ if (e < 0 || e >= n.length) return [...n];
41
+ const r = [...n];
42
+ return r.splice(e, 1), r;
43
+ }
44
+ function b(n, e, r) {
45
+ if (e === r || e < 0 || e >= n.length || r < 0 || r >= n.length)
46
+ return [...n];
47
+ const t = [...n], [i] = t.splice(e, 1);
48
+ return i ? (t.splice(r, 0, i), t) : [...n];
49
+ }
50
+ function y(n, e, r) {
51
+ return h(n, r, Math.max(0, Math.min(n.length, e + 1)));
52
+ }
53
+ function v(n) {
54
+ const { tracks: e, currentIndex: r, repeatMode: t, playbackMode: i } = n, u = e.length;
55
+ return u === 0 ? { kind: "stop", nextIndex: 0 } : t === "one" ? { kind: "replay_same" } : i === "single" ? { kind: "stop", nextIndex: l(r, u) } : r < u - 1 ? { kind: "advance", nextIndex: r + 1 } : t === "all" ? { kind: "wrap", nextIndex: 0 } : { kind: "stop", nextIndex: l(r, u) };
56
+ }
57
+ function T(n) {
58
+ const { tracks: e, currentIndex: r, repeatMode: t, playbackMode: i } = n, u = e.length;
59
+ return u === 0 ? 0 : i === "single" ? l(r, u) : r < u - 1 ? r + 1 : t === "all" ? 0 : l(r, u);
60
+ }
61
+ function I(n) {
62
+ const { tracks: e, currentIndex: r, repeatMode: t, playbackMode: i } = n, u = e.length;
63
+ return u === 0 ? 0 : i === "single" ? l(r, u) : r > 0 ? r - 1 : t === "all" ? u - 1 : 0;
64
+ }
65
+ function w(n) {
66
+ return n === "off" ? "all" : n === "all" ? "one" : "off";
67
+ }
68
+ function m(n, e) {
69
+ return (n == null ? void 0 : n.artworkUrl) ?? e ?? void 0;
70
+ }
71
+ function p(n, e) {
72
+ return (n == null ? void 0 : n.album) ?? e ?? void 0;
73
+ }
74
+ function a(n) {
75
+ return n.tracks[n.currentIndex] ?? null;
76
+ }
77
+ function A(n) {
78
+ return n.errorMessage ? "error" : n.tracks.length === 0 ? "idle" : n.isBuffering ? "loading" : n.isPaused ? Number.isFinite(n.duration) && n.duration > 0 && n.currentTime >= n.duration - 0.05 ? "ended" : "paused" : "playing";
79
+ }
80
+ function d(n) {
81
+ var t;
82
+ const e = n.duration;
83
+ if (Number.isFinite(e) && e > 0) return e;
84
+ const r = (t = n.tracks[n.currentIndex]) == null ? void 0 : t.durationSeconds;
85
+ return typeof r == "number" && Number.isFinite(r) && r > 0 ? r : 0;
86
+ }
87
+ function N(n) {
88
+ const r = d(n) - n.currentTime;
89
+ return Number.isFinite(r) ? Math.max(0, r) : 0;
90
+ }
91
+ function k(n) {
92
+ const e = d(n);
93
+ return e > 0 ? Math.min(1, Math.max(0, n.currentTime / e)) : 0;
94
+ }
95
+ function U(n) {
96
+ var r;
97
+ const e = a(n);
98
+ return m(e, (r = n.playlistMeta) == null ? void 0 : r.artworkUrl);
99
+ }
100
+ function F(n) {
101
+ var r;
102
+ const e = a(n);
103
+ return p(e, (r = n.playlistMeta) == null ? void 0 : r.subtitle);
104
+ }
105
+ export {
106
+ U as a,
107
+ d as b,
108
+ T as c,
109
+ A as d,
110
+ N as e,
111
+ I as f,
112
+ a as g,
113
+ x as h,
114
+ l as i,
115
+ w as j,
116
+ y as k,
117
+ M as l,
118
+ b as m,
119
+ h as n,
120
+ m as o,
121
+ k as p,
122
+ v as q,
123
+ F as r,
124
+ g as s,
125
+ c as t
126
+ };
127
+ //# sourceMappingURL=selectors-BalBCc7X.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selectors-BalBCc7X.js","sources":["../src/core/queue.ts","../src/core/transitions.ts","../src/internal/selectors.ts"],"sourcesContent":["import type { Track } from \"../types\";\n\nexport function clampIndex(index: number, length: number): number {\n if (length <= 0) return 0;\n return Math.max(0, Math.min(length - 1, index));\n}\n\nexport function shuffleWithAnchor(tracks: Track[], anchorIndex: number): Track[] {\n if (tracks.length <= 1) return [...tracks];\n const anchor = tracks[anchorIndex];\n if (!anchor) return [...tracks];\n const rest = tracks.filter((_, i) => i !== anchorIndex);\n for (let i = rest.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [rest[i], rest[j]] = [rest[j]!, rest[i]!];\n }\n return [anchor, ...rest];\n}\n\nexport function trackIdentity(track: Track | null | undefined): string {\n if (!track) return \"\";\n return track.id != null && track.id !== \"\" ? `id:${track.id}` : `file:${track.fileUrl}`;\n}\n\nexport function findIndexByTrackIdentity(\n tracks: Track[],\n target: Track | null | undefined,\n): number {\n if (!target) return 0;\n const byRef = tracks.findIndex((t) => t === target);\n if (byRef !== -1) return byRef;\n\n const identity = trackIdentity(target);\n if (!identity) return 0;\n\n const matches: number[] = [];\n for (let i = 0; i < tracks.length; i += 1) {\n if (trackIdentity(tracks[i]) === identity) matches.push(i);\n }\n if (matches.length === 0) return 0;\n if (matches.length === 1) return matches[0]!;\n\n const nodeEnv =\n typeof globalThis !== \"undefined\" && \"process\" in globalThis\n ? (globalThis as { process?: { env?: { NODE_ENV?: string } } }).process?.env?.NODE_ENV\n : undefined;\n if (nodeEnv != null && nodeEnv !== \"production\") {\n console.warn(\n \"[@lucaismyname/ginger] Ambiguous track identity: multiple queue rows share the same fileUrl without a unique `id`. Resolving to the first match.\",\n );\n }\n return matches[0]!;\n}\n\nexport function insertTrackAt(tracks: Track[], track: Track, index?: number): Track[] {\n const next = [...tracks];\n const at = Math.max(0, Math.min(next.length, index ?? next.length));\n next.splice(at, 0, track);\n return next;\n}\n\nexport function removeTrackAt(tracks: Track[], index: number): Track[] {\n if (index < 0 || index >= tracks.length) return [...tracks];\n const next = [...tracks];\n next.splice(index, 1);\n return next;\n}\n\nexport function moveTrack(tracks: Track[], fromIndex: number, toIndex: number): Track[] {\n if (\n fromIndex === toIndex ||\n fromIndex < 0 ||\n fromIndex >= tracks.length ||\n toIndex < 0 ||\n toIndex >= tracks.length\n ) {\n return [...tracks];\n }\n const next = [...tracks];\n const [item] = next.splice(fromIndex, 1);\n if (!item) return [...tracks];\n next.splice(toIndex, 0, item);\n return next;\n}\n\nexport function addNextTrack(tracks: Track[], currentIndex: number, track: Track): Track[] {\n return insertTrackAt(tracks, track, Math.max(0, Math.min(tracks.length, currentIndex + 1)));\n}\n","import type { GingerState, RepeatMode, Track } from \"../types\";\nimport { clampIndex } from \"./queue\";\n\n/** Fields used by next/prev/ended navigation; avoids coupling helpers to full `GingerState`. */\nexport type GingerPlaybackNavigationSlice = Pick<\n GingerState,\n \"tracks\" | \"currentIndex\" | \"repeatMode\" | \"playbackMode\"\n>;\n\nexport type EndedTransition =\n | { kind: \"replay_same\" }\n | { kind: \"advance\"; nextIndex: number }\n | { kind: \"wrap\"; nextIndex: number }\n | { kind: \"stop\"; nextIndex: number };\n\nexport function computeEndedTransition(state: GingerPlaybackNavigationSlice): EndedTransition {\n const { tracks, currentIndex, repeatMode, playbackMode } = state;\n const len = tracks.length;\n if (len === 0) return { kind: \"stop\", nextIndex: 0 };\n if (repeatMode === \"one\") return { kind: \"replay_same\" };\n if (playbackMode === \"single\") return { kind: \"stop\", nextIndex: clampIndex(currentIndex, len) };\n if (currentIndex < len - 1) return { kind: \"advance\", nextIndex: currentIndex + 1 };\n if (repeatMode === \"all\") return { kind: \"wrap\", nextIndex: 0 };\n return { kind: \"stop\", nextIndex: clampIndex(currentIndex, len) };\n}\n\nexport function computeNextIndex(state: GingerPlaybackNavigationSlice): number {\n const { tracks, currentIndex, repeatMode, playbackMode } = state;\n const len = tracks.length;\n if (len === 0) return 0;\n if (playbackMode === \"single\") return clampIndex(currentIndex, len);\n if (currentIndex < len - 1) return currentIndex + 1;\n if (repeatMode === \"all\") return 0;\n return clampIndex(currentIndex, len);\n}\n\nexport function computePrevIndex(state: GingerPlaybackNavigationSlice): number {\n const { tracks, currentIndex, repeatMode, playbackMode } = state;\n const len = tracks.length;\n if (len === 0) return 0;\n if (playbackMode === \"single\") return clampIndex(currentIndex, len);\n if (currentIndex > 0) return currentIndex - 1;\n if (repeatMode === \"all\") return len - 1;\n return 0;\n}\n\nexport function cycleRepeatMode(mode: RepeatMode): RepeatMode {\n if (mode === \"off\") return \"all\";\n if (mode === \"all\") return \"one\";\n return \"off\";\n}\n\nexport function resolveArtworkUrl(\n track: Track | null,\n playlistArtwork?: string | null,\n): string | undefined {\n return track?.artworkUrl ?? playlistArtwork ?? undefined;\n}\n\nexport function resolveAlbumLine(\n track: Track | null,\n playlistSubtitle?: string | null,\n): string | undefined {\n return track?.album ?? playlistSubtitle ?? undefined;\n}\n","import { resolveAlbumLine, resolveArtworkUrl } from \"../core/transitions\";\nimport type { GingerState, PlaybackUiState, Track } from \"../types\";\n\nexport function getCurrentTrack(state: GingerState): Track | null {\n const t = state.tracks[state.currentIndex];\n return t ?? null;\n}\n\nexport function derivePlaybackUiState(state: GingerState): PlaybackUiState {\n if (state.errorMessage) return \"error\";\n if (state.tracks.length === 0) return \"idle\";\n if (state.isBuffering) return \"loading\";\n if (!state.isPaused) return \"playing\";\n if (\n Number.isFinite(state.duration) &&\n state.duration > 0 &&\n state.currentTime >= state.duration - 0.05\n ) {\n return \"ended\";\n }\n return \"paused\";\n}\n\nexport function effectiveDuration(state: GingerState): number {\n const d = state.duration;\n if (Number.isFinite(d) && d > 0) return d;\n const hint = state.tracks[state.currentIndex]?.durationSeconds;\n if (typeof hint === \"number\" && Number.isFinite(hint) && hint > 0) return hint;\n return 0;\n}\n\nexport function effectiveRemaining(state: GingerState): number {\n const dur = effectiveDuration(state);\n const rem = dur - state.currentTime;\n return Number.isFinite(rem) ? Math.max(0, rem) : 0;\n}\n\nexport function progressFraction(state: GingerState): number {\n const dur = effectiveDuration(state);\n if (!(dur > 0)) return 0;\n return Math.min(1, Math.max(0, state.currentTime / dur));\n}\n\nexport function resolvedArtwork(state: GingerState): string | undefined {\n const track = getCurrentTrack(state);\n return resolveArtworkUrl(track, state.playlistMeta?.artworkUrl);\n}\n\nexport function resolvedAlbumLine(state: GingerState): string | undefined {\n const track = getCurrentTrack(state);\n return resolveAlbumLine(track, state.playlistMeta?.subtitle);\n}\n"],"names":["clampIndex","index","length","shuffleWithAnchor","tracks","anchorIndex","anchor","rest","_","i","j","trackIdentity","track","findIndexByTrackIdentity","target","_a","_b","byRef","t","identity","matches","nodeEnv","insertTrackAt","next","at","removeTrackAt","moveTrack","fromIndex","toIndex","item","addNextTrack","currentIndex","computeEndedTransition","state","repeatMode","playbackMode","len","computeNextIndex","computePrevIndex","cycleRepeatMode","mode","resolveArtworkUrl","playlistArtwork","resolveAlbumLine","playlistSubtitle","getCurrentTrack","derivePlaybackUiState","effectiveDuration","d","hint","effectiveRemaining","rem","progressFraction","dur","resolvedArtwork","resolvedAlbumLine"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use strict";function l(n,e){return e<=0?0:Math.max(0,Math.min(e-1,n))}function p(n,e){if(n.length<=1)return[...n];const r=n[e];if(!r)return[...n];const t=n.filter((i,u)=>u!==e);for(let i=t.length-1;i>0;i--){const u=Math.floor(Math.random()*(i+1));[t[i],t[u]]=[t[u],t[i]]}return[r,...t]}function c(n){return n?n.id!=null&&n.id!==""?`id:${n.id}`:`file:${n.fileUrl}`:""}function g(n,e){var a,s;if(!e)return 0;const r=n.findIndex(o=>o===e);if(r!==-1)return r;const t=c(e);if(!t)return 0;const i=[];for(let o=0;o<n.length;o+=1)c(n[o])===t&&i.push(o);if(i.length===0)return 0;if(i.length===1)return i[0];const u=typeof globalThis<"u"&&"process"in globalThis?(s=(a=globalThis.process)==null?void 0:a.env)==null?void 0:s.NODE_ENV:void 0;return u!=null&&u!=="production"&&console.warn("[@lucaismyname/ginger] Ambiguous track identity: multiple queue rows share the same fileUrl without a unique `id`. Resolving to the first match."),i[0]}function h(n,e,r){const t=[...n],i=Math.max(0,Math.min(t.length,r??t.length));return t.splice(i,0,e),t}function x(n,e){if(e<0||e>=n.length)return[...n];const r=[...n];return r.splice(e,1),r}function v(n,e,r){if(e===r||e<0||e>=n.length||r<0||r>=n.length)return[...n];const t=[...n],[i]=t.splice(e,1);return i?(t.splice(r,0,i),t):[...n]}function y(n,e,r){return h(n,r,Math.max(0,Math.min(n.length,e+1)))}function M(n){const{tracks:e,currentIndex:r,repeatMode:t,playbackMode:i}=n,u=e.length;return u===0?{kind:"stop",nextIndex:0}:t==="one"?{kind:"replay_same"}:i==="single"?{kind:"stop",nextIndex:l(r,u)}:r<u-1?{kind:"advance",nextIndex:r+1}:t==="all"?{kind:"wrap",nextIndex:0}:{kind:"stop",nextIndex:l(r,u)}}function b(n){const{tracks:e,currentIndex:r,repeatMode:t,playbackMode:i}=n,u=e.length;return u===0?0:i==="single"?l(r,u):r<u-1?r+1:t==="all"?0:l(r,u)}function T(n){const{tracks:e,currentIndex:r,repeatMode:t,playbackMode:i}=n,u=e.length;return u===0?0:i==="single"?l(r,u):r>0?r-1:t==="all"?u-1:0}function I(n){return n==="off"?"all":n==="all"?"one":"off"}function m(n,e){return(n==null?void 0:n.artworkUrl)??e??void 0}function k(n,e){return(n==null?void 0:n.album)??e??void 0}function f(n){return n.tracks[n.currentIndex]??null}function A(n){return n.errorMessage?"error":n.tracks.length===0?"idle":n.isBuffering?"loading":n.isPaused?Number.isFinite(n.duration)&&n.duration>0&&n.currentTime>=n.duration-.05?"ended":"paused":"playing"}function d(n){var t;const e=n.duration;if(Number.isFinite(e)&&e>0)return e;const r=(t=n.tracks[n.currentIndex])==null?void 0:t.durationSeconds;return typeof r=="number"&&Number.isFinite(r)&&r>0?r:0}function w(n){const r=d(n)-n.currentTime;return Number.isFinite(r)?Math.max(0,r):0}function N(n){const e=d(n);return e>0?Math.min(1,Math.max(0,n.currentTime/e)):0}function U(n){var r;const e=f(n);return m(e,(r=n.playlistMeta)==null?void 0:r.artworkUrl)}function F(n){var r;const e=f(n);return k(e,(r=n.playlistMeta)==null?void 0:r.subtitle)}exports.addNextTrack=y;exports.clampIndex=l;exports.computeEndedTransition=M;exports.computeNextIndex=b;exports.computePrevIndex=T;exports.cycleRepeatMode=I;exports.derivePlaybackUiState=A;exports.effectiveDuration=d;exports.effectiveRemaining=w;exports.findIndexByTrackIdentity=g;exports.getCurrentTrack=f;exports.insertTrackAt=h;exports.moveTrack=v;exports.progressFraction=N;exports.removeTrackAt=x;exports.resolveArtworkUrl=m;exports.resolvedAlbumLine=F;exports.resolvedArtwork=U;exports.shuffleWithAnchor=p;exports.trackIdentity=c;
2
+ //# sourceMappingURL=selectors-YXnP8Y8g.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selectors-YXnP8Y8g.cjs","sources":["../src/core/queue.ts","../src/core/transitions.ts","../src/internal/selectors.ts"],"sourcesContent":["import type { Track } from \"../types\";\n\nexport function clampIndex(index: number, length: number): number {\n if (length <= 0) return 0;\n return Math.max(0, Math.min(length - 1, index));\n}\n\nexport function shuffleWithAnchor(tracks: Track[], anchorIndex: number): Track[] {\n if (tracks.length <= 1) return [...tracks];\n const anchor = tracks[anchorIndex];\n if (!anchor) return [...tracks];\n const rest = tracks.filter((_, i) => i !== anchorIndex);\n for (let i = rest.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [rest[i], rest[j]] = [rest[j]!, rest[i]!];\n }\n return [anchor, ...rest];\n}\n\nexport function trackIdentity(track: Track | null | undefined): string {\n if (!track) return \"\";\n return track.id != null && track.id !== \"\" ? `id:${track.id}` : `file:${track.fileUrl}`;\n}\n\nexport function findIndexByTrackIdentity(\n tracks: Track[],\n target: Track | null | undefined,\n): number {\n if (!target) return 0;\n const byRef = tracks.findIndex((t) => t === target);\n if (byRef !== -1) return byRef;\n\n const identity = trackIdentity(target);\n if (!identity) return 0;\n\n const matches: number[] = [];\n for (let i = 0; i < tracks.length; i += 1) {\n if (trackIdentity(tracks[i]) === identity) matches.push(i);\n }\n if (matches.length === 0) return 0;\n if (matches.length === 1) return matches[0]!;\n\n const nodeEnv =\n typeof globalThis !== \"undefined\" && \"process\" in globalThis\n ? (globalThis as { process?: { env?: { NODE_ENV?: string } } }).process?.env?.NODE_ENV\n : undefined;\n if (nodeEnv != null && nodeEnv !== \"production\") {\n console.warn(\n \"[@lucaismyname/ginger] Ambiguous track identity: multiple queue rows share the same fileUrl without a unique `id`. Resolving to the first match.\",\n );\n }\n return matches[0]!;\n}\n\nexport function insertTrackAt(tracks: Track[], track: Track, index?: number): Track[] {\n const next = [...tracks];\n const at = Math.max(0, Math.min(next.length, index ?? next.length));\n next.splice(at, 0, track);\n return next;\n}\n\nexport function removeTrackAt(tracks: Track[], index: number): Track[] {\n if (index < 0 || index >= tracks.length) return [...tracks];\n const next = [...tracks];\n next.splice(index, 1);\n return next;\n}\n\nexport function moveTrack(tracks: Track[], fromIndex: number, toIndex: number): Track[] {\n if (\n fromIndex === toIndex ||\n fromIndex < 0 ||\n fromIndex >= tracks.length ||\n toIndex < 0 ||\n toIndex >= tracks.length\n ) {\n return [...tracks];\n }\n const next = [...tracks];\n const [item] = next.splice(fromIndex, 1);\n if (!item) return [...tracks];\n next.splice(toIndex, 0, item);\n return next;\n}\n\nexport function addNextTrack(tracks: Track[], currentIndex: number, track: Track): Track[] {\n return insertTrackAt(tracks, track, Math.max(0, Math.min(tracks.length, currentIndex + 1)));\n}\n","import type { GingerState, RepeatMode, Track } from \"../types\";\nimport { clampIndex } from \"./queue\";\n\n/** Fields used by next/prev/ended navigation; avoids coupling helpers to full `GingerState`. */\nexport type GingerPlaybackNavigationSlice = Pick<\n GingerState,\n \"tracks\" | \"currentIndex\" | \"repeatMode\" | \"playbackMode\"\n>;\n\nexport type EndedTransition =\n | { kind: \"replay_same\" }\n | { kind: \"advance\"; nextIndex: number }\n | { kind: \"wrap\"; nextIndex: number }\n | { kind: \"stop\"; nextIndex: number };\n\nexport function computeEndedTransition(state: GingerPlaybackNavigationSlice): EndedTransition {\n const { tracks, currentIndex, repeatMode, playbackMode } = state;\n const len = tracks.length;\n if (len === 0) return { kind: \"stop\", nextIndex: 0 };\n if (repeatMode === \"one\") return { kind: \"replay_same\" };\n if (playbackMode === \"single\") return { kind: \"stop\", nextIndex: clampIndex(currentIndex, len) };\n if (currentIndex < len - 1) return { kind: \"advance\", nextIndex: currentIndex + 1 };\n if (repeatMode === \"all\") return { kind: \"wrap\", nextIndex: 0 };\n return { kind: \"stop\", nextIndex: clampIndex(currentIndex, len) };\n}\n\nexport function computeNextIndex(state: GingerPlaybackNavigationSlice): number {\n const { tracks, currentIndex, repeatMode, playbackMode } = state;\n const len = tracks.length;\n if (len === 0) return 0;\n if (playbackMode === \"single\") return clampIndex(currentIndex, len);\n if (currentIndex < len - 1) return currentIndex + 1;\n if (repeatMode === \"all\") return 0;\n return clampIndex(currentIndex, len);\n}\n\nexport function computePrevIndex(state: GingerPlaybackNavigationSlice): number {\n const { tracks, currentIndex, repeatMode, playbackMode } = state;\n const len = tracks.length;\n if (len === 0) return 0;\n if (playbackMode === \"single\") return clampIndex(currentIndex, len);\n if (currentIndex > 0) return currentIndex - 1;\n if (repeatMode === \"all\") return len - 1;\n return 0;\n}\n\nexport function cycleRepeatMode(mode: RepeatMode): RepeatMode {\n if (mode === \"off\") return \"all\";\n if (mode === \"all\") return \"one\";\n return \"off\";\n}\n\nexport function resolveArtworkUrl(\n track: Track | null,\n playlistArtwork?: string | null,\n): string | undefined {\n return track?.artworkUrl ?? playlistArtwork ?? undefined;\n}\n\nexport function resolveAlbumLine(\n track: Track | null,\n playlistSubtitle?: string | null,\n): string | undefined {\n return track?.album ?? playlistSubtitle ?? undefined;\n}\n","import { resolveAlbumLine, resolveArtworkUrl } from \"../core/transitions\";\nimport type { GingerState, PlaybackUiState, Track } from \"../types\";\n\nexport function getCurrentTrack(state: GingerState): Track | null {\n const t = state.tracks[state.currentIndex];\n return t ?? null;\n}\n\nexport function derivePlaybackUiState(state: GingerState): PlaybackUiState {\n if (state.errorMessage) return \"error\";\n if (state.tracks.length === 0) return \"idle\";\n if (state.isBuffering) return \"loading\";\n if (!state.isPaused) return \"playing\";\n if (\n Number.isFinite(state.duration) &&\n state.duration > 0 &&\n state.currentTime >= state.duration - 0.05\n ) {\n return \"ended\";\n }\n return \"paused\";\n}\n\nexport function effectiveDuration(state: GingerState): number {\n const d = state.duration;\n if (Number.isFinite(d) && d > 0) return d;\n const hint = state.tracks[state.currentIndex]?.durationSeconds;\n if (typeof hint === \"number\" && Number.isFinite(hint) && hint > 0) return hint;\n return 0;\n}\n\nexport function effectiveRemaining(state: GingerState): number {\n const dur = effectiveDuration(state);\n const rem = dur - state.currentTime;\n return Number.isFinite(rem) ? Math.max(0, rem) : 0;\n}\n\nexport function progressFraction(state: GingerState): number {\n const dur = effectiveDuration(state);\n if (!(dur > 0)) return 0;\n return Math.min(1, Math.max(0, state.currentTime / dur));\n}\n\nexport function resolvedArtwork(state: GingerState): string | undefined {\n const track = getCurrentTrack(state);\n return resolveArtworkUrl(track, state.playlistMeta?.artworkUrl);\n}\n\nexport function resolvedAlbumLine(state: GingerState): string | undefined {\n const track = getCurrentTrack(state);\n return resolveAlbumLine(track, state.playlistMeta?.subtitle);\n}\n"],"names":["clampIndex","index","length","shuffleWithAnchor","tracks","anchorIndex","anchor","rest","_","i","j","trackIdentity","track","findIndexByTrackIdentity","target","byRef","t","identity","matches","nodeEnv","_b","_a","insertTrackAt","next","at","removeTrackAt","moveTrack","fromIndex","toIndex","item","addNextTrack","currentIndex","computeEndedTransition","state","repeatMode","playbackMode","len","computeNextIndex","computePrevIndex","cycleRepeatMode","mode","resolveArtworkUrl","playlistArtwork","resolveAlbumLine","playlistSubtitle","getCurrentTrack","derivePlaybackUiState","effectiveDuration","d","hint","effectiveRemaining","rem","progressFraction","dur","resolvedArtwork","resolvedAlbumLine"],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import { clampPlaybackRate, clampVolume } from './core/playbackReducer';
2
+ import { GingerAction, GingerInitPayload, GingerState, PlaylistMeta, RepeatMode, Track } from './types';
3
+ export type GingerStoreOptions = {
4
+ tracks?: Track[];
5
+ currentIndex?: number;
6
+ playlistMeta?: PlaylistMeta | null;
7
+ isPaused?: boolean;
8
+ isShuffled?: boolean;
9
+ repeatMode?: RepeatMode;
10
+ playbackMode?: GingerState["playbackMode"];
11
+ volume?: number;
12
+ muted?: boolean;
13
+ playbackRate?: number;
14
+ };
15
+ export type GingerStore = {
16
+ /** Returns the current state snapshot. */
17
+ getState: () => GingerState;
18
+ /** Dispatch an action to update state. Synchronously updates state and notifies listeners. */
19
+ dispatch: (action: GingerAction) => void;
20
+ /**
21
+ * Subscribe to state changes. The listener is called after every `dispatch` that produces
22
+ * a new state object. Returns an unsubscribe function.
23
+ */
24
+ subscribe: (listener: (state: GingerState) => void) => () => void;
25
+ /** Convenience: re-initialise with a new set of init options (equivalent to `dispatch({ type: "INIT", ... })`). */
26
+ init: (payload: GingerInitPayload) => void;
27
+ /** Clamp helpers re-exported for convenience. */
28
+ clampVolume: typeof clampVolume;
29
+ clampPlaybackRate: typeof clampPlaybackRate;
30
+ };
31
+ /**
32
+ * Framework-agnostic store wrapping `gingerReducer`.
33
+ * Usable outside React — e.g. in Svelte, Vue, Node.js testing, or server-side rendering contexts.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * import { createGingerStore } from "@lucaismyname/ginger";
38
+ *
39
+ * const store = createGingerStore({ tracks: myTracks });
40
+ * const unsub = store.subscribe((state) => console.log(state.currentIndex));
41
+ * store.dispatch({ type: "NEXT" });
42
+ * unsub();
43
+ * ```
44
+ */
45
+ export declare function createGingerStore(options?: GingerStoreOptions): GingerStore;
46
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAqC,MAAM,wBAAwB,CAAC;AAC3G,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAE7G,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,YAAY,CAAC,EAAE,WAAW,CAAC,cAAc,CAAC,CAAC;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,WAAW,CAAC;IAC5B,8FAA8F;IAC9F,QAAQ,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;IACzC;;;OAGG;IACH,SAAS,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAClE,mHAAmH;IACnH,IAAI,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC3C,iDAAiD;IACjD,WAAW,EAAE,OAAO,WAAW,CAAC;IAChC,iBAAiB,EAAE,OAAO,iBAAiB,CAAC;CAC7C,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,kBAAuB,GAAG,WAAW,CA2C/E"}
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const Al=require("react/jsx-runtime"),Xi=require("react"),yt=require("react-dom"),Il=require("../ginger-B26HM2Ja.cjs");function Pc(e){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const r in e)if(r!=="default"){const n=Object.getOwnPropertyDescriptor(e,r);Object.defineProperty(t,r,n.get?n:{enumerable:!0,get:()=>e[r]})}}return t.default=e,Object.freeze(t)}const ur=Pc(Xi);function Tc(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var wi={exports:{}},ne={};/**
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const Al=require("react/jsx-runtime"),Xi=require("react"),yt=require("react-dom"),Il=require("../ginger-NEcOSSJD.cjs");function Pc(e){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const r in e)if(r!=="default"){const n=Object.getOwnPropertyDescriptor(e,r);Object.defineProperty(t,r,n.get?n:{enumerable:!0,get:()=>e[r]})}}return t.default=e,Object.freeze(t)}const ur=Pc(Xi);function Tc(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var wi={exports:{}},ne={};/**
2
2
  * @license React
3
3
  * react-dom-test-utils.production.min.js
4
4
  *
@@ -2,7 +2,7 @@ import { jsxs as Rc, jsx as Pc } from "react/jsx-runtime";
2
2
  import * as ur from "react";
3
3
  import Us from "react";
4
4
  import yt from "react-dom";
5
- import { G as Ml } from "../ginger-DlNYfHbV.js";
5
+ import { G as Ml } from "../ginger-L2ZFgzH4.js";
6
6
  function Tc(e) {
7
7
  return e && e.__esModule && Object.prototype.hasOwnProperty.call(e, "default") ? e.default : e;
8
8
  }
@@ -4,7 +4,7 @@ declare class MockAudioNode {
4
4
  readonly connectCalls: MockAudioNode[];
5
5
  disconnectCalls: number;
6
6
  connect(node: MockAudioNode): MockAudioNode;
7
- disconnect(): void;
7
+ disconnect(_node?: MockAudioNode): void;
8
8
  }
9
9
  export declare class MockAudioDestinationNode extends MockAudioNode {
10
10
  }
@@ -21,16 +21,30 @@ export declare class MockAnalyserNode extends MockAudioNode {
21
21
  getByteFrequencyData(array: Uint8Array): void;
22
22
  getByteTimeDomainData(array: Uint8Array): void;
23
23
  }
24
+ export declare class MockBiquadFilterNode extends MockAudioNode {
25
+ type: BiquadFilterType;
26
+ readonly frequency: {
27
+ value: number;
28
+ };
29
+ readonly gain: {
30
+ value: number;
31
+ };
32
+ readonly Q: {
33
+ value: number;
34
+ };
35
+ }
24
36
  export declare class MockAudioContext extends EventTarget {
25
37
  readonly destination: MockAudioDestinationNode;
26
38
  readonly sources: MockMediaElementAudioSourceNode[];
27
39
  readonly analysers: MockAnalyserNode[];
40
+ readonly biquadFilters: MockBiquadFilterNode[];
28
41
  sampleRate: number;
29
42
  state: MockAudioContextState;
30
43
  closeCalls: number;
31
44
  resumeCalls: number;
32
45
  createMediaElementSource(element: HTMLAudioElement): MediaElementAudioSourceNode;
33
46
  createAnalyser(): AnalyserNode;
47
+ createBiquadFilter(): BiquadFilterNode;
34
48
  resume(): Promise<void>;
35
49
  close(): Promise<void>;
36
50
  setStateForTest(nextState: MockAudioContextState): void;
@@ -1 +1 @@
1
- {"version":3,"file":"mockWebAudio.d.ts","sourceRoot":"","sources":["../../src/testing/mockWebAudio.ts"],"names":[],"mappings":"AAAA,KAAK,qBAAqB,GAAG,iBAAiB,CAAC;AAE/C,cAAM,aAAa;IACjB,QAAQ,CAAC,WAAW,EAAE,aAAa,EAAE,CAAM;IAC3C,QAAQ,CAAC,YAAY,EAAE,aAAa,EAAE,CAAM;IAC5C,eAAe,SAAK;IAEpB,OAAO,CAAC,IAAI,EAAE,aAAa;IAM3B,UAAU;CAIX;AAED,qBAAa,wBAAyB,SAAQ,aAAa;CAAG;AAE9D,qBAAa,+BAAgC,SAAQ,aAAa;IACpD,QAAQ,CAAC,YAAY,EAAE,gBAAgB;gBAA9B,YAAY,EAAE,gBAAgB;CAGpD;AAED,qBAAa,gBAAiB,SAAQ,aAAa;IACjD,OAAO,SAAQ;IACf,qBAAqB,SAAO;IAC5B,WAAW,SAAQ;IACnB,WAAW,SAAO;IAElB,IAAI,iBAAiB,WAEpB;IAED,oBAAoB,CAAC,KAAK,EAAE,UAAU;IAMtC,qBAAqB,CAAC,KAAK,EAAE,UAAU;CAKxC;AAED,qBAAa,gBAAiB,SAAQ,WAAW;IAC/C,QAAQ,CAAC,WAAW,2BAAkC;IACtD,QAAQ,CAAC,OAAO,EAAE,+BAA+B,EAAE,CAAM;IACzD,QAAQ,CAAC,SAAS,EAAE,gBAAgB,EAAE,CAAM;IAE5C,UAAU,SAAU;IACpB,KAAK,EAAE,qBAAqB,CAAa;IACzC,UAAU,SAAK;IACf,WAAW,SAAK;IAEhB,wBAAwB,CAAC,OAAO,EAAE,gBAAgB,GAGpB,2BAA2B;IAGzD,cAAc,IAGkB,YAAY;IAGtC,MAAM;IAON,KAAK;IAKX,eAAe,CAAC,SAAS,EAAE,qBAAqB;CAKjD;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,mBAAmB,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,oBAAoB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,0BAA0B,EAAE,MAAM,MAAM,CAAC;IACzC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF,wBAAgB,mBAAmB,IAAI,mBAAmB,CAgFzD"}
1
+ {"version":3,"file":"mockWebAudio.d.ts","sourceRoot":"","sources":["../../src/testing/mockWebAudio.ts"],"names":[],"mappings":"AAAA,KAAK,qBAAqB,GAAG,iBAAiB,CAAC;AAE/C,cAAM,aAAa;IACjB,QAAQ,CAAC,WAAW,EAAE,aAAa,EAAE,CAAM;IAC3C,QAAQ,CAAC,YAAY,EAAE,aAAa,EAAE,CAAM;IAC5C,eAAe,SAAK;IAEpB,OAAO,CAAC,IAAI,EAAE,aAAa;IAM3B,UAAU,CAAC,KAAK,CAAC,EAAE,aAAa;CAIjC;AAED,qBAAa,wBAAyB,SAAQ,aAAa;CAAG;AAE9D,qBAAa,+BAAgC,SAAQ,aAAa;IACpD,QAAQ,CAAC,YAAY,EAAE,gBAAgB;gBAA9B,YAAY,EAAE,gBAAgB;CAGpD;AAED,qBAAa,gBAAiB,SAAQ,aAAa;IACjD,OAAO,SAAQ;IACf,qBAAqB,SAAO;IAC5B,WAAW,SAAQ;IACnB,WAAW,SAAO;IAElB,IAAI,iBAAiB,WAEpB;IAED,oBAAoB,CAAC,KAAK,EAAE,UAAU;IAMtC,qBAAqB,CAAC,KAAK,EAAE,UAAU;CAKxC;AAED,qBAAa,oBAAqB,SAAQ,aAAa;IACrD,IAAI,EAAE,gBAAgB,CAAa;IACnC,QAAQ,CAAC,SAAS;;MAAmB;IACrC,QAAQ,CAAC,IAAI;;MAAgB;IAC7B,QAAQ,CAAC,CAAC;;MAAgB;CAC3B;AAED,qBAAa,gBAAiB,SAAQ,WAAW;IAC/C,QAAQ,CAAC,WAAW,2BAAkC;IACtD,QAAQ,CAAC,OAAO,EAAE,+BAA+B,EAAE,CAAM;IACzD,QAAQ,CAAC,SAAS,EAAE,gBAAgB,EAAE,CAAM;IAC5C,QAAQ,CAAC,aAAa,EAAE,oBAAoB,EAAE,CAAM;IAEpD,UAAU,SAAU;IACpB,KAAK,EAAE,qBAAqB,CAAa;IACzC,UAAU,SAAK;IACf,WAAW,SAAK;IAEhB,wBAAwB,CAAC,OAAO,EAAE,gBAAgB,GAGpB,2BAA2B;IAGzD,cAAc,IAGkB,YAAY;IAG5C,kBAAkB,IAGY,gBAAgB;IAGxC,MAAM;IAON,KAAK;IAKX,eAAe,CAAC,SAAS,EAAE,qBAAqB;CAKjD;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,mBAAmB,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,oBAAoB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,0BAA0B,EAAE,MAAM,MAAM,CAAC;IACzC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF,wBAAgB,mBAAmB,IAAI,mBAAmB,CAgFzD"}