@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/README.md CHANGED
@@ -64,8 +64,47 @@ Mount **`<Ginger.Player />`** once inside the same provider tree so the hidden a
64
64
  - `@lucaismyname/ginger/client`
65
65
  - `@lucaismyname/ginger/testing`
66
66
  - `@lucaismyname/ginger/waveform`
67
+ - `@lucaismyname/ginger/equalizer`
67
68
  - `@lucaismyname/ginger/experimental-gapless`
68
69
 
70
+ ### Equalizer
71
+
72
+ ```tsx
73
+ import { useGingerEqualizer } from "@lucaismyname/ginger/equalizer";
74
+
75
+ function MyPlayer() {
76
+ const { setBandGain, bands, error } = useGingerEqualizer({
77
+ bands: [
78
+ { frequency: 60 },
79
+ { frequency: 250 },
80
+ { frequency: 1000 },
81
+ { frequency: 4000 },
82
+ { frequency: 16000 },
83
+ ],
84
+ });
85
+
86
+ return (
87
+ <div>
88
+ {bands.map((band, i) => (
89
+ <input
90
+ key={band.frequency}
91
+ type="range"
92
+ min={-12}
93
+ max={12}
94
+ step={0.5}
95
+ defaultValue={0}
96
+ onChange={(e) => setBandGain(i, Number(e.target.value))}
97
+ aria-label={`${band.frequency} Hz`}
98
+ />
99
+ ))}
100
+ {error && <p>{error}</p>}
101
+ </div>
102
+ );
103
+ }
104
+ ```
105
+
106
+ 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
+
69
108
  ### Experimental Notice
70
109
 
71
110
  `@lucaismyname/ginger/experimental-gapless` is intentionally non-production.
@@ -734,15 +773,15 @@ Example:
734
773
 
735
774
  - **Headless control bindings** (bind to your own components): **`useSeekBarBinding()`**, **`useVolumeSlider()`**, **`usePlayPauseBinding({ playAriaLabel?, pauseAriaLabel? })`**. Each returns props such as `value`, `min`, `max`, handlers, and `ariaLabel` / `ariaValueText` where relevant.
736
775
 
737
- - **Advanced hooks** — **`useGingerKeyboardShortcuts()`**, **`useGingerSleepTimer()`**, **`useSeekDrag()`**, **`useNextTrackPrefetch()`**, **`useGingerChapters()`**, **`useGingerLyricsSync()`**, and **`useGingerDebugLog()`** are available for custom UX and diagnostics.
776
+ - **Advanced hooks** — **`useGingerKeyboardShortcuts()`**, **`useGingerSleepTimer()`**, **`useSeekDrag()`**, **`useNextTrackPrefetch()`**, **`useGingerChapters()`**, **`useGingerChapterProgress()`**, **`useGingerLyricsSync()`**, **`useGingerDebugLog()`**, **`useGingerPlaybackHistory()`**, **`useGingerVolumeFade()`** are available for custom UX and diagnostics.
738
777
 
739
778
  - **Locale** — Pass **`locale={partialMessages}`** on `Ginger.Provider` (type **`GingerLocaleMessages`**) to translate built-in control strings, chapter list labels, and synced lyrics list names; **`useGingerLocale()`** reads the merged messages anywhere under the provider.
740
779
 
741
- - **Track extras** — Optional **`metadata?: Record<string, unknown>`** on **`Track`** (and on **`PlaylistMeta`**) is ignored by core logic; use it for badges, flags, or UI-only fields.
780
+ - **Track extras** — Optional **`metadata?: Record<string, unknown>`** on **`Track`** (and on **`PlaylistMeta`**) is ignored by core logic; use it for badges, flags, or UI-only fields. Chapter entries and timed lyric lines are now typed as the named exports **`TrackChapter`** and **`TrackLyricLine`** respectively.
742
781
 
743
782
  - **Buffered UI** — **`Ginger.Current.BufferRail`** shows load progress; **`Ginger.Current.TimeRail`** supports **`showBuffered`** to stack a buffered layer behind the played segment.
744
783
 
745
- - **Audio analyzers** — Live Web Audio data for real-time visuals (**`useGingerLiveAnalyzer`**, main package) and whole-file grids for waveforms or spectrograms (**`useAudioFileAnalysis`** / **`analyzeAudioFile`**, `@lucaismyname/ginger/waveform`). See [Audio analyzers (visualizations)](#audio-analyzers-visualizations).
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).
746
785
 
747
786
  Recipes below cover queue lifecycle and media edge cases.
748
787
 
@@ -787,7 +826,12 @@ Important:
787
826
  - **CORS** — For cross-origin `fileUrl` values, set **`crossOrigin`** on **`Ginger.Player`** (for example `"anonymous"`) so the media element is usable with **`AudioContext`**.
788
827
  - **One `MediaElementAudioSourceNode` per `<audio>`** — The library reuses a single Web Audio graph per underlying element. Multiple instances of **`useGingerLiveAnalyzer`** attach extra **`AnalyserNode`**s as taps; only one tap carries audio to **`destination`** so volume stays correct.
789
828
  - **Autoplay** — The **`AudioContext`** may start **`suspended`** until a user gesture; call **`resume()`** or start playback after interaction.
790
- - **Reading buffers** — `frequencyData` and `timeDomainData` are updated each animation frame while enabled; read them during render after **`frequencyBinCount > 0`** (they are backed by mutable buffers that the hook fills in a `requestAnimationFrame` loop).
829
+ - **Reading buffers** — `frequencyData` and `timeDomainData` are updated each animation frame while enabled; read them during render after **`frequencyBinCount > 0`** (they are backed by mutable buffers that the hook fills in a `requestAnimationFrame` loop). Because the array _reference_ never changes, use the returned **`frame`** counter as a `useMemo` / `useEffect` dependency to react to new data:
830
+
831
+ ```ts
832
+ const { frequencyData, frame } = useGingerLiveAnalyzer();
833
+ const peak = useMemo(() => Math.max(...frequencyData), [frame]);
834
+ ```
791
835
 
792
836
  ### Whole file: `@lucaismyname/ginger/waveform`
793
837
 
@@ -864,15 +908,18 @@ import { useGingerKeyboardShortcuts } from "@lucaismyname/ginger";
864
908
  function Hotkeys() {
865
909
  useGingerKeyboardShortcuts(true, {
866
910
  playPause: " ",
867
- next: "ArrowRight",
868
- previous: "ArrowLeft",
911
+ next: "n",
912
+ previous: "p",
869
913
  mute: "m",
914
+ seekForward: "ArrowRight",
915
+ seekBackward: "ArrowLeft",
916
+ seekSeconds: 5,
870
917
  });
871
918
  return null;
872
919
  }
873
920
  ```
874
921
 
875
- `mute` is optional; if omitted, no mute key binding is installed.
922
+ `mute`, `seekForward`, and `seekBackward` are all optional; keys are lower-cased before comparison. `seekSeconds` defaults to `5` when seek bindings are set.
876
923
 
877
924
  ### Chapters and synced lyrics
878
925
 
@@ -918,6 +965,76 @@ export function App() {
918
965
 
919
966
  The hook preloads the **logical** next track (same rules as the Next button) using a detached `HTMLAudioElement` with `preload="auto"`. Pass **`crossOrigin`** when it matches **`Ginger.Player`** for cross-origin URLs.
920
967
 
968
+ ### Playback history
969
+
970
+ ```tsx
971
+ import { useGingerPlaybackHistory } from "@lucaismyname/ginger";
972
+
973
+ function RecentTracks() {
974
+ const { history, clearHistory } = useGingerPlaybackHistory({ maxLength: 20 });
975
+ return (
976
+ <ul>
977
+ {history.map((entry, i) => (
978
+ <li key={i}>{entry.track.title}</li>
979
+ ))}
980
+ </ul>
981
+ );
982
+ }
983
+ ```
984
+
985
+ Records every track change in chronological order (most recent last). Useful for displaying play history or implementing "smart previous" in shuffle mode. History lives in component state and is reset on remount.
986
+
987
+ ### Volume fade
988
+
989
+ ```tsx
990
+ import { useGingerVolumeFade } from "@lucaismyname/ginger";
991
+
992
+ function FadeButton() {
993
+ const { fadeVolumeTo, cancelFade, isFading } = useGingerVolumeFade();
994
+ return (
995
+ <button
996
+ onClick={() => fadeVolumeTo({ targetVolume: 0, durationMs: 1500, onComplete: pause })}
997
+ disabled={isFading}
998
+ >
999
+ Fade out
1000
+ </button>
1001
+ );
1002
+ }
1003
+ ```
1004
+
1005
+ Smoothly interpolates volume over a given duration using `requestAnimationFrame`. Call `cancelFade()` to hold at the current level.
1006
+
1007
+ ### Chapter progress
1008
+
1009
+ ```tsx
1010
+ import { useGingerChapterProgress } from "@lucaismyname/ginger";
1011
+
1012
+ function ChapterBar() {
1013
+ const { progress, elapsed, remaining } = useGingerChapterProgress();
1014
+ return <progress value={progress} max={1} />;
1015
+ }
1016
+ ```
1017
+
1018
+ Returns `progress` (0–1 fraction through the active chapter), `elapsed` (seconds into it), and `remaining` (seconds until the next chapter or track end). Complements `useGingerChapters` for chapter scrubber UIs.
1019
+
1020
+ ### Framework-agnostic store
1021
+
1022
+ ```ts
1023
+ import { createGingerStore } from "@lucaismyname/ginger";
1024
+
1025
+ const store = createGingerStore({ tracks: myTracks });
1026
+
1027
+ const unsub = store.subscribe((state) => {
1028
+ console.log("current track index:", state.currentIndex);
1029
+ });
1030
+
1031
+ store.dispatch({ type: "NEXT" });
1032
+ store.init({ tracks: newTracks });
1033
+ unsub();
1034
+ ```
1035
+
1036
+ A pure-JS store wrapping `gingerReducer` with `getState`, `dispatch`, `subscribe`, and `init`. No React required — usable in Svelte, Vue, Node.js testing environments, or server-side rendering.
1037
+
921
1038
  ### Sleep timer and drag seek
922
1039
 
923
1040
  ```tsx
@@ -930,6 +1047,8 @@ function Extras({ duration }: { duration: number }) {
930
1047
  }
931
1048
  ```
932
1049
 
1050
+ When `respectPause` is `true` (default), pausing stops the countdown and elapsed time is preserved across pause/resume cycles — the timer picks up where it left off rather than restarting from the full duration.
1051
+
933
1052
  ### Persistence + resume
934
1053
 
935
1054
  `Ginger.Provider` accepts:
@@ -938,7 +1057,20 @@ function Extras({ duration }: { duration: number }) {
938
1057
  - `hydrateOnMount?: boolean`
939
1058
  - `resumeOnTrackChange?: boolean`
940
1059
 
941
- This allows persisted volume/rate/repeat/index and optional per-track resume positions.
1060
+ This allows persisted volume/rate/repeat/index and optional per-track resume positions. Errors thrown by the adapter are caught and logged as dev-mode warnings so a storage quota error never crashes the player.
1061
+
1062
+ ### Provider event callbacks
1063
+
1064
+ New callbacks available on `Ginger.Provider`:
1065
+
1066
+ - `onVolumeChange?: (volume: number, muted: boolean) => void` — fired when volume or mute state changes
1067
+ - `onPlaybackRateChange?: (rate: number) => void` — fired when playback speed changes
1068
+ - `onSeek?: (timeSeconds: number) => void` — fired on any `seek()` call
1069
+
1070
+ ### Provider layout props
1071
+
1072
+ - `dir?: "ltr" | "rtl" | "auto"` — explicit layout direction; takes priority over the automatic RTL heuristic derived from locale strings
1073
+ - `prevRestartThresholdSeconds?: number` — pressing previous restarts the current track when `currentTime > threshold` (default `3`); set to `0` to always skip to the previous track
942
1074
 
943
1075
  ### Queue mutation actions and single-track mode
944
1076
 
@@ -1,4 +1,8 @@
1
- /** One MediaElementAudioSourceNode per HTMLAudioElement; multiple AnalyserNodes may tap the source. */
1
+ /**
2
+ * One MediaElementAudioSourceNode per HTMLAudioElement; multiple AnalyserNodes may tap the source.
3
+ * An optional processing chain (e.g. EQ filters) can be inserted between the source and the
4
+ * analysers via `setProcessingChain`.
5
+ */
2
6
  export type LiveAnalyserOptions = {
3
7
  fftSize: number;
4
8
  smoothingTimeConstant: number;
@@ -11,4 +15,13 @@ export declare function attachLiveAnalyser(element: HTMLAudioElement, options: L
11
15
  analyser: AnalyserNode;
12
16
  };
13
17
  export declare function detachLiveAnalyser(element: HTMLAudioElement, id: number): void;
18
+ /**
19
+ * Insert an ordered processing chain (e.g. BiquadFilterNode[]) between the audio source and the
20
+ * analyser consumers. Pass an empty array to clear the chain.
21
+ *
22
+ * Safe to call while analyser consumers are active; the graph is rebuilt immediately.
23
+ * Note: because `createMediaElementSource` can only be called once per element, the EQ and live
24
+ * analyser share the same AudioContext. Calling both for the same element is supported.
25
+ */
26
+ export declare function setProcessingChain(element: HTMLAudioElement, nodes: AudioNode[]): void;
14
27
  //# sourceMappingURL=liveAudioGraph.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"liveAudioGraph.d.ts","sourceRoot":"","sources":["../../src/analyzer/liveAudioGraph.ts"],"names":[],"mappings":"AAAA,uGAAuG;AAEvG,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAsBF,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE,mBAAmB,GAC3B;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,YAAY,CAAC;IAAC,QAAQ,EAAE,YAAY,CAAA;CAAE,CAkC/D;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CA6B9E"}
1
+ {"version":3,"file":"liveAudioGraph.d.ts","sourceRoot":"","sources":["../../src/analyzer/liveAudioGraph.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AA2HF,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE,mBAAmB,GAC3B;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,YAAY,CAAC;IAAC,QAAQ,EAAE,YAAY,CAAA;CAAE,CAiB/D;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAoB9E;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,IAAI,CAoBtF"}
@@ -16,6 +16,12 @@ export type UseGingerLiveAnalyzerResult = {
16
16
  isSuspended: boolean;
17
17
  error: string | null;
18
18
  resume: () => Promise<void>;
19
+ /**
20
+ * Monotonically increasing counter incremented each animation frame.
21
+ * Use as a `useMemo` / `useEffect` dependency to respond to new audio data, since
22
+ * `frequencyData` and `timeDomainData` are mutated in-place (their reference never changes).
23
+ */
24
+ frame: number;
19
25
  };
20
26
  export declare function useGingerLiveAnalyzer(options?: UseGingerLiveAnalyzerOptions): UseGingerLiveAnalyzerResult;
21
27
  //# sourceMappingURL=useGingerLiveAnalyzer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useGingerLiveAnalyzer.d.ts","sourceRoot":"","sources":["../../src/analyzer/useGingerLiveAnalyzer.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,4BAA4B,GAAG;IACzC,iFAAiF;IACjF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,kHAAkH;IAClH,aAAa,EAAE,UAAU,CAAC;IAC1B,8DAA8D;IAC9D,cAAc,EAAE,UAAU,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B,CAAC;AAKF,wBAAgB,qBAAqB,CACnC,OAAO,GAAE,4BAAiC,GACzC,2BAA2B,CAkK7B"}
1
+ {"version":3,"file":"useGingerLiveAnalyzer.d.ts","sourceRoot":"","sources":["../../src/analyzer/useGingerLiveAnalyzer.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,4BAA4B,GAAG;IACzC,iFAAiF;IACjF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,kHAAkH;IAClH,aAAa,EAAE,UAAU,CAAC;IAC1B,8DAA8D;IAC9D,cAAc,EAAE,UAAU,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAKF,wBAAgB,qBAAqB,CACnC,OAAO,GAAE,4BAAiC,GACzC,2BAA2B,CAiK7B"}
package/dist/client.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./ginger-B26HM2Ja.cjs"),r=require("./useNextTrackPrefetch-wSILz6TL.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.derivePlaybackUiState=e.derivePlaybackUiState;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=r.attachLiveAnalyser;exports.detachLiveAnalyser=r.detachLiveAnalyser;exports.useGinger=r.useGinger;exports.useGingerDebugLog=r.useGingerDebugLog;exports.useGingerKeyboardShortcuts=r.useGingerKeyboardShortcuts;exports.useGingerLiveAnalyzer=r.useGingerLiveAnalyzer;exports.useGingerSleepTimer=r.useGingerSleepTimer;exports.useNextTrackPrefetch=r.useNextTrackPrefetch;exports.useSeekDrag=r.useSeekDrag;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"),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;
2
2
  //# sourceMappingURL=client.cjs.map
package/dist/client.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=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 +1 @@
1
- {"version":3,"file":"GingerPlaylist.d.ts","sourceRoot":"","sources":["../../../src/components/playlist/GingerPlaylist.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,SAAS,EAGf,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,EAAE,OAAO,CAAC;CACvB,CAAC;AAYF,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,cAAc,CAAC,gBAAgB,CAAC,EAAE,UAAU,CAAC,GAAG;IACrF,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,SAAS,CAAC;IAC5E,iFAAiF;IACjF,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,QAAgB,EAChB,QAAQ,EACR,WAAW,EACX,YAAmB,EACnB,KAAK,EACL,GAAG,IAAI,EACR,EAAE,mBAAmB,2CAuErB;yBA/Ee,cAAc;;;AAmF9B,MAAM,MAAM,wBAAwB,GAAG,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,GAAG;IAC7F,KAAK,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAC;CAC3C,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,QAAgB,EAChB,SAAS,EACT,KAAK,EACL,QAAQ,EACR,OAAO,EACP,OAAO,EACP,GAAG,UAAU,EACd,EAAE,wBAAwB,2CA+C1B;yBAxDe,mBAAmB;;;AA4DnC,eAAO,MAAM,sBAAsB;;CAEjC,CAAC"}
1
+ {"version":3,"file":"GingerPlaylist.d.ts","sourceRoot":"","sources":["../../../src/components/playlist/GingerPlaylist.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,SAAS,EAGf,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,EAAE,OAAO,CAAC;CACvB,CAAC;AAgBF,MAAM,MAAM,mBAAmB,GAAG,IAAI,CACpC,cAAc,CAAC,gBAAgB,CAAC,EAChC,UAAU,CACX,GAAG;IACF,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,SAAS,CAAC;IAC5E,iFAAiF;IACjF,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,QAAgB,EAChB,QAAQ,EACR,WAAW,EACX,YAAmB,EACnB,KAAK,EACL,GAAG,IAAI,EACR,EAAE,mBAAmB,2CA0ErB;yBAlFe,cAAc;;;AAsF9B,MAAM,MAAM,wBAAwB,GAAG,IAAI,CACzC,oBAAoB,CAAC,iBAAiB,CAAC,EACvC,MAAM,CACP,GAAG;IACF,KAAK,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAC;CAC3C,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,QAAgB,EAChB,SAAS,EACT,KAAK,EACL,QAAQ,EACR,OAAO,EACP,OAAO,EACP,GAAG,UAAU,EACd,EAAE,wBAAwB,2CAmD1B;yBA5De,mBAAmB;;;AAgEnC,eAAO,MAAM,sBAAsB;;CAEjC,CAAC"}
@@ -1,3 +1,3 @@
1
1
  import { GingerProviderProps } from '../types';
2
- export declare function GingerProvider({ children, initialTracks, initialIndex, initialPlaylistMeta, initialShuffle, initialRepeatMode, initialPlaybackMode, initialPaused, initialVolume, initialMuted, initialPlaybackRate, initialStateKey, locale, mediaSession, beforePlay, onPlayBlocked, persistence, hydrateOnMount, resumeOnTrackChange, unstyled, asChild, className, style, onTrackChange, onPlay, onPause, onQueueEnd, onError, }: GingerProviderProps): import("react/jsx-runtime").JSX.Element;
2
+ export declare function GingerProvider({ children, initialTracks, initialIndex, initialPlaylistMeta, initialShuffle, initialRepeatMode, initialPlaybackMode, initialPaused, initialVolume, initialMuted, initialPlaybackRate, initialStateKey, locale, mediaSession, beforePlay, onPlayBlocked, persistence, hydrateOnMount, resumeOnTrackChange, unstyled, asChild, className, style, dir: dirProp, prevRestartThresholdSeconds, onTrackChange, onPlay, onPause, onQueueEnd, onError, onVolumeChange, onPlaybackRateChange, onSeek, }: GingerProviderProps): import("react/jsx-runtime").JSX.Element;
3
3
  //# sourceMappingURL=GingerProvider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"GingerProvider.d.ts","sourceRoot":"","sources":["../../src/context/GingerProvider.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAEV,mBAAmB,EAKpB,MAAM,UAAU,CAAC;AAuBlB,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,aAAkB,EAClB,YAAgB,EAChB,mBAA0B,EAC1B,cAAsB,EACtB,iBAAyB,EACzB,mBAAgC,EAChC,aAAoB,EACpB,aAAiB,EACjB,YAAoB,EACpB,mBAAuB,EACvB,eAAe,EACf,MAAM,EACN,YAAoB,EACpB,UAAU,EACV,aAAa,EACb,WAAW,EACX,cAAsB,EACtB,mBAA2B,EAC3B,QAAgB,EAChB,OAAe,EACf,SAAS,EACT,KAAK,EACL,aAAa,EACb,MAAM,EACN,OAAO,EACP,UAAU,EACV,OAAO,GACR,EAAE,mBAAmB,2CAqiBrB"}
1
+ {"version":3,"file":"GingerProvider.d.ts","sourceRoot":"","sources":["../../src/context/GingerProvider.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAEV,mBAAmB,EAKpB,MAAM,UAAU,CAAC;AAuBlB,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,aAAkB,EAClB,YAAgB,EAChB,mBAA0B,EAC1B,cAAsB,EACtB,iBAAyB,EACzB,mBAAgC,EAChC,aAAoB,EACpB,aAAiB,EACjB,YAAoB,EACpB,mBAAuB,EACvB,eAAe,EACf,MAAM,EACN,YAAoB,EACpB,UAAU,EACV,aAAa,EACb,WAAW,EACX,cAAsB,EACtB,mBAA2B,EAC3B,QAAgB,EAChB,OAAe,EACf,SAAS,EACT,KAAK,EACL,GAAG,EAAE,OAAO,EACZ,2BAA+B,EAC/B,aAAa,EACb,MAAM,EACN,OAAO,EACP,UAAU,EACV,OAAO,EACP,cAAc,EACd,oBAAoB,EACpB,MAAM,GACP,EAAE,mBAAmB,2CAsmBrB"}
@@ -1 +1 @@
1
- {"version":3,"file":"playbackReducer.d.ts","sourceRoot":"","sources":["../../src/core/playbackReducer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAY7E,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAG7C;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAGnD;AAkBD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IACzC,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,WAAW,CAAC,cAAc,CAAC,CAAC;IAC3C,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,GAAG,WAAW,CA0Bd;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,GAAG,WAAW,CA2OnF"}
1
+ {"version":3,"file":"playbackReducer.d.ts","sourceRoot":"","sources":["../../src/core/playbackReducer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAY7E,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAG7C;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAGnD;AAkBD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IACzC,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,WAAW,CAAC,cAAc,CAAC,CAAC;IAC3C,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,GAAG,WAAW,CA0Bd;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,GAAG,WAAW,CAiQnF"}
@@ -0,0 +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;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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"}
@@ -0,0 +1,3 @@
1
+ export { useGingerEqualizer } from './useGingerEqualizer';
2
+ export type { EqualizerBand, UseGingerEqualizerOptions, UseGingerEqualizerResult, } from './useGingerEqualizer';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/equalizer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,YAAY,EACV,aAAa,EACb,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,51 @@
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";
3
+ const D = [
4
+ { frequency: 60 },
5
+ { frequency: 250 },
6
+ { frequency: 1e3 },
7
+ { frequency: 4e3 },
8
+ { frequency: 16e3 }
9
+ ];
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([]);
12
+ S(() => {
13
+ const e = u.current;
14
+ if (!(!e || typeof window > "u")) {
15
+ if (!l) {
16
+ f(e, []), s.current = [];
17
+ return;
18
+ }
19
+ try {
20
+ const t = z(e, {
21
+ fftSize: 32,
22
+ smoothingTimeConstant: 0,
23
+ minDecibels: -100,
24
+ maxDecibels: 0
25
+ }), { context: n, id: o } = t, c = i.map((r) => {
26
+ const a = n.createBiquadFilter();
27
+ 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
+ s.current = c, f(e, c), C(e, o), m(null);
30
+ } catch (t) {
31
+ const n = t instanceof Error ? t.message : "Failed to create equalizer";
32
+ m(n), s.current = [];
33
+ }
34
+ return () => {
35
+ const t = u.current;
36
+ t && f(t, []), s.current = [];
37
+ };
38
+ }
39
+ }, [l, i, u, h.currentIndex]);
40
+ const B = q((e, t) => {
41
+ 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) => {
44
+ d(e);
45
+ }, []);
46
+ return { setBandGain: B, setBands: E, bands: i, error: v };
47
+ }
48
+ export {
49
+ L as useGingerEqualizer
50
+ };
51
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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;"}
@@ -0,0 +1,41 @@
1
+ export type EqualizerBand = {
2
+ /** Center frequency in Hz (e.g. 60, 250, 1000, 4000, 16000). */
3
+ frequency: number;
4
+ /** Gain in dB. Positive = boost, negative = cut. Typical range: -12 to +12. Default: 0. */
5
+ gain?: number;
6
+ /** Q factor (bandwidth). Higher = narrower. Default: 1. */
7
+ q?: number;
8
+ /** BiquadFilterNode type. Default: "peaking". */
9
+ type?: BiquadFilterType;
10
+ };
11
+ export type UseGingerEqualizerOptions = {
12
+ /** When false, EQ nodes are detached and the audio path is bypassed. Default: true. */
13
+ enabled?: boolean;
14
+ /** Band definitions. Each band maps to one BiquadFilterNode. */
15
+ bands?: EqualizerBand[];
16
+ };
17
+ export type UseGingerEqualizerResult = {
18
+ /** Update a single band's gain in dB without rebuilding the filter chain. */
19
+ setBandGain: (bandIndex: number, gainDb: number) => void;
20
+ /** Replace all bands (rebuilds the filter chain). */
21
+ setBands: (bands: EqualizerBand[]) => void;
22
+ /** Current band definitions (reflects the latest `setBands` call). */
23
+ bands: EqualizerBand[];
24
+ /** Error string if Web Audio is unavailable or filter creation failed. */
25
+ error: string | null;
26
+ };
27
+ /**
28
+ * Inserts a parametric EQ into the Web Audio graph for the active Ginger media element.
29
+ *
30
+ * Each band is a `BiquadFilterNode` connected in series between the audio source and the
31
+ * speakers. If `useGingerLiveAnalyzer` is also active, the EQ is inserted before the analyser
32
+ * — both share the same `AudioContext` (the browser allows only one `MediaElementAudioSourceNode`
33
+ * per element).
34
+ *
35
+ * Available as a subpath export:
36
+ * ```ts
37
+ * import { useGingerEqualizer } from "@lucaismyname/ginger/equalizer";
38
+ * ```
39
+ */
40
+ export declare function useGingerEqualizer(options?: UseGingerEqualizerOptions): UseGingerEqualizerResult;
41
+ //# sourceMappingURL=useGingerEqualizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useGingerEqualizer.d.ts","sourceRoot":"","sources":["../../src/equalizer/useGingerEqualizer.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,aAAa,GAAG;IAC1B,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,2FAA2F;IAC3F,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,iDAAiD;IACjD,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,uFAAuF;IACvF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gEAAgE;IAChE,KAAK,CAAC,EAAE,aAAa,EAAE,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,6EAA6E;IAC7E,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,qDAAqD;IACrD,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,IAAI,CAAC;IAC3C,sEAAsE;IACtE,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,0EAA0E;IAC1E,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AAUF;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,yBAA8B,GACtC,wBAAwB,CA6E1B"}