@lucaismyname/ginger 0.0.30 → 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.
- package/README.md +140 -8
- package/dist/analyzer/liveAudioGraph.d.ts +14 -1
- package/dist/analyzer/liveAudioGraph.d.ts.map +1 -1
- package/dist/analyzer/useGingerLiveAnalyzer.d.ts +6 -0
- package/dist/analyzer/useGingerLiveAnalyzer.d.ts.map +1 -1
- package/dist/client.cjs +1 -1
- package/dist/client.js +36 -29
- package/dist/client.js.map +1 -1
- package/dist/context/GingerProvider.d.ts +1 -1
- package/dist/context/GingerProvider.d.ts.map +1 -1
- package/dist/core/playbackReducer.d.ts.map +1 -1
- package/dist/equalizer/index.cjs +2 -0
- package/dist/equalizer/index.cjs.map +1 -0
- package/dist/equalizer/index.d.ts +3 -0
- package/dist/equalizer/index.d.ts.map +1 -0
- package/dist/equalizer/index.js +51 -0
- package/dist/equalizer/index.js.map +1 -0
- package/dist/equalizer/useGingerEqualizer.d.ts +41 -0
- package/dist/equalizer/useGingerEqualizer.d.ts.map +1 -0
- package/dist/ginger-L2ZFgzH4.js +2223 -0
- package/dist/ginger-L2ZFgzH4.js.map +1 -0
- package/dist/ginger-NEcOSSJD.cjs +2 -0
- package/dist/ginger-NEcOSSJD.cjs.map +1 -0
- package/dist/hooks/useGingerChapterProgress.d.ts +14 -0
- package/dist/hooks/useGingerChapterProgress.d.ts.map +1 -0
- package/dist/hooks/useGingerKeyboardShortcuts.d.ts +6 -0
- package/dist/hooks/useGingerKeyboardShortcuts.d.ts.map +1 -1
- package/dist/hooks/useGingerPlaybackHistory.d.ts +26 -0
- package/dist/hooks/useGingerPlaybackHistory.d.ts.map +1 -0
- package/dist/hooks/useGingerSleepTimer.d.ts.map +1 -1
- package/dist/hooks/useGingerVolumeFade.d.ts +22 -0
- package/dist/hooks/useGingerVolumeFade.d.ts.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +18 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +36 -29
- package/dist/index.js.map +1 -1
- package/dist/liveAudioGraph-CmEsdLgZ.js +150 -0
- package/dist/liveAudioGraph-CmEsdLgZ.js.map +1 -0
- package/dist/liveAudioGraph-D1BXMv_u.cjs +2 -0
- package/dist/liveAudioGraph-D1BXMv_u.cjs.map +1 -0
- package/dist/selectors-BalBCc7X.js +127 -0
- package/dist/selectors-BalBCc7X.js.map +1 -0
- package/dist/selectors-YXnP8Y8g.cjs +2 -0
- package/dist/selectors-YXnP8Y8g.cjs.map +1 -0
- package/dist/store.d.ts +46 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/testing/index.cjs +1 -1
- package/dist/testing/index.js +1 -1
- package/dist/testing/mockWebAudio.d.ts +15 -1
- package/dist/testing/mockWebAudio.d.ts.map +1 -1
- package/dist/types.d.ts +30 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/useGingerChapterProgress-BOqUimE7.cjs +2 -0
- package/dist/useGingerChapterProgress-BOqUimE7.cjs.map +1 -0
- package/dist/useGingerChapterProgress-DLYdGytK.js +321 -0
- package/dist/useGingerChapterProgress-DLYdGytK.js.map +1 -0
- package/package.json +7 -2
- package/dist/ginger-DYlsaFGA.js +0 -2283
- package/dist/ginger-DYlsaFGA.js.map +0 -1
- package/dist/ginger-YDZhJqet.cjs +0 -2
- package/dist/ginger-YDZhJqet.cjs.map +0 -1
- package/dist/useNextTrackPrefetch-BZdly__j.js +0 -265
- package/dist/useNextTrackPrefetch-BZdly__j.js.map +0 -1
- package/dist/useNextTrackPrefetch-pBFiWDyx.cjs +0 -2
- package/dist/useNextTrackPrefetch-pBFiWDyx.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()`**,
|
|
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: "
|
|
868
|
-
previous: "
|
|
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`
|
|
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
|
-
/**
|
|
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
|
|
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;
|
|
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-
|
|
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
|
|
2
|
-
import { a as C, d as
|
|
3
|
-
import {
|
|
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
|
-
|
|
10
|
-
|
|
11
|
+
t as Play,
|
|
12
|
+
n as RepeatGlyph,
|
|
11
13
|
g as ShuffleIcon,
|
|
12
|
-
|
|
14
|
+
o as SkipBack,
|
|
13
15
|
c as SkipForward,
|
|
14
|
-
|
|
16
|
+
l as Volume2,
|
|
15
17
|
p as VolumeX,
|
|
16
18
|
G as Wrapper,
|
|
17
19
|
C as attachLiveAnalyser,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
d as
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;"}
|
|
@@ -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,
|
|
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,
|
|
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 @@
|
|
|
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"}
|