@lucaismyname/ginger 0.0.12 → 0.0.14

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 (36) hide show
  1. package/README.md +76 -0
  2. package/dist/analyzer/liveAudioGraph.d.ts +14 -0
  3. package/dist/analyzer/liveAudioGraph.d.ts.map +1 -0
  4. package/dist/analyzer/useGingerLiveAnalyzer.d.ts +21 -0
  5. package/dist/analyzer/useGingerLiveAnalyzer.d.ts.map +1 -0
  6. package/dist/client.cjs +1 -1
  7. package/dist/client.js +21 -18
  8. package/dist/index.cjs +1 -1
  9. package/dist/index.d.ts +4 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +21 -18
  12. package/dist/internal/fft.d.ts +7 -0
  13. package/dist/internal/fft.d.ts.map +1 -0
  14. package/dist/internal/fft.test.d.ts +2 -0
  15. package/dist/internal/fft.test.d.ts.map +1 -0
  16. package/dist/useSeekDrag-Cc0wpC70.cjs +2 -0
  17. package/dist/useSeekDrag-Cc0wpC70.cjs.map +1 -0
  18. package/dist/useSeekDrag-DkAJvf8H.js +289 -0
  19. package/dist/useSeekDrag-DkAJvf8H.js.map +1 -0
  20. package/dist/waveform/analyzeAudioFile.d.ts +31 -0
  21. package/dist/waveform/analyzeAudioFile.d.ts.map +1 -0
  22. package/dist/waveform/analyzeAudioFile.test.d.ts +2 -0
  23. package/dist/waveform/analyzeAudioFile.test.d.ts.map +1 -0
  24. package/dist/waveform/index.cjs +1 -1
  25. package/dist/waveform/index.cjs.map +1 -1
  26. package/dist/waveform/index.d.ts +4 -0
  27. package/dist/waveform/index.d.ts.map +1 -1
  28. package/dist/waveform/index.js +207 -23
  29. package/dist/waveform/index.js.map +1 -1
  30. package/dist/waveform/useAudioFileAnalysis.d.ts +8 -0
  31. package/dist/waveform/useAudioFileAnalysis.d.ts.map +1 -0
  32. package/package.json +1 -1
  33. package/dist/useSeekDrag-DBzoym0-.cjs +0 -2
  34. package/dist/useSeekDrag-DBzoym0-.cjs.map +0 -1
  35. package/dist/useSeekDrag-jLsYA-uG.js +0 -174
  36. package/dist/useSeekDrag-jLsYA-uG.js.map +0 -1
package/README.md CHANGED
@@ -695,8 +695,84 @@ Example:
695
695
 
696
696
  - **Buffered UI** — **`Ginger.Current.BufferRail`** shows load progress; **`Ginger.Current.TimeRail`** supports **`showBuffered`** to stack a buffered layer behind the played segment.
697
697
 
698
+ - **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).
699
+
698
700
  Recipes below cover queue lifecycle and media edge cases.
699
701
 
702
+ ## Audio analyzers (visualizations)
703
+
704
+ Ginger separates **live** analysis (while the native `<audio>` element plays) from **whole-file** analysis (decode once, build static or seek-independent grids).
705
+
706
+ ### Live: `useGingerLiveAnalyzer`
707
+
708
+ Import from the main package:
709
+
710
+ ```tsx
711
+ import { Ginger, useGingerLiveAnalyzer } from "@lucaismyname/ginger";
712
+
713
+ function Spectrum() {
714
+ const { frequencyData, frequencyBinCount, error, resume, isSuspended } = useGingerLiveAnalyzer({
715
+ fftSize: 2048,
716
+ });
717
+
718
+ if (error) return <p role="alert">{error}</p>;
719
+ if (isSuspended) {
720
+ return (
721
+ <button type="button" onClick={() => void resume()}>
722
+ Enable audio analysis
723
+ </button>
724
+ );
725
+ }
726
+
727
+ return (
728
+ <div>
729
+ {/* e.g. map frequencyData[0..frequencyBinCount) to bar heights */}
730
+ <span>{frequencyBinCount} bins</span>
731
+ </div>
732
+ );
733
+ }
734
+
735
+ // Mount <Ginger.Player crossOrigin="anonymous" /> when fileUrl is cross-origin so Web Audio can use the element.
736
+ ```
737
+
738
+ Important:
739
+
740
+ - **CORS** — For cross-origin `fileUrl` values, set **`crossOrigin`** on **`Ginger.Player`** (for example `"anonymous"`) so the media element is usable with **`AudioContext`**.
741
+ - **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.
742
+ - **Autoplay** — The **`AudioContext`** may start **`suspended`** until a user gesture; call **`resume()`** or start playback after interaction.
743
+
744
+ ### Whole file: `@lucaismyname/ginger/waveform`
745
+
746
+ Use **`useAudioFileAnalysis`** (React hook) or **`analyzeAudioFile`** / **`analyzeAudioBuffer`** (imperative) for amplitude grids and optional spectrogram rows without playing the track.
747
+
748
+ ```tsx
749
+ import { useAudioFileAnalysis } from "@lucaismyname/ginger/waveform";
750
+
751
+ function FileViz({ url }: { url: string }) {
752
+ const { data, isLoading, error } = useAudioFileAnalysis(url, {
753
+ timeSlices: 128,
754
+ samplesPerSlice: 8,
755
+ spectrogram: true,
756
+ fftSize: 1024,
757
+ frequencyBins: 256,
758
+ });
759
+
760
+ if (isLoading) return <p>Loading analysis…</p>;
761
+ if (error) return <p role="alert">{error}</p>;
762
+ if (!data) return null;
763
+
764
+ return (
765
+ <div>
766
+ <p>Duration: {data.duration}s</p>
767
+ {/* data.amplitudeGrid: number[][] */}
768
+ {/* data.spectrogram?: number[][] normalized to [0, 1] */}
769
+ </div>
770
+ );
771
+ }
772
+ ```
773
+
774
+ **`useAudioPeaks`** (same subpath) remains a lightweight helper: a single row of decoded amplitude peaks. Prefer **`useAudioFileAnalysis`** when you need a 2D amplitude grid or spectrogram.
775
+
700
776
  ## Recipes
701
777
 
702
778
  ### Updating the queue after mount
@@ -0,0 +1,14 @@
1
+ /** One MediaElementAudioSourceNode per HTMLAudioElement; multiple AnalyserNodes may tap the source. */
2
+ export type LiveAnalyserOptions = {
3
+ fftSize: number;
4
+ smoothingTimeConstant: number;
5
+ minDecibels: number;
6
+ maxDecibels: number;
7
+ };
8
+ export declare function attachLiveAnalyser(element: HTMLAudioElement, options: LiveAnalyserOptions): {
9
+ id: number;
10
+ context: AudioContext;
11
+ analyser: AnalyserNode;
12
+ };
13
+ export declare function detachLiveAnalyser(element: HTMLAudioElement, id: number): void;
14
+ //# sourceMappingURL=liveAudioGraph.d.ts.map
@@ -0,0 +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,CAgC/D;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CA6B9E"}
@@ -0,0 +1,21 @@
1
+ export type UseGingerLiveAnalyzerOptions = {
2
+ /** When false, the analyser is detached and no frames are read. Default true. */
3
+ enabled?: boolean;
4
+ fftSize?: number;
5
+ smoothingTimeConstant?: number;
6
+ minDecibels?: number;
7
+ maxDecibels?: number;
8
+ };
9
+ export type UseGingerLiveAnalyzerResult = {
10
+ /** Byte frequency data (0–255); length equals `frequencyBinCount`. Updated each animation frame while enabled. */
11
+ frequencyData: Uint8Array;
12
+ /** Byte time-domain data (0–255); length equals `fftSize`. */
13
+ timeDomainData: Uint8Array;
14
+ frequencyBinCount: number;
15
+ sampleRate: number;
16
+ isSuspended: boolean;
17
+ error: string | null;
18
+ resume: () => Promise<void>;
19
+ };
20
+ export declare function useGingerLiveAnalyzer(options?: UseGingerLiveAnalyzerOptions): UseGingerLiveAnalyzerResult;
21
+ //# sourceMappingURL=useGingerLiveAnalyzer.d.ts.map
@@ -0,0 +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,CAAC,OAAO,GAAE,4BAAiC,GAAG,2BAA2B,CAkK7G"}
package/dist/client.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./ginger-DYoHDn8T.cjs"),r=require("./useSeekDrag-DBzoym0-.cjs"),i=require("./GingerSplitContexts-Bze1Bqe2.cjs");exports.Ginger=e.Ginger;exports.clampPlaybackRate=e.clampPlaybackRate;exports.clampVolume=e.clampVolume;exports.defaultGingerLocale=e.defaultGingerLocale;exports.derivePlaybackUiState=e.derivePlaybackUiState;exports.useGingerLocale=e.useGingerLocale;exports.usePlayPauseBinding=e.usePlayPauseBinding;exports.useSeekBarBinding=e.useSeekBarBinding;exports.useVolumeSlider=e.useVolumeSlider;exports.parseLrc=r.parseLrc;exports.useGinger=r.useGinger;exports.useGingerChapters=r.useGingerChapters;exports.useGingerDebugLog=r.useGingerDebugLog;exports.useGingerKeyboardShortcuts=r.useGingerKeyboardShortcuts;exports.useGingerLyricsSync=r.useGingerLyricsSync;exports.useGingerSleepTimer=r.useGingerSleepTimer;exports.useSeekDrag=r.useSeekDrag;exports.gingerStateFromContextValues=i.gingerStateFromContextValues;exports.gingerStateFromContexts=i.gingerStateFromContexts;exports.useGingerMedia=i.useGingerMedia;exports.useGingerPlayback=i.useGingerPlayback;exports.useGingerState=i.useGingerState;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("./ginger-DYoHDn8T.cjs"),e=require("./useSeekDrag-Cc0wpC70.cjs"),a=require("./GingerSplitContexts-Bze1Bqe2.cjs");exports.Ginger=r.Ginger;exports.clampPlaybackRate=r.clampPlaybackRate;exports.clampVolume=r.clampVolume;exports.defaultGingerLocale=r.defaultGingerLocale;exports.derivePlaybackUiState=r.derivePlaybackUiState;exports.useGingerLocale=r.useGingerLocale;exports.usePlayPauseBinding=r.usePlayPauseBinding;exports.useSeekBarBinding=r.useSeekBarBinding;exports.useVolumeSlider=r.useVolumeSlider;exports.attachLiveAnalyser=e.attachLiveAnalyser;exports.detachLiveAnalyser=e.detachLiveAnalyser;exports.parseLrc=e.parseLrc;exports.useGinger=e.useGinger;exports.useGingerChapters=e.useGingerChapters;exports.useGingerDebugLog=e.useGingerDebugLog;exports.useGingerKeyboardShortcuts=e.useGingerKeyboardShortcuts;exports.useGingerLiveAnalyzer=e.useGingerLiveAnalyzer;exports.useGingerLyricsSync=e.useGingerLyricsSync;exports.useGingerSleepTimer=e.useGingerSleepTimer;exports.useSeekDrag=e.useSeekDrag;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,28 +1,31 @@
1
- import { G as s, c as r, a as i, d as u, b as g, u as n, e as t, f as o, g as c } from "./ginger-Dntdd6zH.js";
2
- import { p as G, u as m, a as S, b as d, c as p, d as b, e as y, f } from "./useSeekDrag-jLsYA-uG.js";
3
- import { g as x, a as L, u as P, b as B, c as C } from "./GingerSplitContexts-4YZ-OJ9V.js";
1
+ import { G as s, c as r, a as i, d as g, b as n, u, e as t, f as l, g as o } from "./ginger-Dntdd6zH.js";
2
+ import { a as G, d, p as m, u as y, b as S, c as p, e as b, f as L, g as f, h, i as k } from "./useSeekDrag-DkAJvf8H.js";
3
+ import { g as P, a as v, u as A, b as B, c as C } from "./GingerSplitContexts-4YZ-OJ9V.js";
4
4
  export {
5
5
  s as Ginger,
6
+ G as attachLiveAnalyser,
6
7
  r as clampPlaybackRate,
7
8
  i as clampVolume,
8
- u as defaultGingerLocale,
9
- g as derivePlaybackUiState,
10
- x as gingerStateFromContextValues,
11
- L as gingerStateFromContexts,
12
- G as parseLrc,
13
- m as useGinger,
9
+ g as defaultGingerLocale,
10
+ n as derivePlaybackUiState,
11
+ d as detachLiveAnalyser,
12
+ P as gingerStateFromContextValues,
13
+ v as gingerStateFromContexts,
14
+ m as parseLrc,
15
+ y as useGinger,
14
16
  S as useGingerChapters,
15
- d as useGingerDebugLog,
16
- p as useGingerKeyboardShortcuts,
17
- n as useGingerLocale,
18
- b as useGingerLyricsSync,
19
- P as useGingerMedia,
17
+ p as useGingerDebugLog,
18
+ b as useGingerKeyboardShortcuts,
19
+ L as useGingerLiveAnalyzer,
20
+ u as useGingerLocale,
21
+ f as useGingerLyricsSync,
22
+ A as useGingerMedia,
20
23
  B as useGingerPlayback,
21
- y as useGingerSleepTimer,
24
+ h as useGingerSleepTimer,
22
25
  C as useGingerState,
23
26
  t as usePlayPauseBinding,
24
- o as useSeekBarBinding,
25
- f as useSeekDrag,
26
- c as useVolumeSlider
27
+ l as useSeekBarBinding,
28
+ k as useSeekDrag,
29
+ o as useVolumeSlider
27
30
  };
28
31
  //# sourceMappingURL=client.js.map
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./ginger-DYoHDn8T.cjs"),r=require("./useSeekDrag-DBzoym0-.cjs"),i=require("./GingerSplitContexts-Bze1Bqe2.cjs");exports.Ginger=e.Ginger;exports.clampPlaybackRate=e.clampPlaybackRate;exports.clampVolume=e.clampVolume;exports.defaultGingerLocale=e.defaultGingerLocale;exports.derivePlaybackUiState=e.derivePlaybackUiState;exports.useGingerLocale=e.useGingerLocale;exports.usePlayPauseBinding=e.usePlayPauseBinding;exports.useSeekBarBinding=e.useSeekBarBinding;exports.useVolumeSlider=e.useVolumeSlider;exports.parseLrc=r.parseLrc;exports.useGinger=r.useGinger;exports.useGingerChapters=r.useGingerChapters;exports.useGingerDebugLog=r.useGingerDebugLog;exports.useGingerKeyboardShortcuts=r.useGingerKeyboardShortcuts;exports.useGingerLyricsSync=r.useGingerLyricsSync;exports.useGingerSleepTimer=r.useGingerSleepTimer;exports.useSeekDrag=r.useSeekDrag;exports.gingerStateFromContextValues=i.gingerStateFromContextValues;exports.gingerStateFromContexts=i.gingerStateFromContexts;exports.useGingerMedia=i.useGingerMedia;exports.useGingerPlayback=i.useGingerPlayback;exports.useGingerState=i.useGingerState;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("./ginger-DYoHDn8T.cjs"),e=require("./useSeekDrag-Cc0wpC70.cjs"),a=require("./GingerSplitContexts-Bze1Bqe2.cjs");exports.Ginger=r.Ginger;exports.clampPlaybackRate=r.clampPlaybackRate;exports.clampVolume=r.clampVolume;exports.defaultGingerLocale=r.defaultGingerLocale;exports.derivePlaybackUiState=r.derivePlaybackUiState;exports.useGingerLocale=r.useGingerLocale;exports.usePlayPauseBinding=r.usePlayPauseBinding;exports.useSeekBarBinding=r.useSeekBarBinding;exports.useVolumeSlider=r.useVolumeSlider;exports.attachLiveAnalyser=e.attachLiveAnalyser;exports.detachLiveAnalyser=e.detachLiveAnalyser;exports.parseLrc=e.parseLrc;exports.useGinger=e.useGinger;exports.useGingerChapters=e.useGingerChapters;exports.useGingerDebugLog=e.useGingerDebugLog;exports.useGingerKeyboardShortcuts=e.useGingerKeyboardShortcuts;exports.useGingerLiveAnalyzer=e.useGingerLiveAnalyzer;exports.useGingerLyricsSync=e.useGingerLyricsSync;exports.useGingerSleepTimer=e.useGingerSleepTimer;exports.useSeekDrag=e.useSeekDrag;exports.gingerStateFromContextValues=a.gingerStateFromContextValues;exports.gingerStateFromContexts=a.gingerStateFromContexts;exports.useGingerMedia=a.useGingerMedia;exports.useGingerPlayback=a.useGingerPlayback;exports.useGingerState=a.useGingerState;
2
2
  //# sourceMappingURL=index.cjs.map
package/dist/index.d.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  export { Ginger } from './ginger';
2
2
  export { useGinger } from './hooks/useGinger';
3
+ export { useGingerLiveAnalyzer } from './analyzer/useGingerLiveAnalyzer';
4
+ export type { UseGingerLiveAnalyzerOptions, UseGingerLiveAnalyzerResult } from './analyzer/useGingerLiveAnalyzer';
5
+ export { attachLiveAnalyser, detachLiveAnalyser } from './analyzer/liveAudioGraph';
6
+ export type { LiveAnalyserOptions } from './analyzer/liveAudioGraph';
3
7
  export type { DisplayBaseProps, GingerAction, GingerInitPayload, GingerLocaleMessages, GingerMediaSlice, GingerPersistenceAdapter, GingerPlaybackSlice, GingerProviderProps, GingerState, PlaybackMode, PlaybackUiState, PlaylistMeta, RepeatMode, Track, } from './types';
4
8
  export { clampPlaybackRate, clampVolume } from './core/playbackReducer';
5
9
  export { derivePlaybackUiState } from './internal/selectors';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,YAAY,EACV,gBAAgB,EAChB,YAAY,EACZ,iBAAiB,EACjB,oBAAoB,EACpB,gBAAgB,EAChB,wBAAwB,EACxB,mBAAmB,EACnB,mBAAmB,EACnB,WAAW,EACX,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,UAAU,EACV,KAAK,GACN,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EACL,uBAAuB,EACvB,4BAA4B,EAC5B,cAAc,EACd,iBAAiB,EACjB,cAAc,GACf,MAAM,+BAA+B,CAAC;AACvC,YAAY,EACV,kBAAkB,EAClB,uBAAuB,EACvB,qBAAqB,EACrB,0BAA0B,GAC3B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrF,YAAY,EACV,gBAAgB,EAChB,cAAc,EACd,aAAa,GACd,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AACrG,YAAY,EAAE,8BAA8B,EAAE,MAAM,oCAAoC,CAAC;AACzF,OAAO,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAC;AAChF,YAAY,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACnF,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,YAAY,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,YAAY,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AAC3E,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,YAAY,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,YAAY,EACV,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,sCAAsC,CAAC;AAC9C,YAAY,EACV,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,+BAA+B,CAAC;AACvC,YAAY,EAAE,YAAY,IAAI,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACxF,YAAY,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjE,YAAY,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC/D,YAAY,EACV,eAAe,EACf,aAAa,EACb,aAAa,EACb,aAAa,GACd,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,gCAAgC,CAAC;AACxC,YAAY,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACzE,YAAY,EACV,SAAS,EACT,SAAS,EACT,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,WAAW,GACZ,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,28 +1,31 @@
1
- import { G as s, c as r, a as i, d as u, b as g, u as n, e as t, f as o, g as c } from "./ginger-Dntdd6zH.js";
2
- import { p as G, u as m, a as S, b as d, c as p, d as b, e as y, f } from "./useSeekDrag-jLsYA-uG.js";
3
- import { g as x, a as L, u as P, b as B, c as C } from "./GingerSplitContexts-4YZ-OJ9V.js";
1
+ import { G as s, c as r, a as i, d as g, b as n, u, e as t, f as l, g as o } from "./ginger-Dntdd6zH.js";
2
+ import { a as G, d, p as m, u as y, b as S, c as p, e as b, f as L, g as f, h, i as k } from "./useSeekDrag-DkAJvf8H.js";
3
+ import { g as P, a as v, u as A, b as B, c as C } from "./GingerSplitContexts-4YZ-OJ9V.js";
4
4
  export {
5
5
  s as Ginger,
6
+ G as attachLiveAnalyser,
6
7
  r as clampPlaybackRate,
7
8
  i as clampVolume,
8
- u as defaultGingerLocale,
9
- g as derivePlaybackUiState,
10
- x as gingerStateFromContextValues,
11
- L as gingerStateFromContexts,
12
- G as parseLrc,
13
- m as useGinger,
9
+ g as defaultGingerLocale,
10
+ n as derivePlaybackUiState,
11
+ d as detachLiveAnalyser,
12
+ P as gingerStateFromContextValues,
13
+ v as gingerStateFromContexts,
14
+ m as parseLrc,
15
+ y as useGinger,
14
16
  S as useGingerChapters,
15
- d as useGingerDebugLog,
16
- p as useGingerKeyboardShortcuts,
17
- n as useGingerLocale,
18
- b as useGingerLyricsSync,
19
- P as useGingerMedia,
17
+ p as useGingerDebugLog,
18
+ b as useGingerKeyboardShortcuts,
19
+ L as useGingerLiveAnalyzer,
20
+ u as useGingerLocale,
21
+ f as useGingerLyricsSync,
22
+ A as useGingerMedia,
20
23
  B as useGingerPlayback,
21
- y as useGingerSleepTimer,
24
+ h as useGingerSleepTimer,
22
25
  C as useGingerState,
23
26
  t as usePlayPauseBinding,
24
- o as useSeekBarBinding,
25
- f as useSeekDrag,
26
- c as useVolumeSlider
27
+ l as useSeekBarBinding,
28
+ k as useSeekDrag,
29
+ o as useVolumeSlider
27
30
  };
28
31
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ /** In-place radix-2 Cooley–Tukey FFT; `length` must be a power of 2 and >= 2. */
2
+ export declare function fftInPlace(re: Float64Array, im: Float64Array): void;
3
+ /** Magnitude spectrum for real input: length `n` (power of 2). Returns `n/2` magnitudes for bins 0..n/2-1. */
4
+ export declare function realFftMagnitudes(samples: Float64Array): Float64Array;
5
+ export declare function hanningWindow(length: number): Float64Array;
6
+ export declare function clampFftSize(n: number): number;
7
+ //# sourceMappingURL=fft.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fft.d.ts","sourceRoot":"","sources":["../../src/internal/fft.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,wBAAgB,UAAU,CAAC,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,YAAY,GAAG,IAAI,CAgDnE;AAED,8GAA8G;AAC9G,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,YAAY,GAAG,YAAY,CAcrE;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAW1D;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAG9C"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fft.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fft.test.d.ts","sourceRoot":"","sources":["../../src/internal/fft.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ "use strict";const a=require("react"),d=require("./GingerSplitContexts-Bze1Bqe2.cjs"),R=require("./ginger-DYoHDn8T.cjs");function V(){const n=d.useGingerPlayback(),t=d.useGingerMedia();return a.useMemo(()=>{const e=d.gingerStateFromContextValues(n,t);return{state:e,currentTrack:R.getCurrentTrack(e),playbackUi:R.derivePlaybackUiState(e),duration:R.effectiveDuration(e),remaining:R.effectiveRemaining(e),progress:R.progressFraction(e),artworkUrl:R.resolvedArtwork(e),albumLine:R.resolvedAlbumLine(e),play:n.play,pause:n.pause,togglePlayPause:n.togglePlayPause,seek:t.seek,setVolume:t.setVolume,setMuted:t.setMuted,toggleMute:t.toggleMute,setPlaybackRate:t.setPlaybackRate,next:n.next,prev:n.prev,setRepeatMode:n.setRepeatMode,cycleRepeat:n.cycleRepeat,toggleShuffle:n.toggleShuffle,setQueue:n.setQueue,insertTrackAt:n.insertTrackAt,removeTrackAt:n.removeTrackAt,moveTrack:n.moveTrack,enqueueNext:n.enqueueNext,playTrackAt:n.playTrackAt,selectTrackAt:n.selectTrackAt,setPlaylistMeta:n.setPlaylistMeta,init:n.init,audioRef:t.audioRef,dispatch:n.dispatch}},[n,t])}const q=new WeakMap;function j(n){const t=2**Math.round(Math.log2(n));return Math.min(32768,Math.max(32,t))}function X(n,t){let e=q.get(n);if(!e){const i=window.AudioContext??window.webkitAudioContext;if(!i)throw new Error("Web Audio API is not available");const f=new i,k=f.createMediaElementSource(n);e={context:f,source:k,consumers:new Map,nextId:0},q.set(n,e)}const{context:s,source:o}=e,c=s.createAnalyser();c.fftSize=j(t.fftSize),c.smoothingTimeConstant=t.smoothingTimeConstant,c.minDecibels=t.minDecibels,c.maxDecibels=t.maxDecibels,o.connect(c);const r=e.consumers.size===0;r&&c.connect(s.destination);const u=e.nextId;return e.nextId+=1,e.consumers.set(u,{analyser:c,isPlaybackSink:r}),{id:u,context:s,analyser:c}}function N(n,t){const e=q.get(n);if(!e)return;const s=e.consumers.get(t);if(!s)return;const{analyser:o,isPlaybackSink:c}=s;if(o.disconnect(),e.consumers.delete(t),e.consumers.size===0){try{e.source.disconnect()}catch{}e.context.close(),q.delete(n);return}if(c){const r=e.consumers.values().next().value;r&&(r.analyser.connect(e.context.destination),r.isPlaybackSink=!0)}}const D=new Uint8Array(0),I=new Uint8Array(0);function J(n={}){const{enabled:t=!0,fftSize:e=2048,smoothingTimeConstant:s=.8,minDecibels:o=-100,maxDecibels:c=-30}=n,{audioRef:r,state:u}=V(),i=a.useMemo(()=>({fftSize:e,smoothingTimeConstant:s,minDecibels:o,maxDecibels:c}),[e,s,o,c]),[f,k]=a.useState(0),[g,l]=a.useState(null),[S,v]=a.useState(!1),[b,T]=a.useState({frequencyBinCount:0,sampleRate:0}),p=a.useRef(D),h=a.useRef(I),Q=a.useCallback(async()=>{const x=w.current;x&&x.state==="suspended"&&await x.resume()},[]),w=a.useRef(null),P=a.useRef(null);return a.useLayoutEffect(()=>{if(!t||typeof window>"u")return;let x=!1,C=null,E=null,G=0;const F=()=>{const m=w.current;m&&v(m.state==="suspended")},z=()=>{if(x)return;const m=P.current,A=p.current,y=h.current;m&&A.length>0&&y.length>0&&(m.getByteFrequencyData(A),m.getByteTimeDomainData(y),k(L=>L+1)),G=requestAnimationFrame(z)},B=()=>{const m=r.current;if(!m||x)return"no-element";try{const{id:A,context:y,analyser:L}=X(m,i);C=A,E=m,w.current=y,P.current=L,v(y.state==="suspended"),l(null),y.addEventListener("statechange",F);const M=L.frequencyBinCount,W=L.fftSize;return p.current=new Uint8Array(M),h.current=new Uint8Array(W),T({frequencyBinCount:M,sampleRate:y.sampleRate}),G=requestAnimationFrame(z),"ok"}catch(A){const y=A instanceof Error?A.message:"Failed to attach live analyser";return l(y),w.current=null,P.current=null,p.current=D,h.current=I,T({frequencyBinCount:0,sampleRate:0}),"error"}},U=B();if(U!=="ok"){let m=0;const A=120;let y=0;const L=()=>{if(x)return;const M=B();M==="ok"||M==="error"||(y+=1,!(y>=A)&&(m=requestAnimationFrame(L)))};return U==="no-element"&&(m=requestAnimationFrame(L)),()=>{var M;x=!0,cancelAnimationFrame(m),cancelAnimationFrame(G),C!=null&&E&&N(E,C),(M=w.current)==null||M.removeEventListener("statechange",F),w.current=null,P.current=null,p.current=D,h.current=I}}return()=>{var m;x=!0,cancelAnimationFrame(G),C!=null&&E&&N(E,C),(m=w.current)==null||m.removeEventListener("statechange",F),w.current=null,P.current=null,p.current=D,h.current=I,T({frequencyBinCount:0,sampleRate:0})}},[t,r,i,u.currentIndex]),{frequencyData:p.current,timeDomainData:h.current,frequencyBinCount:b.frequencyBinCount,sampleRate:b.sampleRate,isSuspended:S,error:g,resume:Q}}function O(n=!0,t={}){const{togglePlayPause:e,next:s,prev:o}=d.useGingerPlayback(),{toggleMute:c}=d.useGingerMedia(),r=t.mute;a.useEffect(()=>{if(!n||typeof window>"u")return;const u=(t.playPause??" ").toLowerCase(),i=(t.next??"ArrowRight").toLowerCase(),f=(t.previous??"ArrowLeft").toLowerCase(),k=r==null?void 0:r.toLowerCase(),g=l=>{const S=l.target;if(S&&(["INPUT","TEXTAREA","SELECT"].includes(S.tagName)||S.isContentEditable))return;const v=l.key.toLowerCase();v===u?(l.preventDefault(),e()):v===i?(l.preventDefault(),s()):v===f?(l.preventDefault(),o()):k&&v===k&&(l.preventDefault(),c())};return window.addEventListener("keydown",g),()=>window.removeEventListener("keydown",g)},[t.next,t.playPause,t.previous,n,r,s,o,c,e])}function Y(){const{tracks:n,currentIndex:t}=d.useGingerPlayback(),{currentTime:e,seek:s}=d.useGingerMedia(),o=a.useMemo(()=>{var u;return[...((u=n[t])==null?void 0:u.chapters)??[]].filter(i=>i&&Number.isFinite(i.startSeconds)&&i.startSeconds>=0).sort((i,f)=>i.startSeconds-f.startSeconds)},[t,n]),c=a.useMemo(()=>{if(o.length===0)return-1;for(let r=o.length-1;r>=0;r-=1)if(e>=o[r].startSeconds)return r;return-1},[e,o]);return{list:o,activeIndex:c,active:c>=0?o[c]??null:null,seekTo:r=>{const u=o[r];u&&s(u.startSeconds)}}}const K=/\[(\d{1,2}):(\d{2})(?:\.(\d{1,3}))?\]/g;function H(n){const t=[];for(const e of n.split(/\r?\n/)){const s=[...e.matchAll(K)];if(s.length===0)continue;const o=e.replace(K,"").trim();for(const c of s){const r=Number(c[1]??0),u=Number(c[2]??0),i=Number((c[3]??"0").padEnd(3,"0")),f=r*60+u+i/1e3;Number.isFinite(f)&&f>=0&&t.push({time:f,text:o})}}return t.sort((e,s)=>e.time-s.time)}function Z(){const{tracks:n,currentIndex:t}=d.useGingerPlayback(),{currentTime:e}=d.useGingerMedia(),s=n[t],o=a.useMemo(()=>s?Array.isArray(s.lyricsTimed)&&s.lyricsTimed.length>0?[...s.lyricsTimed].filter(r=>Number.isFinite(r.time)&&r.time>=0).sort((r,u)=>r.time-u.time):typeof s.lyrics=="string"?H(s.lyrics):[]:[],[s]),c=a.useMemo(()=>{for(let r=o.length-1;r>=0;r-=1)if(e>=o[r].time)return r;return-1},[e,o]);return{lines:o,activeIndex:c,activeLine:c>=0?o[c]??null:null}}function _(n){const{durationMs:t,stopAfterTracks:e,respectPause:s=!0,enabled:o=!0,onFire:c}=n,{currentIndex:r,pause:u,isPaused:i}=d.useGingerPlayback(),f=a.useRef(e??0),k=a.useRef(r);a.useEffect(()=>{f.current=e??0},[e]),a.useEffect(()=>{if(!o||!t||t<=0||s&&i)return;const g=setTimeout(()=>{u(),c==null||c()},t);return()=>clearTimeout(g)},[t,o,i,c,u,s]),a.useEffect(()=>{if(!o||!e||e<=0)return;const g=k.current;k.current=r,r!==g&&(f.current-=1,f.current<=0&&(u(),c==null||c()))},[r,o,c,u,e])}function $(n=!1){const t=d.useGingerState(),e=a.useRef(t);a.useEffect(()=>{if(!n||typeof console>"u")return;const s=e.current;s!==t&&console.debug("[ginger]",{from:{currentIndex:s.currentIndex,isPaused:s.isPaused,currentTime:s.currentTime,repeatMode:s.repeatMode},to:{currentIndex:t.currentIndex,isPaused:t.isPaused,currentTime:t.currentTime,repeatMode:t.repeatMode}}),e.current=t},[n,t])}function ee(n){return Math.max(0,Math.min(1,n))}function te(n){const t=d.useGingerMedia(),e=d.useGingerPlayback(),{seek:s}=t,[o,c]=a.useState(0),[r,u]=a.useState(!1),i=R.progressFraction(d.gingerStateFromContextValues(e,t)),f=r?o:i,k=a.useCallback(g=>{if(!(n>0))return;const l=g.currentTarget,S=l.getBoundingClientRect(),v=p=>{const h=ee((p-S.left)/S.width);c(h),s(h*n)};u(!0),l.setPointerCapture(g.pointerId),v(g.clientX);const b=p=>v(p.clientX),T=p=>{v(p.clientX),u(!1),l.releasePointerCapture(g.pointerId),l.removeEventListener("pointermove",b),l.removeEventListener("pointerup",T),l.removeEventListener("pointercancel",T)};l.addEventListener("pointermove",b),l.addEventListener("pointerup",T),l.addEventListener("pointercancel",T)},[n,s]);return{fraction:o,displayFraction:f,isDragging:r,onPointerDown:k}}exports.attachLiveAnalyser=X;exports.detachLiveAnalyser=N;exports.parseLrc=H;exports.useGinger=V;exports.useGingerChapters=Y;exports.useGingerDebugLog=$;exports.useGingerKeyboardShortcuts=O;exports.useGingerLiveAnalyzer=J;exports.useGingerLyricsSync=Z;exports.useGingerSleepTimer=_;exports.useSeekDrag=te;
2
+ //# sourceMappingURL=useSeekDrag-Cc0wpC70.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSeekDrag-Cc0wpC70.cjs","sources":["../src/hooks/useGinger.ts","../src/analyzer/liveAudioGraph.ts","../src/analyzer/useGingerLiveAnalyzer.ts","../src/hooks/useGingerKeyboardShortcuts.ts","../src/hooks/useGingerChapters.ts","../src/internal/lyrics.ts","../src/hooks/useGingerLyricsSync.ts","../src/hooks/useGingerSleepTimer.ts","../src/hooks/useGingerDebugLog.ts","../src/hooks/useSeekDrag.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { gingerStateFromContextValues, useGingerMedia, useGingerPlayback } from \"../context/GingerSplitContexts\";\nimport {\n derivePlaybackUiState,\n effectiveDuration,\n effectiveRemaining,\n getCurrentTrack,\n progressFraction,\n resolvedAlbumLine,\n resolvedArtwork,\n} from \"../internal/selectors\";\n\nexport function useGinger() {\n const pb = useGingerPlayback();\n const md = useGingerMedia();\n\n return useMemo(\n () => {\n const state = gingerStateFromContextValues(pb, md);\n return {\n state,\n currentTrack: getCurrentTrack(state),\n playbackUi: derivePlaybackUiState(state),\n duration: effectiveDuration(state),\n remaining: effectiveRemaining(state),\n progress: progressFraction(state),\n artworkUrl: resolvedArtwork(state),\n albumLine: resolvedAlbumLine(state),\n play: pb.play,\n pause: pb.pause,\n togglePlayPause: pb.togglePlayPause,\n seek: md.seek,\n setVolume: md.setVolume,\n setMuted: md.setMuted,\n toggleMute: md.toggleMute,\n setPlaybackRate: md.setPlaybackRate,\n next: pb.next,\n prev: pb.prev,\n setRepeatMode: pb.setRepeatMode,\n cycleRepeat: pb.cycleRepeat,\n toggleShuffle: pb.toggleShuffle,\n setQueue: pb.setQueue,\n insertTrackAt: pb.insertTrackAt,\n removeTrackAt: pb.removeTrackAt,\n moveTrack: pb.moveTrack,\n enqueueNext: pb.enqueueNext,\n playTrackAt: pb.playTrackAt,\n selectTrackAt: pb.selectTrackAt,\n setPlaylistMeta: pb.setPlaylistMeta,\n init: pb.init,\n audioRef: md.audioRef,\n dispatch: pb.dispatch,\n };\n },\n [pb, md],\n );\n}\n","/** One MediaElementAudioSourceNode per HTMLAudioElement; multiple AnalyserNodes may tap the source. */\n\nexport type LiveAnalyserOptions = {\n fftSize: number;\n smoothingTimeConstant: number;\n minDecibels: number;\n maxDecibels: number;\n};\n\ntype Consumer = {\n analyser: AnalyserNode;\n /** This analyser is wired to `audioContext.destination` so the graph is audible. */\n isPlaybackSink: boolean;\n};\n\ntype ElementEntry = {\n context: AudioContext;\n source: MediaElementAudioSourceNode;\n consumers: Map<number, Consumer>;\n nextId: number;\n};\n\nconst entries = new WeakMap<HTMLAudioElement, ElementEntry>();\n\nfunction clampFftSize(n: number): number {\n const p = 2 ** Math.round(Math.log2(n));\n return Math.min(32768, Math.max(32, p));\n}\n\nexport function attachLiveAnalyser(\n element: HTMLAudioElement,\n options: LiveAnalyserOptions,\n): { id: number; context: AudioContext; analyser: AnalyserNode } {\n let entry = entries.get(element);\n if (!entry) {\n const Context = window.AudioContext ?? (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Context) {\n throw new Error(\"Web Audio API is not available\");\n }\n const context = new Context();\n const source = context.createMediaElementSource(element);\n entry = { context, source, consumers: new Map(), nextId: 0 };\n entries.set(element, entry);\n }\n\n const { context, source } = entry;\n const analyser = context.createAnalyser();\n analyser.fftSize = clampFftSize(options.fftSize);\n analyser.smoothingTimeConstant = options.smoothingTimeConstant;\n analyser.minDecibels = options.minDecibels;\n analyser.maxDecibels = options.maxDecibels;\n\n source.connect(analyser);\n\n const isFirst = entry.consumers.size === 0;\n if (isFirst) {\n analyser.connect(context.destination);\n }\n\n const id = entry.nextId;\n entry.nextId += 1;\n entry.consumers.set(id, { analyser, isPlaybackSink: isFirst });\n\n return { id, context, analyser };\n}\n\nexport function detachLiveAnalyser(element: HTMLAudioElement, id: number): void {\n const entry = entries.get(element);\n if (!entry) return;\n\n const consumer = entry.consumers.get(id);\n if (!consumer) return;\n\n const { analyser, isPlaybackSink } = consumer;\n analyser.disconnect();\n entry.consumers.delete(id);\n\n if (entry.consumers.size === 0) {\n try {\n entry.source.disconnect();\n } catch {\n // ignore\n }\n void entry.context.close();\n entries.delete(element);\n return;\n }\n\n if (isPlaybackSink) {\n const first = entry.consumers.values().next().value as Consumer | undefined;\n if (first) {\n first.analyser.connect(entry.context.destination);\n first.isPlaybackSink = true;\n }\n }\n}\n","import { useCallback, useLayoutEffect, useMemo, useRef, useState } from \"react\";\nimport { useGinger } from \"../hooks/useGinger\";\nimport { attachLiveAnalyser, detachLiveAnalyser, type LiveAnalyserOptions } from \"./liveAudioGraph\";\n\nexport type UseGingerLiveAnalyzerOptions = {\n /** When false, the analyser is detached and no frames are read. Default true. */\n enabled?: boolean;\n fftSize?: number;\n smoothingTimeConstant?: number;\n minDecibels?: number;\n maxDecibels?: number;\n};\n\nexport type UseGingerLiveAnalyzerResult = {\n /** Byte frequency data (0–255); length equals `frequencyBinCount`. Updated each animation frame while enabled. */\n frequencyData: Uint8Array;\n /** Byte time-domain data (0–255); length equals `fftSize`. */\n timeDomainData: Uint8Array;\n frequencyBinCount: number;\n sampleRate: number;\n isSuspended: boolean;\n error: string | null;\n resume: () => Promise<void>;\n};\n\nconst emptyFreq = new Uint8Array(0);\nconst emptyTime = new Uint8Array(0);\n\nexport function useGingerLiveAnalyzer(options: UseGingerLiveAnalyzerOptions = {}): UseGingerLiveAnalyzerResult {\n const {\n enabled = true,\n fftSize = 2048,\n smoothingTimeConstant = 0.8,\n minDecibels = -100,\n maxDecibels = -30,\n } = options;\n\n const { audioRef, state } = useGinger();\n const opts = useMemo<LiveAnalyserOptions>(\n () => ({\n fftSize,\n smoothingTimeConstant,\n minDecibels,\n maxDecibels,\n }),\n [fftSize, smoothingTimeConstant, minDecibels, maxDecibels],\n );\n\n const [frame, setFrame] = useState(0);\n const [error, setError] = useState<string | null>(null);\n const [isSuspended, setIsSuspended] = useState(false);\n const [meta, setMeta] = useState({ frequencyBinCount: 0, sampleRate: 0 });\n\n const freqRef = useRef<Uint8Array>(emptyFreq);\n const timeRef = useRef<Uint8Array>(emptyTime);\n\n const resume = useCallback(async () => {\n const ctx = contextHolderRef.current;\n if (ctx && ctx.state === \"suspended\") {\n await ctx.resume();\n }\n }, []);\n\n const contextHolderRef = useRef<AudioContext | null>(null);\n const analyserHolderRef = useRef<AnalyserNode | null>(null);\n\n useLayoutEffect(() => {\n if (!enabled || typeof window === \"undefined\") {\n return;\n }\n\n let cancelled = false;\n let consumerId: number | null = null;\n let element: HTMLAudioElement | null = null;\n let rafId = 0;\n\n const onStateChange = () => {\n const ctx = contextHolderRef.current;\n if (ctx) setIsSuspended(ctx.state === \"suspended\");\n };\n\n const runLoop = () => {\n if (cancelled) return;\n const a = analyserHolderRef.current;\n const fq = freqRef.current;\n const td = timeRef.current;\n if (a && fq.length > 0 && td.length > 0) {\n a.getByteFrequencyData(fq as Uint8Array<ArrayBuffer>);\n a.getByteTimeDomainData(td as Uint8Array<ArrayBuffer>);\n setFrame((n) => n + 1);\n }\n rafId = requestAnimationFrame(runLoop);\n };\n\n type AttachOutcome = \"ok\" | \"no-element\" | \"error\";\n\n const attach = (): AttachOutcome => {\n const el = audioRef.current;\n if (!el || cancelled) return \"no-element\";\n try {\n const { id, context, analyser } = attachLiveAnalyser(el, opts);\n consumerId = id;\n element = el;\n contextHolderRef.current = context;\n analyserHolderRef.current = analyser;\n setIsSuspended(context.state === \"suspended\");\n setError(null);\n\n context.addEventListener(\"statechange\", onStateChange);\n\n const n = analyser.frequencyBinCount;\n const fft = analyser.fftSize;\n freqRef.current = new Uint8Array(n);\n timeRef.current = new Uint8Array(fft);\n setMeta({ frequencyBinCount: n, sampleRate: context.sampleRate });\n\n rafId = requestAnimationFrame(runLoop);\n return \"ok\";\n } catch (e) {\n const msg = e instanceof Error ? e.message : \"Failed to attach live analyser\";\n setError(msg);\n contextHolderRef.current = null;\n analyserHolderRef.current = null;\n freqRef.current = emptyFreq;\n timeRef.current = emptyTime;\n setMeta({ frequencyBinCount: 0, sampleRate: 0 });\n return \"error\";\n }\n };\n\n const first = attach();\n if (first !== \"ok\") {\n let retryRaf = 0;\n const maxAttempts = 120;\n let attempts = 0;\n\n const retryLoop = () => {\n if (cancelled) return;\n const out = attach();\n if (out === \"ok\" || out === \"error\") return;\n attempts += 1;\n if (attempts >= maxAttempts) return;\n retryRaf = requestAnimationFrame(retryLoop);\n };\n\n if (first === \"no-element\") {\n retryRaf = requestAnimationFrame(retryLoop);\n }\n\n return () => {\n cancelled = true;\n cancelAnimationFrame(retryRaf);\n cancelAnimationFrame(rafId);\n if (consumerId != null && element) {\n detachLiveAnalyser(element, consumerId);\n }\n contextHolderRef.current?.removeEventListener(\"statechange\", onStateChange);\n contextHolderRef.current = null;\n analyserHolderRef.current = null;\n freqRef.current = emptyFreq;\n timeRef.current = emptyTime;\n };\n }\n\n return () => {\n cancelled = true;\n cancelAnimationFrame(rafId);\n if (consumerId != null && element) {\n detachLiveAnalyser(element, consumerId);\n }\n contextHolderRef.current?.removeEventListener(\"statechange\", onStateChange);\n contextHolderRef.current = null;\n analyserHolderRef.current = null;\n freqRef.current = emptyFreq;\n timeRef.current = emptyTime;\n setMeta({ frequencyBinCount: 0, sampleRate: 0 });\n };\n }, [enabled, audioRef, opts, state.currentIndex]);\n\n void frame;\n\n return {\n frequencyData: freqRef.current,\n timeDomainData: timeRef.current,\n frequencyBinCount: meta.frequencyBinCount,\n sampleRate: meta.sampleRate,\n isSuspended,\n error,\n resume,\n };\n}\n","import { useEffect } from \"react\";\nimport { useGingerMedia, useGingerPlayback } from \"../context/GingerSplitContexts\";\n\nexport type GingerKeyboardShortcutBindings = {\n playPause?: string;\n next?: string;\n previous?: string;\n mute?: string;\n};\n\nexport function useGingerKeyboardShortcuts(\n enabled = true,\n bindings: GingerKeyboardShortcutBindings = {},\n): void {\n const { togglePlayPause, next, prev } = useGingerPlayback();\n const { toggleMute } = useGingerMedia();\n\n const muteBinding = bindings.mute;\n\n useEffect(() => {\n if (!enabled || typeof window === \"undefined\") return;\n const playPause = (bindings.playPause ?? \" \").toLowerCase();\n const nextKey = (bindings.next ?? \"ArrowRight\").toLowerCase();\n const prevKey = (bindings.previous ?? \"ArrowLeft\").toLowerCase();\n const muteKey = muteBinding?.toLowerCase();\n\n const onKeyDown = (event: KeyboardEvent) => {\n const target = event.target as HTMLElement | null;\n if (target && ([\"INPUT\", \"TEXTAREA\", \"SELECT\"].includes(target.tagName) || target.isContentEditable)) return;\n const key = event.key.toLowerCase();\n if (key === playPause) {\n event.preventDefault();\n togglePlayPause();\n } else if (key === nextKey) {\n event.preventDefault();\n next();\n } else if (key === prevKey) {\n event.preventDefault();\n prev();\n } else if (muteKey && key === muteKey) {\n event.preventDefault();\n toggleMute();\n }\n };\n window.addEventListener(\"keydown\", onKeyDown);\n return () => window.removeEventListener(\"keydown\", onKeyDown);\n }, [bindings.next, bindings.playPause, bindings.previous, enabled, muteBinding, next, prev, toggleMute, togglePlayPause]);\n}\n","import { useMemo } from \"react\";\nimport { useGingerMedia, useGingerPlayback } from \"../context/GingerSplitContexts\";\n\nexport type GingerChapter = {\n title: string;\n startSeconds: number;\n};\n\nexport type GingerChapterState = {\n list: GingerChapter[];\n activeIndex: number;\n active: GingerChapter | null;\n seekTo: (index: number) => void;\n};\n\nexport function useGingerChapters(): GingerChapterState {\n const { tracks, currentIndex } = useGingerPlayback();\n const { currentTime, seek } = useGingerMedia();\n\n const list = useMemo(() => {\n const chapters = tracks[currentIndex]?.chapters ?? [];\n return [...chapters]\n .filter((item) => item && Number.isFinite(item.startSeconds) && item.startSeconds >= 0)\n .sort((a, b) => a.startSeconds - b.startSeconds);\n }, [currentIndex, tracks]);\n\n const activeIndex = useMemo(() => {\n if (list.length === 0) return -1;\n for (let i = list.length - 1; i >= 0; i -= 1) {\n if (currentTime >= list[i]!.startSeconds) return i;\n }\n return -1;\n }, [currentTime, list]);\n\n return {\n list,\n activeIndex,\n active: activeIndex >= 0 ? list[activeIndex] ?? null : null,\n seekTo: (index: number) => {\n const chapter = list[index];\n if (chapter) seek(chapter.startSeconds);\n },\n };\n}\n","export type TimedLyricLine = {\n time: number;\n text: string;\n};\n\nconst lrcTag = /\\[(\\d{1,2}):(\\d{2})(?:\\.(\\d{1,3}))?\\]/g;\n\nexport function parseLrc(lrc: string): TimedLyricLine[] {\n const lines: TimedLyricLine[] = [];\n for (const rawLine of lrc.split(/\\r?\\n/)) {\n const matches = [...rawLine.matchAll(lrcTag)];\n if (matches.length === 0) continue;\n const text = rawLine.replace(lrcTag, \"\").trim();\n for (const m of matches) {\n const minutes = Number(m[1] ?? 0);\n const seconds = Number(m[2] ?? 0);\n const millis = Number((m[3] ?? \"0\").padEnd(3, \"0\"));\n const time = minutes * 60 + seconds + millis / 1000;\n if (Number.isFinite(time) && time >= 0) {\n lines.push({ time, text });\n }\n }\n }\n return lines.sort((a, b) => a.time - b.time);\n}\n","import { useMemo } from \"react\";\nimport { useGingerMedia, useGingerPlayback } from \"../context/GingerSplitContexts\";\nimport { parseLrc, type TimedLyricLine } from \"../internal/lyrics\";\n\nexport type GingerLyricsSyncState = {\n lines: TimedLyricLine[];\n activeIndex: number;\n activeLine: TimedLyricLine | null;\n};\n\nexport function useGingerLyricsSync(): GingerLyricsSyncState {\n const { tracks, currentIndex } = useGingerPlayback();\n const { currentTime } = useGingerMedia();\n const currentTrack = tracks[currentIndex];\n\n const lines = useMemo(() => {\n if (!currentTrack) return [];\n if (Array.isArray(currentTrack.lyricsTimed) && currentTrack.lyricsTimed.length > 0) {\n return [...currentTrack.lyricsTimed]\n .filter((line) => Number.isFinite(line.time) && line.time >= 0)\n .sort((a, b) => a.time - b.time);\n }\n if (typeof currentTrack.lyrics === \"string\") {\n return parseLrc(currentTrack.lyrics);\n }\n return [];\n }, [currentTrack]);\n\n const activeIndex = useMemo(() => {\n for (let i = lines.length - 1; i >= 0; i -= 1) {\n if (currentTime >= lines[i]!.time) return i;\n }\n return -1;\n }, [currentTime, lines]);\n\n return {\n lines,\n activeIndex,\n activeLine: activeIndex >= 0 ? lines[activeIndex] ?? null : null,\n };\n}\n","import { useEffect, useRef } from \"react\";\nimport { useGingerPlayback } from \"../context/GingerSplitContexts\";\n\nexport type GingerSleepTimerOptions = {\n durationMs?: number;\n stopAfterTracks?: number;\n respectPause?: boolean;\n enabled?: boolean;\n onFire?: () => void;\n};\n\nexport function useGingerSleepTimer(options: GingerSleepTimerOptions): void {\n const { durationMs, stopAfterTracks, respectPause = true, enabled = true, onFire } = options;\n const { currentIndex, pause, isPaused } = useGingerPlayback();\n const remainingTracksRef = useRef(stopAfterTracks ?? 0);\n const prevIndexRef = useRef(currentIndex);\n\n useEffect(() => {\n remainingTracksRef.current = stopAfterTracks ?? 0;\n }, [stopAfterTracks]);\n\n useEffect(() => {\n if (!enabled || !durationMs || durationMs <= 0) return;\n if (respectPause && isPaused) return;\n const id = setTimeout(() => {\n pause();\n onFire?.();\n }, durationMs);\n return () => clearTimeout(id);\n }, [durationMs, enabled, isPaused, onFire, pause, respectPause]);\n\n useEffect(() => {\n if (!enabled || !stopAfterTracks || stopAfterTracks <= 0) return;\n const prev = prevIndexRef.current;\n prevIndexRef.current = currentIndex;\n if (currentIndex === prev) return;\n remainingTracksRef.current -= 1;\n if (remainingTracksRef.current <= 0) {\n pause();\n onFire?.();\n }\n }, [currentIndex, enabled, onFire, pause, stopAfterTracks]);\n}\n","import { useEffect, useRef } from \"react\";\nimport { useGingerState } from \"../context/GingerSplitContexts\";\n\nexport function useGingerDebugLog(enabled = false): void {\n const state = useGingerState();\n const prevRef = useRef(state);\n\n useEffect(() => {\n if (!enabled || typeof console === \"undefined\") return;\n const prev = prevRef.current;\n if (prev !== state) {\n console.debug(\"[ginger]\", {\n from: {\n currentIndex: prev.currentIndex,\n isPaused: prev.isPaused,\n currentTime: prev.currentTime,\n repeatMode: prev.repeatMode,\n },\n to: {\n currentIndex: state.currentIndex,\n isPaused: state.isPaused,\n currentTime: state.currentTime,\n repeatMode: state.repeatMode,\n },\n });\n }\n prevRef.current = state;\n }, [enabled, state]);\n}\n","import { useCallback, useState } from \"react\";\nimport type { PointerEvent as ReactPointerEvent } from \"react\";\nimport { useGingerMedia, useGingerPlayback, gingerStateFromContextValues } from \"../context/GingerSplitContexts\";\nimport { progressFraction } from \"../internal/selectors\";\n\nexport type SeekDragState = {\n /** Raw drag fraction — only updated during an active drag gesture. */\n fraction: number;\n /** Blended fraction: follows live playback when idle, drag position when dragging. */\n displayFraction: number;\n isDragging: boolean;\n onPointerDown: (event: ReactPointerEvent<HTMLElement>) => void;\n};\n\nfunction clamp01(value: number): number {\n return Math.max(0, Math.min(1, value));\n}\n\nexport function useSeekDrag(duration: number): SeekDragState {\n const media = useGingerMedia();\n const playback = useGingerPlayback();\n const { seek } = media;\n const [fraction, setFraction] = useState(0);\n const [isDragging, setIsDragging] = useState(false);\n\n const liveFraction = progressFraction(gingerStateFromContextValues(playback, media));\n const displayFraction = isDragging ? fraction : liveFraction;\n\n const onPointerDown = useCallback(\n (event: ReactPointerEvent<HTMLElement>) => {\n if (!(duration > 0)) return;\n const target = event.currentTarget;\n const rect = target.getBoundingClientRect();\n const update = (clientX: number) => {\n const ratio = clamp01((clientX - rect.left) / rect.width);\n setFraction(ratio);\n seek(ratio * duration);\n };\n setIsDragging(true);\n target.setPointerCapture(event.pointerId);\n update(event.clientX);\n const onMove = (moveEvent: PointerEvent) => update(moveEvent.clientX);\n const onUp = (upEvent: PointerEvent) => {\n update(upEvent.clientX);\n setIsDragging(false);\n target.releasePointerCapture(event.pointerId);\n target.removeEventListener(\"pointermove\", onMove);\n target.removeEventListener(\"pointerup\", onUp);\n target.removeEventListener(\"pointercancel\", onUp);\n };\n target.addEventListener(\"pointermove\", onMove);\n target.addEventListener(\"pointerup\", onUp);\n target.addEventListener(\"pointercancel\", onUp);\n },\n [duration, seek],\n );\n\n return { fraction, displayFraction, isDragging, onPointerDown };\n}\n"],"names":["useGinger","pb","useGingerPlayback","md","useGingerMedia","useMemo","state","gingerStateFromContextValues","getCurrentTrack","derivePlaybackUiState","effectiveDuration","effectiveRemaining","progressFraction","resolvedArtwork","resolvedAlbumLine","entries","clampFftSize","p","attachLiveAnalyser","element","options","entry","Context","context","source","analyser","isFirst","id","detachLiveAnalyser","consumer","isPlaybackSink","first","emptyFreq","emptyTime","useGingerLiveAnalyzer","enabled","fftSize","smoothingTimeConstant","minDecibels","maxDecibels","audioRef","opts","frame","setFrame","useState","error","setError","isSuspended","setIsSuspended","meta","setMeta","freqRef","useRef","timeRef","resume","useCallback","ctx","contextHolderRef","analyserHolderRef","useLayoutEffect","cancelled","consumerId","rafId","onStateChange","runLoop","a","fq","td","n","attach","el","fft","e","msg","retryRaf","maxAttempts","attempts","retryLoop","out","_a","useGingerKeyboardShortcuts","bindings","togglePlayPause","next","prev","toggleMute","muteBinding","useEffect","playPause","nextKey","prevKey","muteKey","onKeyDown","event","target","key","useGingerChapters","tracks","currentIndex","currentTime","seek","list","item","b","activeIndex","i","index","chapter","lrcTag","parseLrc","lrc","lines","rawLine","matches","text","m","minutes","seconds","millis","time","useGingerLyricsSync","currentTrack","line","useGingerSleepTimer","durationMs","stopAfterTracks","respectPause","onFire","pause","isPaused","remainingTracksRef","prevIndexRef","useGingerDebugLog","useGingerState","prevRef","clamp01","value","useSeekDrag","duration","media","playback","fraction","setFraction","isDragging","setIsDragging","liveFraction","displayFraction","onPointerDown","rect","update","clientX","ratio","onMove","moveEvent","onUp","upEvent"],"mappings":"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"}