@lucaismyname/ginger 0.0.31 → 0.0.34

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 (83) hide show
  1. package/README.md +105 -5
  2. package/dist/client.cjs +1 -1
  3. package/dist/client.js +37 -36
  4. package/dist/client.js.map +1 -1
  5. package/dist/equalizer/index.cjs +1 -1
  6. package/dist/equalizer/index.cjs.map +1 -1
  7. package/dist/equalizer/index.js +16 -15
  8. package/dist/equalizer/index.js.map +1 -1
  9. package/dist/hooks/useNextTrackPrefetch.d.ts +3 -0
  10. package/dist/hooks/useNextTrackPrefetch.d.ts.map +1 -1
  11. package/dist/index.cjs +1 -1
  12. package/dist/index.js +37 -36
  13. package/dist/index.js.map +1 -1
  14. package/dist/liveAudioGraph-0cpHD_Ic.cjs +2 -0
  15. package/dist/liveAudioGraph-0cpHD_Ic.cjs.map +1 -0
  16. package/dist/liveAudioGraph-DvPaxBCP.js +105 -0
  17. package/dist/liveAudioGraph-DvPaxBCP.js.map +1 -0
  18. package/dist/remote/index.cjs +2 -0
  19. package/dist/remote/index.cjs.map +1 -0
  20. package/dist/remote/index.d.ts +5 -0
  21. package/dist/remote/index.d.ts.map +1 -0
  22. package/dist/remote/index.js +168 -0
  23. package/dist/remote/index.js.map +1 -0
  24. package/dist/remote/remoteProtocol.d.ts +28 -0
  25. package/dist/remote/remoteProtocol.d.ts.map +1 -0
  26. package/dist/remote/useGingerRemote.d.ts +35 -0
  27. package/dist/remote/useGingerRemote.d.ts.map +1 -0
  28. package/dist/remote/useGingerRemote.test.d.ts +2 -0
  29. package/dist/remote/useGingerRemote.test.d.ts.map +1 -0
  30. package/dist/remote/validateGingerInitPayloadDev.d.ts +7 -0
  31. package/dist/remote/validateGingerInitPayloadDev.d.ts.map +1 -0
  32. package/dist/remote/validateGingerInitPayloadDev.test.d.ts +2 -0
  33. package/dist/remote/validateGingerInitPayloadDev.test.d.ts.map +1 -0
  34. package/dist/spatial/index.cjs +2 -0
  35. package/dist/spatial/index.cjs.map +1 -0
  36. package/dist/spatial/index.d.ts +3 -0
  37. package/dist/spatial/index.d.ts.map +1 -0
  38. package/dist/spatial/index.js +59 -0
  39. package/dist/spatial/index.js.map +1 -0
  40. package/dist/spatial/useGingerSpatialAudio.d.ts +34 -0
  41. package/dist/spatial/useGingerSpatialAudio.d.ts.map +1 -0
  42. package/dist/spatial/useGingerSpatialAudio.test.d.ts +2 -0
  43. package/dist/spatial/useGingerSpatialAudio.test.d.ts.map +1 -0
  44. package/dist/testing/mockWebAudio.d.ts +14 -0
  45. package/dist/testing/mockWebAudio.d.ts.map +1 -1
  46. package/dist/transcript/index.cjs +8 -0
  47. package/dist/transcript/index.cjs.map +1 -0
  48. package/dist/transcript/index.d.ts +5 -0
  49. package/dist/transcript/index.d.ts.map +1 -0
  50. package/dist/transcript/index.js +99 -0
  51. package/dist/transcript/index.js.map +1 -0
  52. package/dist/transcript/parseTranscript.d.ts +27 -0
  53. package/dist/transcript/parseTranscript.d.ts.map +1 -0
  54. package/dist/transcript/parseTranscript.test.d.ts +2 -0
  55. package/dist/transcript/parseTranscript.test.d.ts.map +1 -0
  56. package/dist/transcript/useGingerTranscriptSync.d.ts +23 -0
  57. package/dist/transcript/useGingerTranscriptSync.d.ts.map +1 -0
  58. package/dist/useGinger-BXgia32v.cjs +2 -0
  59. package/dist/useGinger-BXgia32v.cjs.map +1 -0
  60. package/dist/useGinger-hpp2pAGY.js +48 -0
  61. package/dist/useGinger-hpp2pAGY.js.map +1 -0
  62. package/dist/useGingerChapterProgress-BdaalJvX.cjs +2 -0
  63. package/dist/useGingerChapterProgress-BdaalJvX.cjs.map +1 -0
  64. package/dist/{useGingerChapterProgress-DLYdGytK.js → useGingerChapterProgress-CZdv-HiI.js} +23 -22
  65. package/dist/useGingerChapterProgress-CZdv-HiI.js.map +1 -0
  66. package/dist/waveform/analyzeAudioFile.d.ts.map +1 -1
  67. package/dist/waveform/getAudioContextConstructor.d.ts +6 -0
  68. package/dist/waveform/getAudioContextConstructor.d.ts.map +1 -0
  69. package/dist/waveform/index.cjs +1 -1
  70. package/dist/waveform/index.cjs.map +1 -1
  71. package/dist/waveform/index.js +162 -153
  72. package/dist/waveform/index.js.map +1 -1
  73. package/dist/waveform/useAudioFileAnalysis.d.ts +1 -0
  74. package/dist/waveform/useAudioFileAnalysis.d.ts.map +1 -1
  75. package/dist/waveform/useAudioPeaks.d.ts.map +1 -1
  76. package/package.json +17 -2
  77. package/dist/liveAudioGraph-CmEsdLgZ.js +0 -150
  78. package/dist/liveAudioGraph-CmEsdLgZ.js.map +0 -1
  79. package/dist/liveAudioGraph-D1BXMv_u.cjs +0 -2
  80. package/dist/liveAudioGraph-D1BXMv_u.cjs.map +0 -1
  81. package/dist/useGingerChapterProgress-BOqUimE7.cjs +0 -2
  82. package/dist/useGingerChapterProgress-BOqUimE7.cjs.map +0 -1
  83. package/dist/useGingerChapterProgress-DLYdGytK.js.map +0 -1
package/README.md CHANGED
@@ -56,7 +56,7 @@ Mount **`<Ginger.Player />`** once inside the same provider tree so the hidden a
56
56
  - Streaming adapters: [`docs/guides/streaming-adapters.md`](./docs/guides/streaming-adapters.md)
57
57
  - Components reference: [`docs/reference/components.md`](./docs/reference/components.md)
58
58
  - Hooks reference: [`docs/reference/hooks.md`](./docs/reference/hooks.md)
59
- - Subpath exports: [`docs/reference/subpaths.md`](./docs/reference/subpaths.md)
59
+ - Subpath exports (waveform, EQ, spatial, transcript, remote, …): [`docs/reference/subpaths.md`](./docs/reference/subpaths.md)
60
60
  - Generated API docs: [`docs/api/index.html`](./docs/api/index.html)
61
61
 
62
62
  ## Subpath Exports
@@ -65,6 +65,9 @@ Mount **`<Ginger.Player />`** once inside the same provider tree so the hidden a
65
65
  - `@lucaismyname/ginger/testing`
66
66
  - `@lucaismyname/ginger/waveform`
67
67
  - `@lucaismyname/ginger/equalizer`
68
+ - `@lucaismyname/ginger/spatial`
69
+ - `@lucaismyname/ginger/transcript`
70
+ - `@lucaismyname/ginger/remote`
68
71
  - `@lucaismyname/ginger/experimental-gapless`
69
72
 
70
73
  ### Equalizer
@@ -105,6 +108,96 @@ function MyPlayer() {
105
108
 
106
109
  The EQ and `useGingerLiveAnalyzer` share the same `AudioContext` and can be used together. EQ filters are inserted before the analyser in the Web Audio graph.
107
110
 
111
+ ### Spatial audio (`@lucaismyname/ginger/spatial`)
112
+
113
+ Inserts an HRTF **`PannerNode`** into the same Web Audio graph as the EQ and live analyser (one `MediaElementAudioSourceNode` per `<audio>`).
114
+
115
+ ```tsx
116
+ import { useGingerSpatialAudio } from "@lucaismyname/ginger/spatial";
117
+
118
+ function Spatialized() {
119
+ const { setSourcePosition, error } = useGingerSpatialAudio({
120
+ panningModel: "HRTF",
121
+ position: [2, 0, 0],
122
+ listenerPosition: [0, 0, 0],
123
+ });
124
+
125
+ return (
126
+ <div>
127
+ <button type="button" onClick={() => setSourcePosition(0, 0, -2)}>
128
+ Move source
129
+ </button>
130
+ {error && <p>{error}</p>}
131
+ </div>
132
+ );
133
+ }
134
+ ```
135
+
136
+ Use **`setListenerPosition`** and **`setPanningModel`** for runtime updates without rebuilding the graph.
137
+
138
+ ### Transcript (`@lucaismyname/ginger/transcript`)
139
+
140
+ Parse **SRT** and **WebVTT** captions and sync cues to playback time (podcasts, video-style transcripts). HTML tags in cue text are stripped.
141
+
142
+ ```tsx
143
+ import {
144
+ parseSrt,
145
+ parseVtt,
146
+ useGingerTranscriptSync,
147
+ } from "@lucaismyname/ginger/transcript";
148
+
149
+ const vtt = `WEBVTT
150
+
151
+ 00:00:01.000 --> 00:00:04.000
152
+ Hello from VTT
153
+ `;
154
+
155
+ function TranscriptPanel() {
156
+ const { cues, activeCue, activeCues } = useGingerTranscriptSync({
157
+ transcript: vtt,
158
+ format: "auto",
159
+ });
160
+
161
+ return (
162
+ <div>
163
+ <p>Now: {activeCue?.text ?? "—"}</p>
164
+ <p>Overlapping: {activeCues.map((c) => c.text).join(" · ")}</p>
165
+ </div>
166
+ );
167
+ }
168
+
169
+ // Or parse ahead of time:
170
+ const cuesFromSrt = parseSrt(srtString);
171
+ const cuesFromVtt = parseVtt(vttString);
172
+ ```
173
+
174
+ **`useGingerTranscriptSync`** mirrors **`useGingerLyricsSync`** but uses cue **start/end** ranges and exposes **`activeCues`** for overlapping captions. **`parseTranscriptAuto`** chooses VTT when the string starts with `WEBVTT`, otherwise SRT.
175
+
176
+ ### Multi-tab sync (`@lucaismyname/ginger/remote`)
177
+
178
+ Elects a **leader** tab via **`BroadcastChannel`** and pushes **`INIT`** snapshots to followers so queue and transport settings stay aligned. Mount **`Ginger.Player`** only on the leader so a single `<audio>` element plays.
179
+
180
+ ```tsx
181
+ import { Ginger } from "@lucaismyname/ginger";
182
+ import { useGingerRemote } from "@lucaismyname/ginger/remote";
183
+
184
+ function RemoteAwarePlayer() {
185
+ const { isLeader, isPending, error } = useGingerRemote({
186
+ channelName: "my-app-ginger",
187
+ });
188
+
189
+ return (
190
+ <>
191
+ {error && <p role="alert">{error}</p>}
192
+ {isLeader && <Ginger.Player />}
193
+ {isPending && <p>Connecting to other tabs…</p>}
194
+ </>
195
+ );
196
+ }
197
+ ```
198
+
199
+ Snapshots send the current queue order with **`isShuffled: false`** so followers do not re-randomize; the visible order matches the leader. **`claimLeadership()`** requests leadership (lexicographically smaller tab IDs win conflicts).
200
+
108
201
  ### Experimental Notice
109
202
 
110
203
  `@lucaismyname/ginger/experimental-gapless` is intentionally non-production.
@@ -781,7 +874,11 @@ Example:
781
874
 
782
875
  - **Buffered UI** — **`Ginger.Current.BufferRail`** shows load progress; **`Ginger.Current.TimeRail`** supports **`showBuffered`** to stack a buffered layer behind the played segment.
783
876
 
784
- - **Audio analyzers** — Live Web Audio data for real-time visuals (**`useGingerLiveAnalyzer`**, main package), parametric EQ (**`useGingerEqualizer`**, `@lucaismyname/ginger/equalizer`), and whole-file grids for waveforms or spectrograms (**`useAudioFileAnalysis`** / **`analyzeAudioFile`**, `@lucaismyname/ginger/waveform`). See [Audio analyzers (visualizations)](#audio-analyzers-visualizations).
877
+ - **Audio analyzers** — Live Web Audio data for real-time visuals (**`useGingerLiveAnalyzer`**, main package), parametric EQ (**`useGingerEqualizer`**, `@lucaismyname/ginger/equalizer`), **spatial / HRTF panning** (**`useGingerSpatialAudio`**, `@lucaismyname/ginger/spatial`), and whole-file grids for waveforms or spectrograms (**`useAudioFileAnalysis`** / **`analyzeAudioFile`**, `@lucaismyname/ginger/waveform`). See [Audio analyzers (visualizations)](#audio-analyzers-visualizations).
878
+
879
+ - **Transcripts** — **SRT / WebVTT** parsing and sync (**`parseSrt`**, **`parseVtt`**, **`useGingerTranscriptSync`**, `@lucaismyname/ginger/transcript`); LRC / in-track lyrics remain **`useGingerLyricsSync`** and **`parseLrc()`** on the main package.
880
+
881
+ - **Multi-tab** — **`useGingerRemote`** (`@lucaismyname/ginger/remote`) coordinates playback state across browser tabs; see [Subpath exports](#subpath-exports).
785
882
 
786
883
  Recipes below cover queue lifecycle and media edge cases.
787
884
 
@@ -1114,9 +1211,12 @@ Additional entrypoints:
1114
1211
  - `@lucaismyname/ginger/client`
1115
1212
  - `@lucaismyname/ginger/testing`
1116
1213
  - `@lucaismyname/ginger/waveform`
1214
+ - `@lucaismyname/ginger/spatial`
1215
+ - `@lucaismyname/ginger/transcript`
1216
+ - `@lucaismyname/ginger/remote`
1117
1217
  - `@lucaismyname/ginger/experimental-gapless`
1118
1218
 
1119
- `experimental-gapless` is explicitly non-production and does not alter core playback.
1219
+ See [Subpath Exports](#subpath-exports) for **`spatial`**, **`transcript`**, and **`remote`** usage. `experimental-gapless` is explicitly non-production and does not alter core playback.
1120
1220
 
1121
1221
  ## Notes
1122
1222
 
@@ -1137,8 +1237,8 @@ See [Recipes — Updating the queue after mount](#updating-the-queue-after-mount
1137
1237
  These priorities guide new work in the library; they are not a guarantee of shipping order.
1138
1238
 
1139
1239
  1. **Music libraries and continuous listening** — Features that make track-to-track playback feel better come first: **next-track prefetch** (`useNextTrackPrefetch`), future gapless or crossfade (see `@lucaismyname/ginger/experimental-gapless`), and first-class **chapter** / **synced lyrics** UI (`Ginger.Current.Chapters`, `Ginger.Current.LyricsSynced`).
1140
- 2. **Podcasts and live-style streams** — **HLS / DASH** integration is emphasized when a concrete app needs it; the core package stays on native `<audio>` with optional adapters or documentation rather than hard dependencies.
1141
- 3. **Embedded or internal players** — **Accessibility**, persistence, and **testing** helpers are favored over heavier ecosystem integrations (Cast, remote playback modules) unless there is a dedicated use case.
1240
+ 2. **Podcasts and live-style streams** — **HLS / DASH** integration is emphasized when a concrete app needs it; the core package stays on native `<audio>` with optional adapters or documentation rather than hard dependencies. **SRT / WebVTT** transcripts are supported via `@lucaismyname/ginger/transcript`.
1241
+ 3. **Embedded or internal players** — **Accessibility**, persistence, and **testing** helpers are favored over heavier ecosystem integrations (Cast, proprietary cast SDKs) unless there is a dedicated use case. **Multi-tab** web apps can use `@lucaismyname/ginger/remote` (BroadcastChannel) before reaching for OS-level remote playback.
1142
1242
 
1143
1243
  ## Monorepo Development
1144
1244
 
package/dist/client.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./ginger-NEcOSSJD.cjs"),i=require("./liveAudioGraph-D1BXMv_u.cjs"),r=require("./useGingerChapterProgress-BOqUimE7.cjs"),s=require("./selectors-YXnP8Y8g.cjs"),a=require("./GingerSplitContexts-C7puo0M7.cjs");exports.Chapters=e.Chapters;exports.Ginger=e.Ginger;exports.LyricsSynced=e.LyricsSynced;exports.Pause=e.Pause;exports.Play=e.Play;exports.RepeatGlyph=e.RepeatGlyph;exports.ShuffleIcon=e.ShuffleIcon;exports.SkipBack=e.SkipBack;exports.SkipForward=e.SkipForward;exports.Volume2=e.Volume2;exports.VolumeX=e.VolumeX;exports.Wrapper=e.Wrapper;exports.clampPlaybackRate=e.clampPlaybackRate;exports.clampVolume=e.clampVolume;exports.defaultGingerLocale=e.defaultGingerLocale;exports.parseLrc=e.parseLrc;exports.useGingerChapters=e.useGingerChapters;exports.useGingerLocale=e.useGingerLocale;exports.useGingerLyricsSync=e.useGingerLyricsSync;exports.usePlayPauseBinding=e.usePlayPauseBinding;exports.useSeekBarBinding=e.useSeekBarBinding;exports.useVolumeSlider=e.useVolumeSlider;exports.attachLiveAnalyser=i.attachLiveAnalyser;exports.detachLiveAnalyser=i.detachLiveAnalyser;exports.setProcessingChain=i.setProcessingChain;exports.useGinger=i.useGinger;exports.createGingerStore=r.createGingerStore;exports.useGingerChapterProgress=r.useGingerChapterProgress;exports.useGingerDebugLog=r.useGingerDebugLog;exports.useGingerKeyboardShortcuts=r.useGingerKeyboardShortcuts;exports.useGingerLiveAnalyzer=r.useGingerLiveAnalyzer;exports.useGingerPlaybackHistory=r.useGingerPlaybackHistory;exports.useGingerSleepTimer=r.useGingerSleepTimer;exports.useGingerVolumeFade=r.useGingerVolumeFade;exports.useNextTrackPrefetch=r.useNextTrackPrefetch;exports.useSeekDrag=r.useSeekDrag;exports.derivePlaybackUiState=s.derivePlaybackUiState;exports.gingerStateFromContextValues=a.gingerStateFromContextValues;exports.gingerStateFromContexts=a.gingerStateFromContexts;exports.useGingerMedia=a.useGingerMedia;exports.useGingerPlayback=a.useGingerPlayback;exports.useGingerState=a.useGingerState;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./ginger-NEcOSSJD.cjs"),s=require("./useGinger-BXgia32v.cjs"),r=require("./useGingerChapterProgress-BdaalJvX.cjs"),i=require("./liveAudioGraph-0cpHD_Ic.cjs"),n=require("./selectors-YXnP8Y8g.cjs"),a=require("./GingerSplitContexts-C7puo0M7.cjs");exports.Chapters=e.Chapters;exports.Ginger=e.Ginger;exports.LyricsSynced=e.LyricsSynced;exports.Pause=e.Pause;exports.Play=e.Play;exports.RepeatGlyph=e.RepeatGlyph;exports.ShuffleIcon=e.ShuffleIcon;exports.SkipBack=e.SkipBack;exports.SkipForward=e.SkipForward;exports.Volume2=e.Volume2;exports.VolumeX=e.VolumeX;exports.Wrapper=e.Wrapper;exports.clampPlaybackRate=e.clampPlaybackRate;exports.clampVolume=e.clampVolume;exports.defaultGingerLocale=e.defaultGingerLocale;exports.parseLrc=e.parseLrc;exports.useGingerChapters=e.useGingerChapters;exports.useGingerLocale=e.useGingerLocale;exports.useGingerLyricsSync=e.useGingerLyricsSync;exports.usePlayPauseBinding=e.usePlayPauseBinding;exports.useSeekBarBinding=e.useSeekBarBinding;exports.useVolumeSlider=e.useVolumeSlider;exports.useGinger=s.useGinger;exports.createGingerStore=r.createGingerStore;exports.useGingerChapterProgress=r.useGingerChapterProgress;exports.useGingerDebugLog=r.useGingerDebugLog;exports.useGingerKeyboardShortcuts=r.useGingerKeyboardShortcuts;exports.useGingerLiveAnalyzer=r.useGingerLiveAnalyzer;exports.useGingerPlaybackHistory=r.useGingerPlaybackHistory;exports.useGingerSleepTimer=r.useGingerSleepTimer;exports.useGingerVolumeFade=r.useGingerVolumeFade;exports.useNextTrackPrefetch=r.useNextTrackPrefetch;exports.useSeekDrag=r.useSeekDrag;exports.attachLiveAnalyser=i.attachLiveAnalyser;exports.detachLiveAnalyser=i.detachLiveAnalyser;exports.setProcessingChain=i.setProcessingChain;exports.derivePlaybackUiState=n.derivePlaybackUiState;exports.gingerStateFromContextValues=a.gingerStateFromContextValues;exports.gingerStateFromContexts=a.gingerStateFromContexts;exports.useGingerMedia=a.useGingerMedia;exports.useGingerPlayback=a.useGingerPlayback;exports.useGingerState=a.useGingerState;
2
2
  //# sourceMappingURL=client.cjs.map
package/dist/client.js CHANGED
@@ -1,50 +1,51 @@
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";
1
+ import { C as s, G as r, L as i, P as t, a as u, R as n, S as o, b as g, c, V as l, d as p, W as G, e as m, f as y, g as S, p as d, u as f, h, i as P, j as k, k as L, l as b } from "./ginger-L2ZFgzH4.js";
2
+ import { u as C } from "./useGinger-hpp2pAGY.js";
3
+ import { c as v, u as B, a as F, b as A, d as R, e as D, f as T, g as W, h as j, i as w } from "./useGingerChapterProgress-CZdv-HiI.js";
4
+ import { a as H, d as I, s as K } from "./liveAudioGraph-DvPaxBCP.js";
5
+ import { d as N } from "./selectors-BalBCc7X.js";
6
+ import { g as X, a as q, u as E, b as J, c as O } from "./GingerSplitContexts-BzBExb95.js";
6
7
  export {
7
8
  s as Chapters,
8
9
  r as Ginger,
9
10
  i as LyricsSynced,
10
- u as Pause,
11
- t as Play,
11
+ t as Pause,
12
+ u as Play,
12
13
  n as RepeatGlyph,
13
- g as ShuffleIcon,
14
- o as SkipBack,
14
+ o as ShuffleIcon,
15
+ g as SkipBack,
15
16
  c as SkipForward,
16
17
  l as Volume2,
17
18
  p as VolumeX,
18
19
  G as Wrapper,
19
- C as attachLiveAnalyser,
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,
30
- B as useGinger,
31
- R as useGingerChapterProgress,
32
- h as useGingerChapters,
33
- D as useGingerDebugLog,
34
- T as useGingerKeyboardShortcuts,
35
- W as useGingerLiveAnalyzer,
36
- P as useGingerLocale,
37
- f as useGingerLyricsSync,
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,
20
+ H as attachLiveAnalyser,
21
+ m as clampPlaybackRate,
22
+ y as clampVolume,
23
+ v as createGingerStore,
24
+ S as defaultGingerLocale,
25
+ N as derivePlaybackUiState,
26
+ I as detachLiveAnalyser,
27
+ X as gingerStateFromContextValues,
28
+ q as gingerStateFromContexts,
29
+ d as parseLrc,
30
+ K as setProcessingChain,
31
+ C as useGinger,
32
+ B as useGingerChapterProgress,
33
+ f as useGingerChapters,
34
+ F as useGingerDebugLog,
35
+ A as useGingerKeyboardShortcuts,
36
+ R as useGingerLiveAnalyzer,
37
+ h as useGingerLocale,
38
+ P as useGingerLyricsSync,
39
+ E as useGingerMedia,
40
+ J as useGingerPlayback,
41
+ D as useGingerPlaybackHistory,
42
+ T as useGingerSleepTimer,
43
+ O as useGingerState,
44
+ W as useGingerVolumeFade,
45
+ j as useNextTrackPrefetch,
45
46
  k as usePlayPauseBinding,
46
47
  L as useSeekBarBinding,
47
- I as useSeekDrag,
48
+ w as useSeekDrag,
48
49
  b as useVolumeSlider
49
50
  };
50
51
  //# sourceMappingURL=client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;"}
1
+ {"version":3,"file":"client.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("react"),c=require("../liveAudioGraph-D1BXMv_u.cjs"),B=[{frequency:60},{frequency:250},{frequency:1e3},{frequency:4e3},{frequency:16e3}];function C(q={}){const{enabled:d=!0,bands:m=B}=q,{audioRef:o,state:h}=c.useGinger(),[l,g]=s.useState(m),[p,y]=s.useState(null),a=s.useRef([]);s.useEffect(()=>{const e=o.current;if(!(!e||typeof window>"u")){if(!d){c.setProcessingChain(e,[]),a.current=[];return}try{const t=c.attachLiveAnalyser(e,{fftSize:32,smoothingTimeConstant:0,minDecibels:-100,maxDecibels:0}),{context:n,id:f}=t,i=l.map(r=>{const u=n.createBiquadFilter();return u.type=r.type??"peaking",u.frequency.value=r.frequency,u.gain.value=r.gain??0,u.Q.value=r.q??1,u});a.current=i,c.setProcessingChain(e,i),c.detachLiveAnalyser(e,f),y(null)}catch(t){const n=t instanceof Error?t.message:"Failed to create equalizer";y(n),a.current=[]}return()=>{const t=o.current;t&&c.setProcessingChain(t,[]),a.current=[]}}},[d,l,o,h.currentIndex]);const v=s.useCallback((e,t)=>{const n=a.current[e];n&&(n.gain.value=t),g(f=>f.map((i,r)=>r===e?{...i,gain:t}:i))},[]),S=s.useCallback(e=>{g(e)},[]);return{setBandGain:v,setBands:S,bands:l,error:p}}exports.useGingerEqualizer=C;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("react"),a=require("../liveAudioGraph-0cpHD_Ic.cjs"),B=require("../useGinger-BXgia32v.cjs"),C=[{frequency:60},{frequency:250},{frequency:1e3},{frequency:4e3},{frequency:16e3}];function E(y={}){const{enabled:d=!0,bands:m=C}=y,{audioRef:o,state:h}=B.useGinger(),[l,g]=s.useState(m),[p,q]=s.useState(null),c=s.useRef([]);s.useEffect(()=>{const e=o.current;if(!(!e||typeof window>"u")){if(!d){a.setProcessingChain(e,[]),c.current=[];return}try{const t=a.attachLiveAnalyser(e,{fftSize:32,smoothingTimeConstant:0,minDecibels:-100,maxDecibels:0}),{context:n,id:f}=t,i=l.map(r=>{const u=n.createBiquadFilter();return u.type=r.type??"peaking",u.frequency.value=r.frequency,u.gain.value=r.gain??0,u.Q.value=r.q??1,u});c.current=i,a.setProcessingChain(e,i),a.detachLiveAnalyser(e,f),q(null)}catch(t){const n=t instanceof Error?t.message:"Failed to create equalizer";q(n),c.current=[]}return()=>{const t=o.current;t&&a.setProcessingChain(t,[]),c.current=[]}}},[d,l,o,h.currentIndex]);const v=s.useCallback((e,t)=>{const n=c.current[e];n&&(n.gain.value=t),g(f=>f.map((i,r)=>r===e?{...i,gain:t}:i))},[]),S=s.useCallback(e=>{g(e)},[]);return{setBandGain:v,setBands:S,bands:l,error:p}}exports.useGingerEqualizer=E;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../../src/equalizer/useGingerEqualizer.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n attachLiveAnalyser,\n detachLiveAnalyser,\n setProcessingChain,\n} from \"../analyzer/liveAudioGraph\";\nimport { useGinger } from \"../hooks/useGinger\";\n\nexport type EqualizerBand = {\n /** Center frequency in Hz (e.g. 60, 250, 1000, 4000, 16000). */\n frequency: number;\n /** Gain in dB. Positive = boost, negative = cut. Typical range: -12 to +12. Default: 0. */\n gain?: number;\n /** Q factor (bandwidth). Higher = narrower. Default: 1. */\n q?: number;\n /** BiquadFilterNode type. Default: \"peaking\". */\n type?: BiquadFilterType;\n};\n\nexport type UseGingerEqualizerOptions = {\n /** When false, EQ nodes are detached and the audio path is bypassed. Default: true. */\n enabled?: boolean;\n /** Band definitions. Each band maps to one BiquadFilterNode. */\n bands?: EqualizerBand[];\n};\n\nexport type UseGingerEqualizerResult = {\n /** Update a single band's gain in dB without rebuilding the filter chain. */\n setBandGain: (bandIndex: number, gainDb: number) => void;\n /** Replace all bands (rebuilds the filter chain). */\n setBands: (bands: EqualizerBand[]) => void;\n /** Current band definitions (reflects the latest `setBands` call). */\n bands: EqualizerBand[];\n /** Error string if Web Audio is unavailable or filter creation failed. */\n error: string | null;\n};\n\nconst DEFAULT_BANDS: EqualizerBand[] = [\n { frequency: 60 },\n { frequency: 250 },\n { frequency: 1000 },\n { frequency: 4000 },\n { frequency: 16000 },\n];\n\n/**\n * Inserts a parametric EQ into the Web Audio graph for the active Ginger media element.\n *\n * Each band is a `BiquadFilterNode` connected in series between the audio source and the\n * speakers. If `useGingerLiveAnalyzer` is also active, the EQ is inserted before the analyser\n * — both share the same `AudioContext` (the browser allows only one `MediaElementAudioSourceNode`\n * per element).\n *\n * Available as a subpath export:\n * ```ts\n * import { useGingerEqualizer } from \"@lucaismyname/ginger/equalizer\";\n * ```\n */\nexport function useGingerEqualizer(\n options: UseGingerEqualizerOptions = {},\n): UseGingerEqualizerResult {\n const { enabled = true, bands: initialBands = DEFAULT_BANDS } = options;\n const { audioRef, state } = useGinger();\n\n const [bands, setBandsState] = useState<EqualizerBand[]>(initialBands);\n const [error, setError] = useState<string | null>(null);\n\n const filterNodesRef = useRef<BiquadFilterNode[]>([]);\n\n useEffect(() => {\n const el = audioRef.current;\n if (!el || typeof window === \"undefined\") {\n return;\n }\n\n if (!enabled) {\n setProcessingChain(el, []);\n filterNodesRef.current = [];\n return;\n }\n\n try {\n // Attach a temporary analyser solely to get (or create) the shared AudioContext.\n // The graph is rebuilt by liveAudioGraph after we call setProcessingChain, so this\n // temporary consumer is detached immediately after we have the context reference.\n const attached = attachLiveAnalyser(el, {\n fftSize: 32,\n smoothingTimeConstant: 0,\n minDecibels: -100,\n maxDecibels: 0,\n });\n const { context, id: tempId } = attached;\n\n const filters: BiquadFilterNode[] = bands.map((band) => {\n const node = context.createBiquadFilter();\n node.type = band.type ?? \"peaking\";\n node.frequency.value = band.frequency;\n node.gain.value = band.gain ?? 0;\n node.Q.value = band.q ?? 1;\n return node;\n });\n\n filterNodesRef.current = filters;\n\n // Install processing chain BEFORE removing the temp analyser so the graph stays valid\n setProcessingChain(el, filters);\n detachLiveAnalyser(el, tempId);\n\n setError(null);\n } catch (e) {\n const msg = e instanceof Error ? e.message : \"Failed to create equalizer\";\n setError(msg);\n filterNodesRef.current = [];\n }\n\n return () => {\n const element = audioRef.current;\n if (element) {\n setProcessingChain(element, []);\n }\n filterNodesRef.current = [];\n };\n }, [enabled, bands, audioRef, state.currentIndex]);\n\n const setBandGain = useCallback((bandIndex: number, gainDb: number) => {\n const node = filterNodesRef.current[bandIndex];\n if (node) {\n node.gain.value = gainDb;\n }\n setBandsState((prev) => prev.map((b, i) => (i === bandIndex ? { ...b, gain: gainDb } : b)));\n }, []);\n\n const setBands = useCallback((nextBands: EqualizerBand[]) => {\n setBandsState(nextBands);\n }, []);\n\n return { setBandGain, setBands, bands, error };\n}\n"],"names":["DEFAULT_BANDS","useGingerEqualizer","options","enabled","initialBands","audioRef","state","useGinger","bands","setBandsState","useState","error","setError","filterNodesRef","useRef","useEffect","el","setProcessingChain","attached","attachLiveAnalyser","context","tempId","filters","band","node","detachLiveAnalyser","e","msg","element","setBandGain","useCallback","bandIndex","gainDb","prev","b","i","setBands","nextBands"],"mappings":"qJAqCMA,EAAiC,CACrC,CAAE,UAAW,EAAA,EACb,CAAE,UAAW,GAAA,EACb,CAAE,UAAW,GAAA,EACb,CAAE,UAAW,GAAA,EACb,CAAE,UAAW,IAAA,CACf,EAeO,SAASC,EACdC,EAAqC,GACX,CAC1B,KAAM,CAAE,QAAAC,EAAU,GAAM,MAAOC,EAAeJ,GAAkBE,EAC1D,CAAE,SAAAG,EAAU,MAAAC,CAAA,EAAUC,YAAA,EAEtB,CAACC,EAAOC,CAAa,EAAIC,EAAAA,SAA0BN,CAAY,EAC/D,CAACO,EAAOC,CAAQ,EAAIF,EAAAA,SAAwB,IAAI,EAEhDG,EAAiBC,EAAAA,OAA2B,EAAE,EAEpDC,EAAAA,UAAU,IAAM,CACd,MAAMC,EAAKX,EAAS,QACpB,GAAI,GAACW,GAAM,OAAO,OAAW,KAI7B,IAAI,CAACb,EAAS,CACZc,EAAAA,mBAAmBD,EAAI,EAAE,EACzBH,EAAe,QAAU,CAAA,EACzB,MACF,CAEA,GAAI,CAIF,MAAMK,EAAWC,EAAAA,mBAAmBH,EAAI,CACtC,QAAS,GACT,sBAAuB,EACvB,YAAa,KACb,YAAa,CAAA,CACd,EACK,CAAE,QAAAI,EAAS,GAAIC,CAAA,EAAWH,EAE1BI,EAA8Bd,EAAM,IAAKe,GAAS,CACtD,MAAMC,EAAOJ,EAAQ,mBAAA,EACrB,OAAAI,EAAK,KAAOD,EAAK,MAAQ,UACzBC,EAAK,UAAU,MAAQD,EAAK,UAC5BC,EAAK,KAAK,MAAQD,EAAK,MAAQ,EAC/BC,EAAK,EAAE,MAAQD,EAAK,GAAK,EAClBC,CACT,CAAC,EAEDX,EAAe,QAAUS,EAGzBL,EAAAA,mBAAmBD,EAAIM,CAAO,EAC9BG,EAAAA,mBAAmBT,EAAIK,CAAM,EAE7BT,EAAS,IAAI,CACf,OAASc,EAAG,CACV,MAAMC,EAAMD,aAAa,MAAQA,EAAE,QAAU,6BAC7Cd,EAASe,CAAG,EACZd,EAAe,QAAU,CAAA,CAC3B,CAEA,MAAO,IAAM,CACX,MAAMe,EAAUvB,EAAS,QACrBuB,GACFX,EAAAA,mBAAmBW,EAAS,EAAE,EAEhCf,EAAe,QAAU,CAAA,CAC3B,EACF,EAAG,CAACV,EAASK,EAAOH,EAAUC,EAAM,YAAY,CAAC,EAEjD,MAAMuB,EAAcC,EAAAA,YAAY,CAACC,EAAmBC,IAAmB,CACrE,MAAMR,EAAOX,EAAe,QAAQkB,CAAS,EACzCP,IACFA,EAAK,KAAK,MAAQQ,GAEpBvB,EAAewB,GAASA,EAAK,IAAI,CAACC,EAAGC,IAAOA,IAAMJ,EAAY,CAAE,GAAGG,EAAG,KAAMF,CAAA,EAAWE,CAAE,CAAC,CAC5F,EAAG,CAAA,CAAE,EAECE,EAAWN,cAAaO,GAA+B,CAC3D5B,EAAc4B,CAAS,CACzB,EAAG,CAAA,CAAE,EAEL,MAAO,CAAE,YAAAR,EAAa,SAAAO,EAAU,MAAA5B,EAAO,MAAAG,CAAA,CACzC"}
1
+ {"version":3,"file":"index.cjs","sources":["../../src/equalizer/useGingerEqualizer.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n attachLiveAnalyser,\n detachLiveAnalyser,\n setProcessingChain,\n} from \"../analyzer/liveAudioGraph\";\nimport { useGinger } from \"../hooks/useGinger\";\n\nexport type EqualizerBand = {\n /** Center frequency in Hz (e.g. 60, 250, 1000, 4000, 16000). */\n frequency: number;\n /** Gain in dB. Positive = boost, negative = cut. Typical range: -12 to +12. Default: 0. */\n gain?: number;\n /** Q factor (bandwidth). Higher = narrower. Default: 1. */\n q?: number;\n /** BiquadFilterNode type. Default: \"peaking\". */\n type?: BiquadFilterType;\n};\n\nexport type UseGingerEqualizerOptions = {\n /** When false, EQ nodes are detached and the audio path is bypassed. Default: true. */\n enabled?: boolean;\n /** Band definitions. Each band maps to one BiquadFilterNode. */\n bands?: EqualizerBand[];\n};\n\nexport type UseGingerEqualizerResult = {\n /** Update a single band's gain in dB without rebuilding the filter chain. */\n setBandGain: (bandIndex: number, gainDb: number) => void;\n /** Replace all bands (rebuilds the filter chain). */\n setBands: (bands: EqualizerBand[]) => void;\n /** Current band definitions (reflects the latest `setBands` call). */\n bands: EqualizerBand[];\n /** Error string if Web Audio is unavailable or filter creation failed. */\n error: string | null;\n};\n\nconst DEFAULT_BANDS: EqualizerBand[] = [\n { frequency: 60 },\n { frequency: 250 },\n { frequency: 1000 },\n { frequency: 4000 },\n { frequency: 16000 },\n];\n\n/**\n * Inserts a parametric EQ into the Web Audio graph for the active Ginger media element.\n *\n * Each band is a `BiquadFilterNode` connected in series between the audio source and the\n * speakers. If `useGingerLiveAnalyzer` is also active, the EQ is inserted before the analyser\n * — both share the same `AudioContext` (the browser allows only one `MediaElementAudioSourceNode`\n * per element).\n *\n * Available as a subpath export:\n * ```ts\n * import { useGingerEqualizer } from \"@lucaismyname/ginger/equalizer\";\n * ```\n */\nexport function useGingerEqualizer(\n options: UseGingerEqualizerOptions = {},\n): UseGingerEqualizerResult {\n const { enabled = true, bands: initialBands = DEFAULT_BANDS } = options;\n const { audioRef, state } = useGinger();\n\n const [bands, setBandsState] = useState<EqualizerBand[]>(initialBands);\n const [error, setError] = useState<string | null>(null);\n\n const filterNodesRef = useRef<BiquadFilterNode[]>([]);\n\n useEffect(() => {\n const el = audioRef.current;\n if (!el || typeof window === \"undefined\") {\n return;\n }\n\n if (!enabled) {\n setProcessingChain(el, []);\n filterNodesRef.current = [];\n return;\n }\n\n try {\n // Attach a temporary analyser solely to get (or create) the shared AudioContext.\n // The graph is rebuilt by liveAudioGraph after we call setProcessingChain, so this\n // temporary consumer is detached immediately after we have the context reference.\n const attached = attachLiveAnalyser(el, {\n fftSize: 32,\n smoothingTimeConstant: 0,\n minDecibels: -100,\n maxDecibels: 0,\n });\n const { context, id: tempId } = attached;\n\n const filters: BiquadFilterNode[] = bands.map((band) => {\n const node = context.createBiquadFilter();\n node.type = band.type ?? \"peaking\";\n node.frequency.value = band.frequency;\n node.gain.value = band.gain ?? 0;\n node.Q.value = band.q ?? 1;\n return node;\n });\n\n filterNodesRef.current = filters;\n\n // Install processing chain BEFORE removing the temp analyser so the graph stays valid\n setProcessingChain(el, filters);\n detachLiveAnalyser(el, tempId);\n\n setError(null);\n } catch (e) {\n const msg = e instanceof Error ? e.message : \"Failed to create equalizer\";\n setError(msg);\n filterNodesRef.current = [];\n }\n\n return () => {\n const element = audioRef.current;\n if (element) {\n setProcessingChain(element, []);\n }\n filterNodesRef.current = [];\n };\n }, [enabled, bands, audioRef, state.currentIndex]);\n\n const setBandGain = useCallback((bandIndex: number, gainDb: number) => {\n const node = filterNodesRef.current[bandIndex];\n if (node) {\n node.gain.value = gainDb;\n }\n setBandsState((prev) => prev.map((b, i) => (i === bandIndex ? { ...b, gain: gainDb } : b)));\n }, []);\n\n const setBands = useCallback((nextBands: EqualizerBand[]) => {\n setBandsState(nextBands);\n }, []);\n\n return { setBandGain, setBands, bands, error };\n}\n"],"names":["DEFAULT_BANDS","useGingerEqualizer","options","enabled","initialBands","audioRef","state","useGinger","bands","setBandsState","useState","error","setError","filterNodesRef","useRef","useEffect","el","setProcessingChain","attached","attachLiveAnalyser","context","tempId","filters","band","node","detachLiveAnalyser","e","msg","element","setBandGain","useCallback","bandIndex","gainDb","prev","b","i","setBands","nextBands"],"mappings":"4LAqCMA,EAAiC,CACrC,CAAE,UAAW,EAAA,EACb,CAAE,UAAW,GAAA,EACb,CAAE,UAAW,GAAA,EACb,CAAE,UAAW,GAAA,EACb,CAAE,UAAW,IAAA,CACf,EAeO,SAASC,EACdC,EAAqC,GACX,CAC1B,KAAM,CAAE,QAAAC,EAAU,GAAM,MAAOC,EAAeJ,GAAkBE,EAC1D,CAAE,SAAAG,EAAU,MAAAC,CAAA,EAAUC,YAAA,EAEtB,CAACC,EAAOC,CAAa,EAAIC,EAAAA,SAA0BN,CAAY,EAC/D,CAACO,EAAOC,CAAQ,EAAIF,EAAAA,SAAwB,IAAI,EAEhDG,EAAiBC,EAAAA,OAA2B,EAAE,EAEpDC,EAAAA,UAAU,IAAM,CACd,MAAMC,EAAKX,EAAS,QACpB,GAAI,GAACW,GAAM,OAAO,OAAW,KAI7B,IAAI,CAACb,EAAS,CACZc,EAAAA,mBAAmBD,EAAI,EAAE,EACzBH,EAAe,QAAU,CAAA,EACzB,MACF,CAEA,GAAI,CAIF,MAAMK,EAAWC,EAAAA,mBAAmBH,EAAI,CACtC,QAAS,GACT,sBAAuB,EACvB,YAAa,KACb,YAAa,CAAA,CACd,EACK,CAAE,QAAAI,EAAS,GAAIC,CAAA,EAAWH,EAE1BI,EAA8Bd,EAAM,IAAKe,GAAS,CACtD,MAAMC,EAAOJ,EAAQ,mBAAA,EACrB,OAAAI,EAAK,KAAOD,EAAK,MAAQ,UACzBC,EAAK,UAAU,MAAQD,EAAK,UAC5BC,EAAK,KAAK,MAAQD,EAAK,MAAQ,EAC/BC,EAAK,EAAE,MAAQD,EAAK,GAAK,EAClBC,CACT,CAAC,EAEDX,EAAe,QAAUS,EAGzBL,EAAAA,mBAAmBD,EAAIM,CAAO,EAC9BG,EAAAA,mBAAmBT,EAAIK,CAAM,EAE7BT,EAAS,IAAI,CACf,OAASc,EAAG,CACV,MAAMC,EAAMD,aAAa,MAAQA,EAAE,QAAU,6BAC7Cd,EAASe,CAAG,EACZd,EAAe,QAAU,CAAA,CAC3B,CAEA,MAAO,IAAM,CACX,MAAMe,EAAUvB,EAAS,QACrBuB,GACFX,EAAAA,mBAAmBW,EAAS,EAAE,EAEhCf,EAAe,QAAU,CAAA,CAC3B,EACF,EAAG,CAACV,EAASK,EAAOH,EAAUC,EAAM,YAAY,CAAC,EAEjD,MAAMuB,EAAcC,EAAAA,YAAY,CAACC,EAAmBC,IAAmB,CACrE,MAAMR,EAAOX,EAAe,QAAQkB,CAAS,EACzCP,IACFA,EAAK,KAAK,MAAQQ,GAEpBvB,EAAewB,GAASA,EAAK,IAAI,CAACC,EAAGC,IAAOA,IAAMJ,EAAY,CAAE,GAAGG,EAAG,KAAMF,CAAA,EAAWE,CAAE,CAAC,CAC5F,EAAG,CAAA,CAAE,EAECE,EAAWN,cAAaO,GAA+B,CAC3D5B,EAAc4B,CAAS,CACzB,EAAG,CAAA,CAAE,EAEL,MAAO,CAAE,YAAAR,EAAa,SAAAO,EAAU,MAAA5B,EAAO,MAAAG,CAAA,CACzC"}
@@ -1,5 +1,6 @@
1
- import { useState as y, useRef as A, useEffect as S, useCallback as q } from "react";
2
- import { u as x, s as f, a as z, d as C } from "../liveAudioGraph-CmEsdLgZ.js";
1
+ import { useState as y, useRef as A, useEffect as S, useCallback as p } from "react";
2
+ import { s as f, a as x, d as z } from "../liveAudioGraph-DvPaxBCP.js";
3
+ import { u as C } from "../useGinger-hpp2pAGY.js";
3
4
  const D = [
4
5
  { frequency: 60 },
5
6
  { frequency: 250 },
@@ -7,45 +8,45 @@ const D = [
7
8
  { frequency: 4e3 },
8
9
  { frequency: 16e3 }
9
10
  ];
10
- function L(g = {}) {
11
- const { enabled: l = !0, bands: p = D } = g, { audioRef: u, state: h } = x(), [i, d] = y(p), [v, m] = y(null), s = A([]);
11
+ function R(q = {}) {
12
+ const { enabled: l = !0, bands: g = D } = q, { audioRef: i, state: h } = C(), [o, d] = y(g), [v, m] = y(null), s = A([]);
12
13
  S(() => {
13
- const e = u.current;
14
+ const e = i.current;
14
15
  if (!(!e || typeof window > "u")) {
15
16
  if (!l) {
16
17
  f(e, []), s.current = [];
17
18
  return;
18
19
  }
19
20
  try {
20
- const t = z(e, {
21
+ const t = x(e, {
21
22
  fftSize: 32,
22
23
  smoothingTimeConstant: 0,
23
24
  minDecibels: -100,
24
25
  maxDecibels: 0
25
- }), { context: n, id: o } = t, c = i.map((r) => {
26
+ }), { context: n, id: u } = t, c = o.map((r) => {
26
27
  const a = n.createBiquadFilter();
27
28
  return a.type = r.type ?? "peaking", a.frequency.value = r.frequency, a.gain.value = r.gain ?? 0, a.Q.value = r.q ?? 1, a;
28
29
  });
29
- s.current = c, f(e, c), C(e, o), m(null);
30
+ s.current = c, f(e, c), z(e, u), m(null);
30
31
  } catch (t) {
31
32
  const n = t instanceof Error ? t.message : "Failed to create equalizer";
32
33
  m(n), s.current = [];
33
34
  }
34
35
  return () => {
35
- const t = u.current;
36
+ const t = i.current;
36
37
  t && f(t, []), s.current = [];
37
38
  };
38
39
  }
39
- }, [l, i, u, h.currentIndex]);
40
- const B = q((e, t) => {
40
+ }, [l, o, i, h.currentIndex]);
41
+ const B = p((e, t) => {
41
42
  const n = s.current[e];
42
- n && (n.gain.value = t), d((o) => o.map((c, r) => r === e ? { ...c, gain: t } : c));
43
- }, []), E = q((e) => {
43
+ n && (n.gain.value = t), d((u) => u.map((c, r) => r === e ? { ...c, gain: t } : c));
44
+ }, []), E = p((e) => {
44
45
  d(e);
45
46
  }, []);
46
- return { setBandGain: B, setBands: E, bands: i, error: v };
47
+ return { setBandGain: B, setBands: E, bands: o, error: v };
47
48
  }
48
49
  export {
49
- L as useGingerEqualizer
50
+ R as useGingerEqualizer
50
51
  };
51
52
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/equalizer/useGingerEqualizer.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n attachLiveAnalyser,\n detachLiveAnalyser,\n setProcessingChain,\n} from \"../analyzer/liveAudioGraph\";\nimport { useGinger } from \"../hooks/useGinger\";\n\nexport type EqualizerBand = {\n /** Center frequency in Hz (e.g. 60, 250, 1000, 4000, 16000). */\n frequency: number;\n /** Gain in dB. Positive = boost, negative = cut. Typical range: -12 to +12. Default: 0. */\n gain?: number;\n /** Q factor (bandwidth). Higher = narrower. Default: 1. */\n q?: number;\n /** BiquadFilterNode type. Default: \"peaking\". */\n type?: BiquadFilterType;\n};\n\nexport type UseGingerEqualizerOptions = {\n /** When false, EQ nodes are detached and the audio path is bypassed. Default: true. */\n enabled?: boolean;\n /** Band definitions. Each band maps to one BiquadFilterNode. */\n bands?: EqualizerBand[];\n};\n\nexport type UseGingerEqualizerResult = {\n /** Update a single band's gain in dB without rebuilding the filter chain. */\n setBandGain: (bandIndex: number, gainDb: number) => void;\n /** Replace all bands (rebuilds the filter chain). */\n setBands: (bands: EqualizerBand[]) => void;\n /** Current band definitions (reflects the latest `setBands` call). */\n bands: EqualizerBand[];\n /** Error string if Web Audio is unavailable or filter creation failed. */\n error: string | null;\n};\n\nconst DEFAULT_BANDS: EqualizerBand[] = [\n { frequency: 60 },\n { frequency: 250 },\n { frequency: 1000 },\n { frequency: 4000 },\n { frequency: 16000 },\n];\n\n/**\n * Inserts a parametric EQ into the Web Audio graph for the active Ginger media element.\n *\n * Each band is a `BiquadFilterNode` connected in series between the audio source and the\n * speakers. If `useGingerLiveAnalyzer` is also active, the EQ is inserted before the analyser\n * — both share the same `AudioContext` (the browser allows only one `MediaElementAudioSourceNode`\n * per element).\n *\n * Available as a subpath export:\n * ```ts\n * import { useGingerEqualizer } from \"@lucaismyname/ginger/equalizer\";\n * ```\n */\nexport function useGingerEqualizer(\n options: UseGingerEqualizerOptions = {},\n): UseGingerEqualizerResult {\n const { enabled = true, bands: initialBands = DEFAULT_BANDS } = options;\n const { audioRef, state } = useGinger();\n\n const [bands, setBandsState] = useState<EqualizerBand[]>(initialBands);\n const [error, setError] = useState<string | null>(null);\n\n const filterNodesRef = useRef<BiquadFilterNode[]>([]);\n\n useEffect(() => {\n const el = audioRef.current;\n if (!el || typeof window === \"undefined\") {\n return;\n }\n\n if (!enabled) {\n setProcessingChain(el, []);\n filterNodesRef.current = [];\n return;\n }\n\n try {\n // Attach a temporary analyser solely to get (or create) the shared AudioContext.\n // The graph is rebuilt by liveAudioGraph after we call setProcessingChain, so this\n // temporary consumer is detached immediately after we have the context reference.\n const attached = attachLiveAnalyser(el, {\n fftSize: 32,\n smoothingTimeConstant: 0,\n minDecibels: -100,\n maxDecibels: 0,\n });\n const { context, id: tempId } = attached;\n\n const filters: BiquadFilterNode[] = bands.map((band) => {\n const node = context.createBiquadFilter();\n node.type = band.type ?? \"peaking\";\n node.frequency.value = band.frequency;\n node.gain.value = band.gain ?? 0;\n node.Q.value = band.q ?? 1;\n return node;\n });\n\n filterNodesRef.current = filters;\n\n // Install processing chain BEFORE removing the temp analyser so the graph stays valid\n setProcessingChain(el, filters);\n detachLiveAnalyser(el, tempId);\n\n setError(null);\n } catch (e) {\n const msg = e instanceof Error ? e.message : \"Failed to create equalizer\";\n setError(msg);\n filterNodesRef.current = [];\n }\n\n return () => {\n const element = audioRef.current;\n if (element) {\n setProcessingChain(element, []);\n }\n filterNodesRef.current = [];\n };\n }, [enabled, bands, audioRef, state.currentIndex]);\n\n const setBandGain = useCallback((bandIndex: number, gainDb: number) => {\n const node = filterNodesRef.current[bandIndex];\n if (node) {\n node.gain.value = gainDb;\n }\n setBandsState((prev) => prev.map((b, i) => (i === bandIndex ? { ...b, gain: gainDb } : b)));\n }, []);\n\n const setBands = useCallback((nextBands: EqualizerBand[]) => {\n setBandsState(nextBands);\n }, []);\n\n return { setBandGain, setBands, bands, error };\n}\n"],"names":["DEFAULT_BANDS","useGingerEqualizer","options","enabled","initialBands","audioRef","state","useGinger","bands","setBandsState","useState","error","setError","filterNodesRef","useRef","useEffect","el","setProcessingChain","attached","attachLiveAnalyser","context","tempId","filters","band","node","detachLiveAnalyser","e","msg","element","setBandGain","useCallback","bandIndex","gainDb","prev","b","i","setBands","nextBands"],"mappings":";;AAqCA,MAAMA,IAAiC;AAAA,EACrC,EAAE,WAAW,GAAA;AAAA,EACb,EAAE,WAAW,IAAA;AAAA,EACb,EAAE,WAAW,IAAA;AAAA,EACb,EAAE,WAAW,IAAA;AAAA,EACb,EAAE,WAAW,KAAA;AACf;AAeO,SAASC,EACdC,IAAqC,IACX;AAC1B,QAAM,EAAE,SAAAC,IAAU,IAAM,OAAOC,IAAeJ,MAAkBE,GAC1D,EAAE,UAAAG,GAAU,OAAAC,EAAA,IAAUC,EAAA,GAEtB,CAACC,GAAOC,CAAa,IAAIC,EAA0BN,CAAY,GAC/D,CAACO,GAAOC,CAAQ,IAAIF,EAAwB,IAAI,GAEhDG,IAAiBC,EAA2B,EAAE;AAEpD,EAAAC,EAAU,MAAM;AACd,UAAMC,IAAKX,EAAS;AACpB,QAAI,GAACW,KAAM,OAAO,SAAW,MAI7B;AAAA,UAAI,CAACb,GAAS;AACZ,QAAAc,EAAmBD,GAAI,EAAE,GACzBH,EAAe,UAAU,CAAA;AACzB;AAAA,MACF;AAEA,UAAI;AAIF,cAAMK,IAAWC,EAAmBH,GAAI;AAAA,UACtC,SAAS;AAAA,UACT,uBAAuB;AAAA,UACvB,aAAa;AAAA,UACb,aAAa;AAAA,QAAA,CACd,GACK,EAAE,SAAAI,GAAS,IAAIC,EAAA,IAAWH,GAE1BI,IAA8Bd,EAAM,IAAI,CAACe,MAAS;AACtD,gBAAMC,IAAOJ,EAAQ,mBAAA;AACrB,iBAAAI,EAAK,OAAOD,EAAK,QAAQ,WACzBC,EAAK,UAAU,QAAQD,EAAK,WAC5BC,EAAK,KAAK,QAAQD,EAAK,QAAQ,GAC/BC,EAAK,EAAE,QAAQD,EAAK,KAAK,GAClBC;AAAA,QACT,CAAC;AAED,QAAAX,EAAe,UAAUS,GAGzBL,EAAmBD,GAAIM,CAAO,GAC9BG,EAAmBT,GAAIK,CAAM,GAE7BT,EAAS,IAAI;AAAA,MACf,SAASc,GAAG;AACV,cAAMC,IAAMD,aAAa,QAAQA,EAAE,UAAU;AAC7C,QAAAd,EAASe,CAAG,GACZd,EAAe,UAAU,CAAA;AAAA,MAC3B;AAEA,aAAO,MAAM;AACX,cAAMe,IAAUvB,EAAS;AACzB,QAAIuB,KACFX,EAAmBW,GAAS,EAAE,GAEhCf,EAAe,UAAU,CAAA;AAAA,MAC3B;AAAA;AAAA,EACF,GAAG,CAACV,GAASK,GAAOH,GAAUC,EAAM,YAAY,CAAC;AAEjD,QAAMuB,IAAcC,EAAY,CAACC,GAAmBC,MAAmB;AACrE,UAAMR,IAAOX,EAAe,QAAQkB,CAAS;AAC7C,IAAIP,MACFA,EAAK,KAAK,QAAQQ,IAEpBvB,EAAc,CAACwB,MAASA,EAAK,IAAI,CAACC,GAAGC,MAAOA,MAAMJ,IAAY,EAAE,GAAGG,GAAG,MAAMF,EAAA,IAAWE,CAAE,CAAC;AAAA,EAC5F,GAAG,CAAA,CAAE,GAECE,IAAWN,EAAY,CAACO,MAA+B;AAC3D,IAAA5B,EAAc4B,CAAS;AAAA,EACzB,GAAG,CAAA,CAAE;AAEL,SAAO,EAAE,aAAAR,GAAa,UAAAO,GAAU,OAAA5B,GAAO,OAAAG,EAAA;AACzC;"}
1
+ {"version":3,"file":"index.js","sources":["../../src/equalizer/useGingerEqualizer.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n attachLiveAnalyser,\n detachLiveAnalyser,\n setProcessingChain,\n} from \"../analyzer/liveAudioGraph\";\nimport { useGinger } from \"../hooks/useGinger\";\n\nexport type EqualizerBand = {\n /** Center frequency in Hz (e.g. 60, 250, 1000, 4000, 16000). */\n frequency: number;\n /** Gain in dB. Positive = boost, negative = cut. Typical range: -12 to +12. Default: 0. */\n gain?: number;\n /** Q factor (bandwidth). Higher = narrower. Default: 1. */\n q?: number;\n /** BiquadFilterNode type. Default: \"peaking\". */\n type?: BiquadFilterType;\n};\n\nexport type UseGingerEqualizerOptions = {\n /** When false, EQ nodes are detached and the audio path is bypassed. Default: true. */\n enabled?: boolean;\n /** Band definitions. Each band maps to one BiquadFilterNode. */\n bands?: EqualizerBand[];\n};\n\nexport type UseGingerEqualizerResult = {\n /** Update a single band's gain in dB without rebuilding the filter chain. */\n setBandGain: (bandIndex: number, gainDb: number) => void;\n /** Replace all bands (rebuilds the filter chain). */\n setBands: (bands: EqualizerBand[]) => void;\n /** Current band definitions (reflects the latest `setBands` call). */\n bands: EqualizerBand[];\n /** Error string if Web Audio is unavailable or filter creation failed. */\n error: string | null;\n};\n\nconst DEFAULT_BANDS: EqualizerBand[] = [\n { frequency: 60 },\n { frequency: 250 },\n { frequency: 1000 },\n { frequency: 4000 },\n { frequency: 16000 },\n];\n\n/**\n * Inserts a parametric EQ into the Web Audio graph for the active Ginger media element.\n *\n * Each band is a `BiquadFilterNode` connected in series between the audio source and the\n * speakers. If `useGingerLiveAnalyzer` is also active, the EQ is inserted before the analyser\n * — both share the same `AudioContext` (the browser allows only one `MediaElementAudioSourceNode`\n * per element).\n *\n * Available as a subpath export:\n * ```ts\n * import { useGingerEqualizer } from \"@lucaismyname/ginger/equalizer\";\n * ```\n */\nexport function useGingerEqualizer(\n options: UseGingerEqualizerOptions = {},\n): UseGingerEqualizerResult {\n const { enabled = true, bands: initialBands = DEFAULT_BANDS } = options;\n const { audioRef, state } = useGinger();\n\n const [bands, setBandsState] = useState<EqualizerBand[]>(initialBands);\n const [error, setError] = useState<string | null>(null);\n\n const filterNodesRef = useRef<BiquadFilterNode[]>([]);\n\n useEffect(() => {\n const el = audioRef.current;\n if (!el || typeof window === \"undefined\") {\n return;\n }\n\n if (!enabled) {\n setProcessingChain(el, []);\n filterNodesRef.current = [];\n return;\n }\n\n try {\n // Attach a temporary analyser solely to get (or create) the shared AudioContext.\n // The graph is rebuilt by liveAudioGraph after we call setProcessingChain, so this\n // temporary consumer is detached immediately after we have the context reference.\n const attached = attachLiveAnalyser(el, {\n fftSize: 32,\n smoothingTimeConstant: 0,\n minDecibels: -100,\n maxDecibels: 0,\n });\n const { context, id: tempId } = attached;\n\n const filters: BiquadFilterNode[] = bands.map((band) => {\n const node = context.createBiquadFilter();\n node.type = band.type ?? \"peaking\";\n node.frequency.value = band.frequency;\n node.gain.value = band.gain ?? 0;\n node.Q.value = band.q ?? 1;\n return node;\n });\n\n filterNodesRef.current = filters;\n\n // Install processing chain BEFORE removing the temp analyser so the graph stays valid\n setProcessingChain(el, filters);\n detachLiveAnalyser(el, tempId);\n\n setError(null);\n } catch (e) {\n const msg = e instanceof Error ? e.message : \"Failed to create equalizer\";\n setError(msg);\n filterNodesRef.current = [];\n }\n\n return () => {\n const element = audioRef.current;\n if (element) {\n setProcessingChain(element, []);\n }\n filterNodesRef.current = [];\n };\n }, [enabled, bands, audioRef, state.currentIndex]);\n\n const setBandGain = useCallback((bandIndex: number, gainDb: number) => {\n const node = filterNodesRef.current[bandIndex];\n if (node) {\n node.gain.value = gainDb;\n }\n setBandsState((prev) => prev.map((b, i) => (i === bandIndex ? { ...b, gain: gainDb } : b)));\n }, []);\n\n const setBands = useCallback((nextBands: EqualizerBand[]) => {\n setBandsState(nextBands);\n }, []);\n\n return { setBandGain, setBands, bands, error };\n}\n"],"names":["DEFAULT_BANDS","useGingerEqualizer","options","enabled","initialBands","audioRef","state","useGinger","bands","setBandsState","useState","error","setError","filterNodesRef","useRef","useEffect","el","setProcessingChain","attached","attachLiveAnalyser","context","tempId","filters","band","node","detachLiveAnalyser","e","msg","element","setBandGain","useCallback","bandIndex","gainDb","prev","b","i","setBands","nextBands"],"mappings":";;;AAqCA,MAAMA,IAAiC;AAAA,EACrC,EAAE,WAAW,GAAA;AAAA,EACb,EAAE,WAAW,IAAA;AAAA,EACb,EAAE,WAAW,IAAA;AAAA,EACb,EAAE,WAAW,IAAA;AAAA,EACb,EAAE,WAAW,KAAA;AACf;AAeO,SAASC,EACdC,IAAqC,IACX;AAC1B,QAAM,EAAE,SAAAC,IAAU,IAAM,OAAOC,IAAeJ,MAAkBE,GAC1D,EAAE,UAAAG,GAAU,OAAAC,EAAA,IAAUC,EAAA,GAEtB,CAACC,GAAOC,CAAa,IAAIC,EAA0BN,CAAY,GAC/D,CAACO,GAAOC,CAAQ,IAAIF,EAAwB,IAAI,GAEhDG,IAAiBC,EAA2B,EAAE;AAEpD,EAAAC,EAAU,MAAM;AACd,UAAMC,IAAKX,EAAS;AACpB,QAAI,GAACW,KAAM,OAAO,SAAW,MAI7B;AAAA,UAAI,CAACb,GAAS;AACZ,QAAAc,EAAmBD,GAAI,EAAE,GACzBH,EAAe,UAAU,CAAA;AACzB;AAAA,MACF;AAEA,UAAI;AAIF,cAAMK,IAAWC,EAAmBH,GAAI;AAAA,UACtC,SAAS;AAAA,UACT,uBAAuB;AAAA,UACvB,aAAa;AAAA,UACb,aAAa;AAAA,QAAA,CACd,GACK,EAAE,SAAAI,GAAS,IAAIC,EAAA,IAAWH,GAE1BI,IAA8Bd,EAAM,IAAI,CAACe,MAAS;AACtD,gBAAMC,IAAOJ,EAAQ,mBAAA;AACrB,iBAAAI,EAAK,OAAOD,EAAK,QAAQ,WACzBC,EAAK,UAAU,QAAQD,EAAK,WAC5BC,EAAK,KAAK,QAAQD,EAAK,QAAQ,GAC/BC,EAAK,EAAE,QAAQD,EAAK,KAAK,GAClBC;AAAA,QACT,CAAC;AAED,QAAAX,EAAe,UAAUS,GAGzBL,EAAmBD,GAAIM,CAAO,GAC9BG,EAAmBT,GAAIK,CAAM,GAE7BT,EAAS,IAAI;AAAA,MACf,SAASc,GAAG;AACV,cAAMC,IAAMD,aAAa,QAAQA,EAAE,UAAU;AAC7C,QAAAd,EAASe,CAAG,GACZd,EAAe,UAAU,CAAA;AAAA,MAC3B;AAEA,aAAO,MAAM;AACX,cAAMe,IAAUvB,EAAS;AACzB,QAAIuB,KACFX,EAAmBW,GAAS,EAAE,GAEhCf,EAAe,UAAU,CAAA;AAAA,MAC3B;AAAA;AAAA,EACF,GAAG,CAACV,GAASK,GAAOH,GAAUC,EAAM,YAAY,CAAC;AAEjD,QAAMuB,IAAcC,EAAY,CAACC,GAAmBC,MAAmB;AACrE,UAAMR,IAAOX,EAAe,QAAQkB,CAAS;AAC7C,IAAIP,MACFA,EAAK,KAAK,QAAQQ,IAEpBvB,EAAc,CAACwB,MAASA,EAAK,IAAI,CAACC,GAAGC,MAAOA,MAAMJ,IAAY,EAAE,GAAGG,GAAG,MAAMF,EAAA,IAAWE,CAAE,CAAC;AAAA,EAC5F,GAAG,CAAA,CAAE,GAECE,IAAWN,EAAY,CAACO,MAA+B;AAC3D,IAAA5B,EAAc4B,CAAS;AAAA,EACzB,GAAG,CAAA,CAAE;AAEL,SAAO,EAAE,aAAAR,GAAa,UAAAO,GAAU,OAAA5B,GAAO,OAAAG,EAAA;AACzC;"}
@@ -11,6 +11,9 @@ export type UseNextTrackPrefetchOptions = {
11
11
  * Warms the browser cache for the **logical** next track (same rules as the Next control:
12
12
  * `computeNextIndex` from queue, repeat, and playback mode) using a detached `HTMLAudioElement`
13
13
  * with `preload="auto"`. Safe to call alongside `Ginger.Player`; it does not replace main playback.
14
+ *
15
+ * The effect depends on the `tracks` array reference from context. If the parent recreates `tracks`
16
+ * every render, prefetch will restart repeatedly; keep a stable queue reference (e.g. memoize) when possible.
14
17
  */
15
18
  export declare function useNextTrackPrefetch(options?: UseNextTrackPrefetchOptions): void;
16
19
  //# sourceMappingURL=useNextTrackPrefetch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useNextTrackPrefetch.d.ts","sourceRoot":"","sources":["../../src/hooks/useNextTrackPrefetch.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,2BAA2B,GAAG;IACxC,kDAAkD;IAClD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,WAAW,CAAC,EAAE,EAAE,GAAG,WAAW,GAAG,iBAAiB,GAAG,SAAS,CAAC;CAChE,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,2BAAgC,GAAG,IAAI,CAsBpF"}
1
+ {"version":3,"file":"useNextTrackPrefetch.d.ts","sourceRoot":"","sources":["../../src/hooks/useNextTrackPrefetch.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,2BAA2B,GAAG;IACxC,kDAAkD;IAClD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,WAAW,CAAC,EAAE,EAAE,GAAG,WAAW,GAAG,iBAAiB,GAAG,SAAS,CAAC;CAChE,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,2BAAgC,GAAG,IAAI,CAsBpF"}
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./ginger-NEcOSSJD.cjs"),i=require("./liveAudioGraph-D1BXMv_u.cjs"),r=require("./useGingerChapterProgress-BOqUimE7.cjs"),s=require("./selectors-YXnP8Y8g.cjs"),a=require("./GingerSplitContexts-C7puo0M7.cjs");exports.Chapters=e.Chapters;exports.Ginger=e.Ginger;exports.LyricsSynced=e.LyricsSynced;exports.Pause=e.Pause;exports.Play=e.Play;exports.RepeatGlyph=e.RepeatGlyph;exports.ShuffleIcon=e.ShuffleIcon;exports.SkipBack=e.SkipBack;exports.SkipForward=e.SkipForward;exports.Volume2=e.Volume2;exports.VolumeX=e.VolumeX;exports.Wrapper=e.Wrapper;exports.clampPlaybackRate=e.clampPlaybackRate;exports.clampVolume=e.clampVolume;exports.defaultGingerLocale=e.defaultGingerLocale;exports.parseLrc=e.parseLrc;exports.useGingerChapters=e.useGingerChapters;exports.useGingerLocale=e.useGingerLocale;exports.useGingerLyricsSync=e.useGingerLyricsSync;exports.usePlayPauseBinding=e.usePlayPauseBinding;exports.useSeekBarBinding=e.useSeekBarBinding;exports.useVolumeSlider=e.useVolumeSlider;exports.attachLiveAnalyser=i.attachLiveAnalyser;exports.detachLiveAnalyser=i.detachLiveAnalyser;exports.setProcessingChain=i.setProcessingChain;exports.useGinger=i.useGinger;exports.createGingerStore=r.createGingerStore;exports.useGingerChapterProgress=r.useGingerChapterProgress;exports.useGingerDebugLog=r.useGingerDebugLog;exports.useGingerKeyboardShortcuts=r.useGingerKeyboardShortcuts;exports.useGingerLiveAnalyzer=r.useGingerLiveAnalyzer;exports.useGingerPlaybackHistory=r.useGingerPlaybackHistory;exports.useGingerSleepTimer=r.useGingerSleepTimer;exports.useGingerVolumeFade=r.useGingerVolumeFade;exports.useNextTrackPrefetch=r.useNextTrackPrefetch;exports.useSeekDrag=r.useSeekDrag;exports.derivePlaybackUiState=s.derivePlaybackUiState;exports.gingerStateFromContextValues=a.gingerStateFromContextValues;exports.gingerStateFromContexts=a.gingerStateFromContexts;exports.useGingerMedia=a.useGingerMedia;exports.useGingerPlayback=a.useGingerPlayback;exports.useGingerState=a.useGingerState;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./ginger-NEcOSSJD.cjs"),s=require("./useGinger-BXgia32v.cjs"),r=require("./useGingerChapterProgress-BdaalJvX.cjs"),i=require("./liveAudioGraph-0cpHD_Ic.cjs"),n=require("./selectors-YXnP8Y8g.cjs"),a=require("./GingerSplitContexts-C7puo0M7.cjs");exports.Chapters=e.Chapters;exports.Ginger=e.Ginger;exports.LyricsSynced=e.LyricsSynced;exports.Pause=e.Pause;exports.Play=e.Play;exports.RepeatGlyph=e.RepeatGlyph;exports.ShuffleIcon=e.ShuffleIcon;exports.SkipBack=e.SkipBack;exports.SkipForward=e.SkipForward;exports.Volume2=e.Volume2;exports.VolumeX=e.VolumeX;exports.Wrapper=e.Wrapper;exports.clampPlaybackRate=e.clampPlaybackRate;exports.clampVolume=e.clampVolume;exports.defaultGingerLocale=e.defaultGingerLocale;exports.parseLrc=e.parseLrc;exports.useGingerChapters=e.useGingerChapters;exports.useGingerLocale=e.useGingerLocale;exports.useGingerLyricsSync=e.useGingerLyricsSync;exports.usePlayPauseBinding=e.usePlayPauseBinding;exports.useSeekBarBinding=e.useSeekBarBinding;exports.useVolumeSlider=e.useVolumeSlider;exports.useGinger=s.useGinger;exports.createGingerStore=r.createGingerStore;exports.useGingerChapterProgress=r.useGingerChapterProgress;exports.useGingerDebugLog=r.useGingerDebugLog;exports.useGingerKeyboardShortcuts=r.useGingerKeyboardShortcuts;exports.useGingerLiveAnalyzer=r.useGingerLiveAnalyzer;exports.useGingerPlaybackHistory=r.useGingerPlaybackHistory;exports.useGingerSleepTimer=r.useGingerSleepTimer;exports.useGingerVolumeFade=r.useGingerVolumeFade;exports.useNextTrackPrefetch=r.useNextTrackPrefetch;exports.useSeekDrag=r.useSeekDrag;exports.attachLiveAnalyser=i.attachLiveAnalyser;exports.detachLiveAnalyser=i.detachLiveAnalyser;exports.setProcessingChain=i.setProcessingChain;exports.derivePlaybackUiState=n.derivePlaybackUiState;exports.gingerStateFromContextValues=a.gingerStateFromContextValues;exports.gingerStateFromContexts=a.gingerStateFromContexts;exports.useGingerMedia=a.useGingerMedia;exports.useGingerPlayback=a.useGingerPlayback;exports.useGingerState=a.useGingerState;
2
2
  //# sourceMappingURL=index.cjs.map
package/dist/index.js CHANGED
@@ -1,50 +1,51 @@
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";
1
+ import { C as s, G as r, L as i, P as t, a as u, R as n, S as o, b as g, c, V as l, d as p, W as G, e as m, f as y, g as S, p as d, u as f, h, i as P, j as k, k as L, l as b } from "./ginger-L2ZFgzH4.js";
2
+ import { u as C } from "./useGinger-hpp2pAGY.js";
3
+ import { c as v, u as B, a as F, b as A, d as R, e as D, f as T, g as W, h as j, i as w } from "./useGingerChapterProgress-CZdv-HiI.js";
4
+ import { a as H, d as I, s as K } from "./liveAudioGraph-DvPaxBCP.js";
5
+ import { d as N } from "./selectors-BalBCc7X.js";
6
+ import { g as X, a as q, u as E, b as J, c as O } from "./GingerSplitContexts-BzBExb95.js";
6
7
  export {
7
8
  s as Chapters,
8
9
  r as Ginger,
9
10
  i as LyricsSynced,
10
- u as Pause,
11
- t as Play,
11
+ t as Pause,
12
+ u as Play,
12
13
  n as RepeatGlyph,
13
- g as ShuffleIcon,
14
- o as SkipBack,
14
+ o as ShuffleIcon,
15
+ g as SkipBack,
15
16
  c as SkipForward,
16
17
  l as Volume2,
17
18
  p as VolumeX,
18
19
  G as Wrapper,
19
- C as attachLiveAnalyser,
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,
30
- B as useGinger,
31
- R as useGingerChapterProgress,
32
- h as useGingerChapters,
33
- D as useGingerDebugLog,
34
- T as useGingerKeyboardShortcuts,
35
- W as useGingerLiveAnalyzer,
36
- P as useGingerLocale,
37
- f as useGingerLyricsSync,
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,
20
+ H as attachLiveAnalyser,
21
+ m as clampPlaybackRate,
22
+ y as clampVolume,
23
+ v as createGingerStore,
24
+ S as defaultGingerLocale,
25
+ N as derivePlaybackUiState,
26
+ I as detachLiveAnalyser,
27
+ X as gingerStateFromContextValues,
28
+ q as gingerStateFromContexts,
29
+ d as parseLrc,
30
+ K as setProcessingChain,
31
+ C as useGinger,
32
+ B as useGingerChapterProgress,
33
+ f as useGingerChapters,
34
+ F as useGingerDebugLog,
35
+ A as useGingerKeyboardShortcuts,
36
+ R as useGingerLiveAnalyzer,
37
+ h as useGingerLocale,
38
+ P as useGingerLyricsSync,
39
+ E as useGingerMedia,
40
+ J as useGingerPlayback,
41
+ D as useGingerPlaybackHistory,
42
+ T as useGingerSleepTimer,
43
+ O as useGingerState,
44
+ W as useGingerVolumeFade,
45
+ j as useNextTrackPrefetch,
45
46
  k as usePlayPauseBinding,
46
47
  L as useSeekBarBinding,
47
- I as useSeekDrag,
48
+ w as useSeekDrag,
48
49
  b as useVolumeSlider
49
50
  };
50
51
  //# 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,2 @@
1
+ "use strict";const r=new WeakMap;function h(t){const e=2**Math.round(Math.log2(t));return Math.min(32768,Math.max(32,e))}function d(t){const{processingChain:e}=t;return e.length>0?e[e.length-1]:t.source}function a(t){const{source:e,processingChain:n,consumers:s,context:c}=t;try{e.disconnect()}catch{}for(const i of n)try{i.disconnect()}catch{}for(const{analyser:i}of s.values())try{i.disconnect(c.destination)}catch{}if(n.length>0){e.connect(n[0]);for(let i=0;i<n.length-1;i++)n[i].connect(n[i+1])}const o=d(t);if(s.size===0)o.connect(c.destination);else{let i=!0;for(const{analyser:u}of s.values())o.connect(u),i&&(u.connect(c.destination),i=!1)}}function f(t){let e=r.get(t);if(!e){const n=window.AudioContext??window.webkitAudioContext;if(!n)throw new Error("Web Audio API is not available");const s=new n,c=s.createMediaElementSource(t);e={context:s,source:c,consumers:new Map,nextId:0,processingChain:[],processingActive:!1},r.set(t,e)}return e}function l(t,e){if(e.consumers.size===0&&!e.processingActive){try{e.source.disconnect()}catch{}e.context.close(),r.delete(t)}}function g(t,e){const n=f(t),{context:s}=n,c=s.createAnalyser();c.fftSize=h(e.fftSize),c.smoothingTimeConstant=e.smoothingTimeConstant,c.minDecibels=e.minDecibels,c.maxDecibels=e.maxDecibels;const o=n.nextId;return n.nextId+=1,n.consumers.set(o,{analyser:c}),a(n),{id:o,context:s,analyser:c}}function y(t,e){const n=r.get(t);if(!n)return;const s=n.consumers.get(e);if(s){try{s.analyser.disconnect()}catch{}if(n.consumers.delete(e),n.consumers.size===0&&!n.processingActive){l(t,n);return}a(n)}}function m(t,e){if(typeof window>"u")return;if(e.length===0){const s=r.get(t);if(!s)return;s.processingChain=[],s.processingActive=!1,s.consumers.size===0?l(t,s):a(s);return}const n=f(t);n.processingChain=e,n.processingActive=!0,a(n)}exports.attachLiveAnalyser=g;exports.detachLiveAnalyser=y;exports.setProcessingChain=m;
2
+ //# sourceMappingURL=liveAudioGraph-0cpHD_Ic.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"liveAudioGraph-0cpHD_Ic.cjs","sources":["../src/analyzer/liveAudioGraph.ts"],"sourcesContent":["/**\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":["entries","clampFftSize","n","p","getChainTail","entry","processingChain","rebuildGraph","source","consumers","context","node","analyser","tail","isFirst","getOrCreateEntry","element","Context","maybeCloseEntry","attachLiveAnalyser","options","id","detachLiveAnalyser","consumer","setProcessingChain","nodes"],"mappings":"aA4BA,MAAMA,MAAc,QAEpB,SAASC,EAAaC,EAAmB,CACvC,MAAMC,EAAI,GAAK,KAAK,MAAM,KAAK,KAAKD,CAAC,CAAC,EACtC,OAAO,KAAK,IAAI,MAAO,KAAK,IAAI,GAAIC,CAAC,CAAC,CACxC,CAEA,SAASC,EAAaC,EAAgC,CACpD,KAAM,CAAE,gBAAAC,GAAoBD,EAC5B,OAAOC,EAAgB,OAAS,EAAIA,EAAgBA,EAAgB,OAAS,CAAC,EAAKD,EAAM,MAC3F,CAMA,SAASE,EAAaF,EAA2B,CAC/C,KAAM,CAAE,OAAAG,EAAQ,gBAAAF,EAAiB,UAAAG,EAAW,QAAAC,GAAYL,EAGxD,GAAI,CACFG,EAAO,WAAA,CACT,MAAQ,CAER,CAGA,UAAWG,KAAQL,EACjB,GAAI,CACFK,EAAK,WAAA,CACP,MAAQ,CAER,CAIF,SAAW,CAAE,SAAAC,CAAA,IAAcH,EAAU,SACnC,GAAI,CACFG,EAAS,WAAWF,EAAQ,WAAW,CACzC,MAAQ,CAER,CAIF,GAAIJ,EAAgB,OAAS,EAAG,CAC9BE,EAAO,QAAQF,EAAgB,CAAC,CAAE,EAClC,QAAS,EAAI,EAAG,EAAIA,EAAgB,OAAS,EAAG,IAC9CA,EAAgB,CAAC,EAAG,QAAQA,EAAgB,EAAI,CAAC,CAAE,CAEvD,CAEA,MAAMO,EAAOT,EAAaC,CAAK,EAE/B,GAAII,EAAU,OAAS,EAErBI,EAAK,QAAQH,EAAQ,WAAW,MAC3B,CAEL,IAAII,EAAU,GACd,SAAW,CAAE,SAAAF,CAAA,IAAcH,EAAU,SACnCI,EAAK,QAAQD,CAAQ,EACjBE,IACFF,EAAS,QAAQF,EAAQ,WAAW,EACpCI,EAAU,GAGhB,CACF,CAEA,SAASC,EAAiBC,EAAyC,CACjE,IAAIX,EAAQL,EAAQ,IAAIgB,CAAO,EAC/B,GAAI,CAACX,EAAO,CACV,MAAMY,EACJ,OAAO,cACN,OAAmE,mBACtE,GAAI,CAACA,EACH,MAAM,IAAI,MAAM,gCAAgC,EAElD,MAAMP,EAAU,IAAIO,EACdT,EAASE,EAAQ,yBAAyBM,CAAO,EACvDX,EAAQ,CACN,QAAAK,EACA,OAAAF,EACA,cAAe,IACf,OAAQ,EACR,gBAAiB,CAAA,EACjB,iBAAkB,EAAA,EAEpBR,EAAQ,IAAIgB,EAASX,CAAK,CAC5B,CACA,OAAOA,CACT,CAEA,SAASa,EAAgBF,EAA2BX,EAA2B,CAC7E,GAAIA,EAAM,UAAU,OAAS,GAAK,CAACA,EAAM,iBAAkB,CACzD,GAAI,CACFA,EAAM,OAAO,WAAA,CACf,MAAQ,CAER,CACKA,EAAM,QAAQ,MAAA,EACnBL,EAAQ,OAAOgB,CAAO,CACxB,CACF,CAEO,SAASG,EACdH,EACAI,EAC+D,CAC/D,MAAMf,EAAQU,EAAiBC,CAAO,EAChC,CAAE,QAAAN,GAAYL,EAEdO,EAAWF,EAAQ,eAAA,EACzBE,EAAS,QAAUX,EAAamB,EAAQ,OAAO,EAC/CR,EAAS,sBAAwBQ,EAAQ,sBACzCR,EAAS,YAAcQ,EAAQ,YAC/BR,EAAS,YAAcQ,EAAQ,YAE/B,MAAMC,EAAKhB,EAAM,OACjB,OAAAA,EAAM,QAAU,EAChBA,EAAM,UAAU,IAAIgB,EAAI,CAAE,SAAAT,EAAU,EAEpCL,EAAaF,CAAK,EAEX,CAAE,GAAAgB,EAAI,QAAAX,EAAS,SAAAE,CAAA,CACxB,CAEO,SAASU,EAAmBN,EAA2BK,EAAkB,CAC9E,MAAMhB,EAAQL,EAAQ,IAAIgB,CAAO,EACjC,GAAI,CAACX,EAAO,OAEZ,MAAMkB,EAAWlB,EAAM,UAAU,IAAIgB,CAAE,EACvC,GAAKE,EAEL,IAAI,CACFA,EAAS,SAAS,WAAA,CACpB,MAAQ,CAER,CAGA,GAFAlB,EAAM,UAAU,OAAOgB,CAAE,EAErBhB,EAAM,UAAU,OAAS,GAAK,CAACA,EAAM,iBAAkB,CACzDa,EAAgBF,EAASX,CAAK,EAC9B,MACF,CAEAE,EAAaF,CAAK,EACpB,CAUO,SAASmB,EAAmBR,EAA2BS,EAA0B,CACtF,GAAI,OAAO,OAAW,IAAa,OAEnC,GAAIA,EAAM,SAAW,EAAG,CACtB,MAAMpB,EAAQL,EAAQ,IAAIgB,CAAO,EACjC,GAAI,CAACX,EAAO,OACZA,EAAM,gBAAkB,CAAA,EACxBA,EAAM,iBAAmB,GACrBA,EAAM,UAAU,OAAS,EAC3Ba,EAAgBF,EAASX,CAAK,EAE9BE,EAAaF,CAAK,EAEpB,MACF,CAEA,MAAMA,EAAQU,EAAiBC,CAAO,EACtCX,EAAM,gBAAkBoB,EACxBpB,EAAM,iBAAmB,GACzBE,EAAaF,CAAK,CACpB"}