@lucaismyname/ginger 0.0.54 → 0.0.55
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 +3 -0
- package/dist/GingerSplitContexts-DQ3rESBu.js +78 -0
- package/dist/GingerSplitContexts-DQ3rESBu.js.map +1 -0
- package/dist/GingerSplitContexts-KSB0vQ5F.cjs +2 -0
- package/dist/GingerSplitContexts-KSB0vQ5F.cjs.map +1 -0
- package/dist/analyzer/useGingerLiveAnalyzer.d.ts.map +1 -1
- package/dist/client.cjs +1 -1
- package/dist/client.js +28 -26
- package/dist/context/GingerProvider.d.ts +1 -1
- package/dist/context/GingerProvider.d.ts.map +1 -1
- package/dist/context/GingerSplitContexts.d.ts +22 -2
- package/dist/context/GingerSplitContexts.d.ts.map +1 -1
- package/dist/crossfade/index.cjs +1 -1
- package/dist/crossfade/index.cjs.map +1 -1
- package/dist/crossfade/index.js +116 -99
- package/dist/crossfade/index.js.map +1 -1
- package/dist/crossfade/useGingerCrossfade.d.ts.map +1 -1
- package/dist/equalizer/index.cjs +1 -1
- package/dist/equalizer/index.js +1 -1
- package/dist/experimental-gapless/index.cjs +1 -1
- package/dist/experimental-gapless/index.js +1 -1
- package/dist/ginger-Bzjmat52.cjs +2 -0
- package/dist/ginger-Bzjmat52.cjs.map +1 -0
- package/dist/ginger-r_BCOWSm.js +2121 -0
- package/dist/ginger-r_BCOWSm.js.map +1 -0
- package/dist/hooks/useGingerKeyboardShortcuts.d.ts.map +1 -1
- package/dist/hooks/useSeekDrag.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -26
- package/dist/internal/formatTime.d.ts.map +1 -1
- package/dist/remote/index.cjs +1 -1
- package/dist/remote/index.js +1 -1
- package/dist/spatial/index.cjs +1 -1
- package/dist/spatial/index.js +1 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/testing/index.cjs +1 -1
- package/dist/testing/index.js +1 -1
- package/dist/transcript/index.cjs +1 -1
- package/dist/transcript/index.js +1 -1
- package/dist/types.d.ts +24 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/{useGinger-4uvPoChz.cjs → useGinger-BMRLzjmr.cjs} +2 -2
- package/dist/{useGinger-4uvPoChz.cjs.map → useGinger-BMRLzjmr.cjs.map} +1 -1
- package/dist/{useGinger-Dz0cPyD1.js → useGinger-DKrHZ4NU.js} +2 -2
- package/dist/{useGinger-Dz0cPyD1.js.map → useGinger-DKrHZ4NU.js.map} +1 -1
- package/dist/useGingerChapterProgress-BgLkDm2S.cjs +2 -0
- package/dist/useGingerChapterProgress-BgLkDm2S.cjs.map +1 -0
- package/dist/useGingerChapterProgress-DIk27KzH.js +343 -0
- package/dist/useGingerChapterProgress-DIk27KzH.js.map +1 -0
- package/package.json +1 -1
- package/dist/GingerSplitContexts-BzBExb95.js +0 -64
- package/dist/GingerSplitContexts-BzBExb95.js.map +0 -1
- package/dist/GingerSplitContexts-C7puo0M7.cjs +0 -2
- package/dist/GingerSplitContexts-C7puo0M7.cjs.map +0 -1
- package/dist/ginger-9lWCvbHv.cjs +0 -2
- package/dist/ginger-9lWCvbHv.cjs.map +0 -1
- package/dist/ginger-XgXdGRB-.js +0 -2049
- package/dist/ginger-XgXdGRB-.js.map +0 -1
- package/dist/useGingerChapterProgress-Cqa9_CyH.cjs +0 -2
- package/dist/useGingerChapterProgress-Cqa9_CyH.cjs.map +0 -1
- package/dist/useGingerChapterProgress-DgqqoY5F.js +0 -324
- package/dist/useGingerChapterProgress-DgqqoY5F.js.map +0 -1
package/dist/crossfade/index.js
CHANGED
|
@@ -1,124 +1,141 @@
|
|
|
1
|
-
import { useState as
|
|
2
|
-
import {
|
|
3
|
-
import { c as
|
|
4
|
-
const
|
|
5
|
-
function
|
|
6
|
-
const
|
|
7
|
-
for (let
|
|
8
|
-
const
|
|
9
|
-
|
|
1
|
+
import { useState as W, useRef as k, useCallback as ee, useEffect as y } from "react";
|
|
2
|
+
import { c as te, u as ne } from "../GingerSplitContexts-DQ3rESBu.js";
|
|
3
|
+
import { c as re } from "../transitions-CmNkf3sd.js";
|
|
4
|
+
const I = 256;
|
|
5
|
+
function oe() {
|
|
6
|
+
const o = new Float32Array(I), i = new Float32Array(I);
|
|
7
|
+
for (let n = 0; n < I; n++) {
|
|
8
|
+
const t = n / (I - 1);
|
|
9
|
+
o[n] = Math.cos(t * (Math.PI / 2)), i[n] = Math.sin(t * (Math.PI / 2));
|
|
10
10
|
}
|
|
11
|
-
return { outCurve:
|
|
11
|
+
return { outCurve: o, inCurve: i };
|
|
12
12
|
}
|
|
13
|
-
function
|
|
13
|
+
function ie() {
|
|
14
14
|
if (!(typeof window > "u"))
|
|
15
15
|
return window.AudioContext ?? window.webkitAudioContext;
|
|
16
16
|
}
|
|
17
|
-
function
|
|
18
|
-
const
|
|
19
|
-
if (!
|
|
17
|
+
function ae(o, i) {
|
|
18
|
+
const n = ie();
|
|
19
|
+
if (!n)
|
|
20
20
|
throw new Error(
|
|
21
21
|
"[@lucaismyname/ginger/crossfade] Web Audio API is not available in this environment."
|
|
22
22
|
);
|
|
23
|
-
const
|
|
24
|
-
return
|
|
23
|
+
const t = new n(), c = t.createMediaElementSource(o), u = t.createMediaElementSource(i), r = t.createGain(), a = t.createGain();
|
|
24
|
+
return r.gain.value = 1, a.gain.value = 0, c.connect(r), r.connect(t.destination), u.connect(a), a.connect(t.destination), { context: t, outGain: r, inGain: a, outSource: c, inSource: u };
|
|
25
25
|
}
|
|
26
|
-
function
|
|
27
|
-
const { context:
|
|
28
|
-
if (
|
|
29
|
-
const { outCurve:
|
|
30
|
-
c.gain.setValueCurveAtTime(
|
|
26
|
+
function se(o, i, n) {
|
|
27
|
+
const { context: t, outGain: c, inGain: u } = o, r = t.currentTime, a = r + i;
|
|
28
|
+
if (n === "equal-power") {
|
|
29
|
+
const { outCurve: p, inCurve: g } = oe();
|
|
30
|
+
c.gain.setValueCurveAtTime(p, r, i), u.gain.setValueCurveAtTime(g, r, i);
|
|
31
31
|
} else
|
|
32
|
-
c.gain.setValueAtTime(1,
|
|
32
|
+
c.gain.setValueAtTime(1, r), c.gain.linearRampToValueAtTime(0, a), u.gain.setValueAtTime(0, r), u.gain.linearRampToValueAtTime(1, a);
|
|
33
33
|
}
|
|
34
|
-
function
|
|
35
|
-
const i = [
|
|
36
|
-
for (const
|
|
34
|
+
function z(o) {
|
|
35
|
+
const i = [o.outSource, o.inSource, o.outGain, o.inGain];
|
|
36
|
+
for (const n of i)
|
|
37
37
|
try {
|
|
38
|
-
|
|
38
|
+
n.disconnect();
|
|
39
39
|
} catch {
|
|
40
40
|
}
|
|
41
|
-
|
|
41
|
+
o.context.close();
|
|
42
42
|
}
|
|
43
|
-
function
|
|
44
|
-
const { duration: i = 3, curve:
|
|
43
|
+
function le(o = {}) {
|
|
44
|
+
const { duration: i = 3, curve: n = "equal-power", crossOrigin: t, enabled: c = !0 } = o, { tracks: u, currentIndex: r, isPaused: a, repeatMode: p, playbackMode: g, dispatch: P } = te(), { currentTime: H, duration: Q, audioRef: R, muted: A, volume: v } = ne(), [X, b] = W(!1), [j, C] = W(0), d = k(null), h = ee(() => {
|
|
45
45
|
const e = d.current;
|
|
46
|
-
e && (e.aborted = !0, clearTimeout(e.timeoutId), cancelAnimationFrame(e.rafId),
|
|
46
|
+
e && (e.aborted = !0, clearTimeout(e.timeoutId), cancelAnimationFrame(e.rafId), z(e.graph), e.incomingAudio.pause(), e.incomingAudio.removeAttribute("src"), e.incomingAudio.load(), d.current = null, b(!1), C(0));
|
|
47
47
|
}, []);
|
|
48
|
-
|
|
48
|
+
y(() => {
|
|
49
49
|
const e = d.current;
|
|
50
|
-
e && (
|
|
51
|
-
}, [
|
|
50
|
+
e && (a || r !== e.startedAtIndex) && h();
|
|
51
|
+
}, [a, r, h]), y(() => () => h(), []), y(() => {
|
|
52
52
|
const e = d.current;
|
|
53
|
-
e && (e.incomingAudio.volume =
|
|
54
|
-
}, [
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
53
|
+
e && (e.incomingAudio.volume = v, e.incomingAudio.muted = A);
|
|
54
|
+
}, [v, A]);
|
|
55
|
+
const V = k({ currentTime: 0, trackDuration: 0 });
|
|
56
|
+
V.current = { currentTime: H, trackDuration: Q };
|
|
57
|
+
const S = k({
|
|
58
|
+
tracks: u,
|
|
59
|
+
currentIndex: r,
|
|
60
|
+
repeatMode: p,
|
|
61
|
+
playbackMode: g,
|
|
62
|
+
volume: v,
|
|
63
|
+
muted: A
|
|
64
|
+
});
|
|
65
|
+
return S.current = { tracks: u, currentIndex: r, repeatMode: p, playbackMode: g, volume: v, muted: A }, y(() => {
|
|
66
|
+
if (!c || a || typeof window > "u") return;
|
|
67
|
+
const e = 250;
|
|
68
|
+
let l = null;
|
|
69
|
+
const _ = () => {
|
|
70
|
+
if (d.current) return;
|
|
71
|
+
const { currentTime: B, trackDuration: q } = V.current;
|
|
72
|
+
if (!(q > 0)) return;
|
|
73
|
+
const w = q - B;
|
|
74
|
+
if (w > i || w <= 0) return;
|
|
75
|
+
const {
|
|
76
|
+
tracks: D,
|
|
77
|
+
currentIndex: E,
|
|
78
|
+
repeatMode: J,
|
|
79
|
+
playbackMode: K,
|
|
80
|
+
volume: Y,
|
|
81
|
+
muted: Z
|
|
82
|
+
} = S.current, x = re({
|
|
83
|
+
tracks: D,
|
|
84
|
+
currentIndex: E,
|
|
85
|
+
repeatMode: J,
|
|
86
|
+
playbackMode: K
|
|
87
|
+
});
|
|
88
|
+
if (x.kind === "stop") return;
|
|
89
|
+
const F = x.kind === "replay_same" ? E : x.nextIndex, T = D[F];
|
|
90
|
+
if (!(T != null && T.fileUrl)) return;
|
|
91
|
+
const L = R.current;
|
|
92
|
+
if (!L) return;
|
|
93
|
+
const s = document.createElement("audio");
|
|
94
|
+
s.preload = "auto", s.volume = Y, s.muted = Z, t && (s.crossOrigin = t), s.src = T.fileUrl;
|
|
95
|
+
let f;
|
|
96
|
+
try {
|
|
97
|
+
f = ae(L, s);
|
|
98
|
+
} catch (m) {
|
|
99
|
+
process.env.NODE_ENV !== "production" && console.warn(
|
|
100
|
+
"[@lucaismyname/ginger/crossfade] Failed to attach crossfade graph. This may be because the audio element is already connected to a Web Audio graph (e.g. via useGingerEqualizer or useGingerLiveAnalyzer). These features are incompatible with useGingerCrossfade.",
|
|
101
|
+
m
|
|
102
|
+
);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
f.context.resume(), s.load(), s.play().catch(() => {
|
|
106
|
+
}), se(f, w, n);
|
|
107
|
+
const N = performance.now(), G = w * 1e3;
|
|
108
|
+
b(!0), C(0);
|
|
109
|
+
let M = 0;
|
|
110
|
+
const O = () => {
|
|
111
|
+
const m = performance.now() - N, U = Math.min(1, m / G);
|
|
112
|
+
C(U), U < 1 && (M = requestAnimationFrame(O));
|
|
113
|
+
};
|
|
114
|
+
M = requestAnimationFrame(O);
|
|
115
|
+
const $ = setTimeout(() => {
|
|
116
|
+
const m = d.current;
|
|
117
|
+
!m || m.aborted || (P({ type: "SET_INDEX", payload: { index: F, autoPlay: !0 } }), z(f), s.pause(), s.removeAttribute("src"), s.load(), d.current = null, b(!1), C(0));
|
|
118
|
+
}, G);
|
|
119
|
+
d.current = {
|
|
120
|
+
graph: f,
|
|
121
|
+
incomingAudio: s,
|
|
122
|
+
startedAtIndex: E,
|
|
123
|
+
startTime: N,
|
|
124
|
+
fadeDurationMs: G,
|
|
125
|
+
timeoutId: $,
|
|
126
|
+
rafId: M,
|
|
127
|
+
aborted: !1
|
|
128
|
+
}, l != null && (clearInterval(l), l = null);
|
|
84
129
|
};
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const l = d.current;
|
|
88
|
-
!l || l.aborted || (I({ type: "SET_INDEX", payload: { index: k, autoPlay: !0 } }), _(m), a.pause(), a.removeAttribute("src"), a.load(), d.current = null, T(!1), v(0));
|
|
89
|
-
}, G);
|
|
90
|
-
d.current = {
|
|
91
|
-
graph: m,
|
|
92
|
-
incomingAudio: a,
|
|
93
|
-
startedAtIndex: t,
|
|
94
|
-
startTime: V,
|
|
95
|
-
fadeDurationMs: G,
|
|
96
|
-
timeoutId: O,
|
|
97
|
-
rafId: x,
|
|
98
|
-
aborted: !1
|
|
130
|
+
return l = setInterval(_, e), _(), () => {
|
|
131
|
+
l != null && clearInterval(l);
|
|
99
132
|
};
|
|
100
|
-
}, [
|
|
101
|
-
c,
|
|
102
|
-
s,
|
|
103
|
-
b,
|
|
104
|
-
M,
|
|
105
|
-
i,
|
|
106
|
-
o,
|
|
107
|
-
n,
|
|
108
|
-
u,
|
|
109
|
-
t,
|
|
110
|
-
f,
|
|
111
|
-
g,
|
|
112
|
-
P,
|
|
113
|
-
A,
|
|
114
|
-
p,
|
|
115
|
-
I
|
|
116
|
-
]), { isCrossfading: D, crossfadeProgress: N };
|
|
133
|
+
}, [c, a, i, n, t, R, P]), { isCrossfading: X, crossfadeProgress: j };
|
|
117
134
|
}
|
|
118
135
|
export {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
136
|
+
ae as attachCrossfadeGraph,
|
|
137
|
+
se as scheduleCrossfade,
|
|
138
|
+
z as teardownCrossfadeGraph,
|
|
139
|
+
le as useGingerCrossfade
|
|
123
140
|
};
|
|
124
141
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../src/crossfade/crossfadeGraph.ts","../../src/crossfade/useGingerCrossfade.ts"],"sourcesContent":["/**\n * Web Audio graph management for crossfade transitions.\n *\n * Creates a shared `AudioContext` that routes both the outgoing and incoming\n * `HTMLAudioElement` through individual `GainNode`s into the same destination.\n * Scheduling the gain ramps on both nodes produces the crossfade effect.\n *\n * **Compatibility note:** because the browser only permits one\n * `MediaElementAudioSourceNode` per `HTMLAudioElement`, this module is\n * incompatible with `liveAudioGraph`-based features (`useGingerEqualizer`,\n * `useGingerLiveAnalyzer`) on the same element. Using both simultaneously will\n * throw a `DOMException` when the second source node is requested.\n */\n\nexport type CrossfadeCurve = \"linear\" | \"equal-power\";\n\nexport type CrossfadeGraph = {\n context: AudioContext;\n outGain: GainNode;\n inGain: GainNode;\n outSource: MediaElementAudioSourceNode;\n inSource: MediaElementAudioSourceNode;\n};\n\nconst EQUAL_POWER_CURVE_LENGTH = 256;\n\nfunction buildEqualPowerCurves(): { outCurve: Float32Array; inCurve: Float32Array } {\n const outCurve = new Float32Array(EQUAL_POWER_CURVE_LENGTH);\n const inCurve = new Float32Array(EQUAL_POWER_CURVE_LENGTH);\n for (let i = 0; i < EQUAL_POWER_CURVE_LENGTH; i++) {\n const t = i / (EQUAL_POWER_CURVE_LENGTH - 1);\n outCurve[i] = Math.cos(t * (Math.PI / 2));\n inCurve[i] = Math.sin(t * (Math.PI / 2));\n }\n return { outCurve, inCurve };\n}\n\nfunction getAudioContextCtor(): (new (options?: AudioContextOptions) => AudioContext) | undefined {\n if (typeof window === \"undefined\") return undefined;\n return (\n window.AudioContext ??\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext\n );\n}\n\n/**\n * Creates a shared `AudioContext` and connects both the outgoing and incoming\n * audio elements to it via individual `GainNode`s.\n *\n * The outgoing gain starts at 1, the incoming gain starts at 0.\n * Call `scheduleCrossfade` immediately after to begin the ramps.\n *\n * @throws `DOMException` if either element already has a `MediaElementAudioSourceNode`\n * in another context (e.g. created by `liveAudioGraph`).\n * @throws `Error` if the Web Audio API is unavailable in this environment.\n */\nexport function attachCrossfadeGraph(\n outgoing: HTMLAudioElement,\n incoming: HTMLAudioElement,\n): CrossfadeGraph {\n const Ctor = getAudioContextCtor();\n if (!Ctor) {\n throw new Error(\n \"[@lucaismyname/ginger/crossfade] Web Audio API is not available in this environment.\",\n );\n }\n\n const context = new Ctor();\n\n const outSource = context.createMediaElementSource(outgoing);\n const inSource = context.createMediaElementSource(incoming);\n\n const outGain = context.createGain();\n const inGain = context.createGain();\n\n outGain.gain.value = 1;\n inGain.gain.value = 0;\n\n outSource.connect(outGain);\n outGain.connect(context.destination);\n\n inSource.connect(inGain);\n inGain.connect(context.destination);\n\n return { context, outGain, inGain, outSource, inSource };\n}\n\n/**\n * Schedules gain ramps on both gain nodes so that `outGain` fades from 1 → 0\n * and `inGain` fades from 0 → 1 over `durationSec` seconds starting immediately.\n *\n * For `\"equal-power\"`, a cosine/sine curve is applied via `setValueCurveAtTime`\n * to maintain consistent perceived loudness throughout the transition.\n */\nexport function scheduleCrossfade(\n graph: CrossfadeGraph,\n durationSec: number,\n curve: CrossfadeCurve,\n): void {\n const { context, outGain, inGain } = graph;\n const startTime = context.currentTime;\n const endTime = startTime + durationSec;\n\n if (curve === \"equal-power\") {\n const { outCurve, inCurve } = buildEqualPowerCurves();\n outGain.gain.setValueCurveAtTime(outCurve, startTime, durationSec);\n inGain.gain.setValueCurveAtTime(inCurve, startTime, durationSec);\n } else {\n outGain.gain.setValueAtTime(1, startTime);\n outGain.gain.linearRampToValueAtTime(0, endTime);\n inGain.gain.setValueAtTime(0, startTime);\n inGain.gain.linearRampToValueAtTime(1, endTime);\n }\n}\n\n/**\n * Disconnects all nodes and closes the `AudioContext`.\n * Safe to call multiple times; errors during disconnect are silently ignored.\n */\nexport function teardownCrossfadeGraph(graph: CrossfadeGraph): void {\n const nodes: AudioNode[] = [graph.outSource, graph.inSource, graph.outGain, graph.inGain];\n for (const node of nodes) {\n try {\n node.disconnect();\n } catch {\n // ignore\n }\n }\n void graph.context.close();\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useGingerMedia, useGingerPlayback } from \"../context/GingerSplitContexts\";\nimport { computeEndedTransition } from \"../core/transitions\";\nimport {\n type CrossfadeCurve,\n type CrossfadeGraph,\n attachCrossfadeGraph,\n scheduleCrossfade,\n teardownCrossfadeGraph,\n} from \"./crossfadeGraph\";\n\nexport type { CrossfadeCurve };\n\nexport type UseGingerCrossfadeOptions = {\n /**\n * Duration of the crossfade in seconds.\n * The hook begins the fade when `timeRemaining ≤ duration`.\n * @default 3\n */\n duration?: number;\n /**\n * Gain curve shape applied to both gain nodes.\n * `\"equal-power\"` uses a cosine/sine curve to maintain consistent perceived\n * loudness; `\"linear\"` is a straight ramp that may dip slightly at the midpoint.\n * @default \"equal-power\"\n */\n curve?: CrossfadeCurve;\n /**\n * `crossOrigin` attribute for the incoming `<audio>` element.\n * Match this to the `crossOrigin` prop on `Ginger.Player` when serving\n * cross-origin audio so the browser can reuse the cached resource.\n */\n crossOrigin?: \"\" | \"anonymous\" | \"use-credentials\";\n /**\n * When `false`, crossfade is completely disabled and playback falls back to\n * the default hard-cut transition.\n * @default true\n */\n enabled?: boolean;\n};\n\nexport type UseGingerCrossfadeResult = {\n /** `true` while a crossfade transition is actively in progress. */\n isCrossfading: boolean;\n /**\n * Progress of the current crossfade from `0` (start) to `1` (complete).\n * Always `0` when idle.\n */\n crossfadeProgress: number;\n};\n\ntype CrossfadeSession = {\n graph: CrossfadeGraph;\n incomingAudio: HTMLAudioElement;\n startedAtIndex: number;\n startTime: number;\n fadeDurationMs: number;\n timeoutId: ReturnType<typeof setTimeout>;\n rafId: number;\n aborted: boolean;\n};\n\n/**\n * Smoothly crossfades between consecutive tracks using the Web Audio API.\n *\n * When the remaining time on the current track falls below `duration`, the hook:\n * 1. Creates a hidden `<audio>` element and begins loading the next track.\n * 2. Routes both the outgoing and incoming elements through `GainNode`s in a\n * shared `AudioContext`.\n * 3. Schedules gain ramps so the outgoing track fades out while the incoming\n * track fades in simultaneously.\n * 4. Dispatches `SET_INDEX` once the ramp completes so the Ginger queue\n * advances to the new track.\n *\n * **Limitations:**\n * - Incompatible with `useGingerEqualizer` and `useGingerLiveAnalyzer` on the\n * same element — the browser only permits one `MediaElementAudioSourceNode`\n * per `<audio>` element.\n * - Requires a prior user gesture before `AudioContext` can be resumed (standard\n * Web Audio policy).\n * - When `repeatMode` is `\"one\"`, the crossfade replays the same track from the\n * beginning, matching the standard `notifyEnded` behaviour.\n *\n * Available as a subpath import:\n * ```ts\n * import { useGingerCrossfade } from \"@lucaismyname/ginger/crossfade\";\n * ```\n */\nexport function useGingerCrossfade(\n options: UseGingerCrossfadeOptions = {},\n): UseGingerCrossfadeResult {\n const { duration = 3, curve = \"equal-power\", crossOrigin, enabled = true } = options;\n\n const { tracks, currentIndex, isPaused, repeatMode, playbackMode, dispatch } =\n useGingerPlayback();\n const { currentTime, duration: trackDuration, audioRef, muted, volume } = useGingerMedia();\n\n const [isCrossfading, setIsCrossfading] = useState(false);\n const [crossfadeProgress, setCrossfadeProgress] = useState(0);\n\n const sessionRef = useRef<CrossfadeSession | null>(null);\n\n const abort = useCallback(() => {\n const session = sessionRef.current;\n if (!session) return;\n session.aborted = true;\n clearTimeout(session.timeoutId);\n cancelAnimationFrame(session.rafId);\n teardownCrossfadeGraph(session.graph);\n session.incomingAudio.pause();\n session.incomingAudio.removeAttribute(\"src\");\n session.incomingAudio.load();\n sessionRef.current = null;\n setIsCrossfading(false);\n setCrossfadeProgress(0);\n }, []);\n\n // Abort if the user pauses or manually advances/changes the track mid-fade.\n useEffect(() => {\n const session = sessionRef.current;\n if (!session) return;\n if (isPaused || currentIndex !== session.startedAtIndex) {\n abort();\n }\n }, [isPaused, currentIndex, abort]);\n\n // Clean up on unmount.\n // biome-ignore lint/correctness/useExhaustiveDependencies: abort is stable; intentional unmount-only cleanup\n useEffect(() => () => abort(), []);\n\n // Keep the incoming element's volume/muted in sync with Ginger state so that\n // the user's volume control applies to the incoming track during the fade.\n useEffect(() => {\n const session = sessionRef.current;\n if (!session) return;\n session.incomingAudio.volume = volume;\n session.incomingAudio.muted = muted;\n }, [volume, muted]);\n\n // Main trigger: start a crossfade when time remaining ≤ duration.\n useEffect(() => {\n if (!enabled) return;\n if (sessionRef.current) return; // already in progress\n if (isPaused) return;\n if (!(trackDuration > 0)) return;\n\n const timeRemaining = trackDuration - currentTime;\n if (timeRemaining > duration || timeRemaining <= 0) return;\n\n // Determine what comes next using the same logic as notifyEnded.\n const transition = computeEndedTransition({ tracks, currentIndex, repeatMode, playbackMode });\n if (transition.kind === \"stop\") return; // queue ends — nothing to crossfade into\n\n const nextIndex = transition.kind === \"replay_same\" ? currentIndex : transition.nextIndex;\n const nextTrack = tracks[nextIndex];\n if (!nextTrack?.fileUrl) return;\n\n const mainEl = audioRef.current;\n if (!mainEl) return;\n\n // Create the incoming element before attaching the graph so that both\n // elements are ready when createMediaElementSource is called.\n const incomingAudio = document.createElement(\"audio\");\n incomingAudio.preload = \"auto\";\n incomingAudio.volume = volume;\n incomingAudio.muted = muted;\n if (crossOrigin) incomingAudio.crossOrigin = crossOrigin;\n incomingAudio.src = nextTrack.fileUrl;\n\n let graph: CrossfadeGraph;\n try {\n graph = attachCrossfadeGraph(mainEl, incomingAudio);\n } catch (e) {\n if (process.env.NODE_ENV !== \"production\") {\n console.warn(\n \"[@lucaismyname/ginger/crossfade] Failed to attach crossfade graph. \" +\n \"This may be because the audio element is already connected to a Web Audio graph \" +\n \"(e.g. via useGingerEqualizer or useGingerLiveAnalyzer). \" +\n \"These features are incompatible with useGingerCrossfade.\",\n e,\n );\n }\n return;\n }\n\n // Browsers suspend AudioContext until a user gesture has occurred.\n void graph.context.resume();\n\n incomingAudio.load();\n void incomingAudio.play().catch(() => {\n // Autoplay may be blocked; the gain ramps continue regardless and the\n // incoming audio will play once the browser permits it.\n });\n\n scheduleCrossfade(graph, timeRemaining, curve);\n\n const startTime = performance.now();\n const fadeDurationMs = timeRemaining * 1000;\n\n setIsCrossfading(true);\n setCrossfadeProgress(0);\n\n // rAF loop: drive crossfadeProgress for consumers (visualisers, UI indicators).\n let rafId = 0;\n const tick = () => {\n const elapsed = performance.now() - startTime;\n const progress = Math.min(1, elapsed / fadeDurationMs);\n setCrossfadeProgress(progress);\n if (progress < 1) {\n rafId = requestAnimationFrame(tick);\n }\n };\n rafId = requestAnimationFrame(tick);\n\n // Once the gain ramps complete, advance the Ginger queue.\n const timeoutId = setTimeout(() => {\n const session = sessionRef.current;\n if (!session || session.aborted) return;\n\n // GingerPlayer will swap the src on the main element and resume playback.\n dispatch({ type: \"SET_INDEX\", payload: { index: nextIndex, autoPlay: true } });\n\n teardownCrossfadeGraph(graph);\n incomingAudio.pause();\n incomingAudio.removeAttribute(\"src\");\n incomingAudio.load();\n\n sessionRef.current = null;\n setIsCrossfading(false);\n setCrossfadeProgress(0);\n }, fadeDurationMs);\n\n sessionRef.current = {\n graph,\n incomingAudio,\n startedAtIndex: currentIndex,\n startTime,\n fadeDurationMs,\n timeoutId,\n rafId,\n aborted: false,\n };\n }, [\n enabled,\n isPaused,\n trackDuration,\n currentTime,\n duration,\n curve,\n crossOrigin,\n tracks,\n currentIndex,\n repeatMode,\n playbackMode,\n audioRef,\n volume,\n muted,\n dispatch,\n ]);\n\n return { isCrossfading, crossfadeProgress };\n}\n"],"names":["EQUAL_POWER_CURVE_LENGTH","buildEqualPowerCurves","outCurve","inCurve","i","t","getAudioContextCtor","attachCrossfadeGraph","outgoing","incoming","Ctor","context","outSource","inSource","outGain","inGain","scheduleCrossfade","graph","durationSec","curve","startTime","endTime","teardownCrossfadeGraph","nodes","node","useGingerCrossfade","options","duration","crossOrigin","enabled","tracks","currentIndex","isPaused","repeatMode","playbackMode","dispatch","useGingerPlayback","currentTime","trackDuration","audioRef","muted","volume","useGingerMedia","isCrossfading","setIsCrossfading","useState","crossfadeProgress","setCrossfadeProgress","sessionRef","useRef","abort","useCallback","session","useEffect","timeRemaining","transition","computeEndedTransition","nextIndex","nextTrack","mainEl","incomingAudio","e","fadeDurationMs","rafId","tick","elapsed","progress","timeoutId"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../src/crossfade/crossfadeGraph.ts","../../src/crossfade/useGingerCrossfade.ts"],"sourcesContent":["/**\n * Web Audio graph management for crossfade transitions.\n *\n * Creates a shared `AudioContext` that routes both the outgoing and incoming\n * `HTMLAudioElement` through individual `GainNode`s into the same destination.\n * Scheduling the gain ramps on both nodes produces the crossfade effect.\n *\n * **Compatibility note:** because the browser only permits one\n * `MediaElementAudioSourceNode` per `HTMLAudioElement`, this module is\n * incompatible with `liveAudioGraph`-based features (`useGingerEqualizer`,\n * `useGingerLiveAnalyzer`) on the same element. Using both simultaneously will\n * throw a `DOMException` when the second source node is requested.\n */\n\nexport type CrossfadeCurve = \"linear\" | \"equal-power\";\n\nexport type CrossfadeGraph = {\n context: AudioContext;\n outGain: GainNode;\n inGain: GainNode;\n outSource: MediaElementAudioSourceNode;\n inSource: MediaElementAudioSourceNode;\n};\n\nconst EQUAL_POWER_CURVE_LENGTH = 256;\n\nfunction buildEqualPowerCurves(): { outCurve: Float32Array; inCurve: Float32Array } {\n const outCurve = new Float32Array(EQUAL_POWER_CURVE_LENGTH);\n const inCurve = new Float32Array(EQUAL_POWER_CURVE_LENGTH);\n for (let i = 0; i < EQUAL_POWER_CURVE_LENGTH; i++) {\n const t = i / (EQUAL_POWER_CURVE_LENGTH - 1);\n outCurve[i] = Math.cos(t * (Math.PI / 2));\n inCurve[i] = Math.sin(t * (Math.PI / 2));\n }\n return { outCurve, inCurve };\n}\n\nfunction getAudioContextCtor(): (new (options?: AudioContextOptions) => AudioContext) | undefined {\n if (typeof window === \"undefined\") return undefined;\n return (\n window.AudioContext ??\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext\n );\n}\n\n/**\n * Creates a shared `AudioContext` and connects both the outgoing and incoming\n * audio elements to it via individual `GainNode`s.\n *\n * The outgoing gain starts at 1, the incoming gain starts at 0.\n * Call `scheduleCrossfade` immediately after to begin the ramps.\n *\n * @throws `DOMException` if either element already has a `MediaElementAudioSourceNode`\n * in another context (e.g. created by `liveAudioGraph`).\n * @throws `Error` if the Web Audio API is unavailable in this environment.\n */\nexport function attachCrossfadeGraph(\n outgoing: HTMLAudioElement,\n incoming: HTMLAudioElement,\n): CrossfadeGraph {\n const Ctor = getAudioContextCtor();\n if (!Ctor) {\n throw new Error(\n \"[@lucaismyname/ginger/crossfade] Web Audio API is not available in this environment.\",\n );\n }\n\n const context = new Ctor();\n\n const outSource = context.createMediaElementSource(outgoing);\n const inSource = context.createMediaElementSource(incoming);\n\n const outGain = context.createGain();\n const inGain = context.createGain();\n\n outGain.gain.value = 1;\n inGain.gain.value = 0;\n\n outSource.connect(outGain);\n outGain.connect(context.destination);\n\n inSource.connect(inGain);\n inGain.connect(context.destination);\n\n return { context, outGain, inGain, outSource, inSource };\n}\n\n/**\n * Schedules gain ramps on both gain nodes so that `outGain` fades from 1 → 0\n * and `inGain` fades from 0 → 1 over `durationSec` seconds starting immediately.\n *\n * For `\"equal-power\"`, a cosine/sine curve is applied via `setValueCurveAtTime`\n * to maintain consistent perceived loudness throughout the transition.\n */\nexport function scheduleCrossfade(\n graph: CrossfadeGraph,\n durationSec: number,\n curve: CrossfadeCurve,\n): void {\n const { context, outGain, inGain } = graph;\n const startTime = context.currentTime;\n const endTime = startTime + durationSec;\n\n if (curve === \"equal-power\") {\n const { outCurve, inCurve } = buildEqualPowerCurves();\n outGain.gain.setValueCurveAtTime(outCurve, startTime, durationSec);\n inGain.gain.setValueCurveAtTime(inCurve, startTime, durationSec);\n } else {\n outGain.gain.setValueAtTime(1, startTime);\n outGain.gain.linearRampToValueAtTime(0, endTime);\n inGain.gain.setValueAtTime(0, startTime);\n inGain.gain.linearRampToValueAtTime(1, endTime);\n }\n}\n\n/**\n * Disconnects all nodes and closes the `AudioContext`.\n * Safe to call multiple times; errors during disconnect are silently ignored.\n */\nexport function teardownCrossfadeGraph(graph: CrossfadeGraph): void {\n const nodes: AudioNode[] = [graph.outSource, graph.inSource, graph.outGain, graph.inGain];\n for (const node of nodes) {\n try {\n node.disconnect();\n } catch {\n // ignore\n }\n }\n void graph.context.close();\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useGingerMedia, useGingerPlayback } from \"../context/GingerSplitContexts\";\nimport { computeEndedTransition } from \"../core/transitions\";\nimport {\n type CrossfadeCurve,\n type CrossfadeGraph,\n attachCrossfadeGraph,\n scheduleCrossfade,\n teardownCrossfadeGraph,\n} from \"./crossfadeGraph\";\n\nexport type { CrossfadeCurve };\n\nexport type UseGingerCrossfadeOptions = {\n /**\n * Duration of the crossfade in seconds.\n * The hook begins the fade when `timeRemaining ≤ duration`.\n * @default 3\n */\n duration?: number;\n /**\n * Gain curve shape applied to both gain nodes.\n * `\"equal-power\"` uses a cosine/sine curve to maintain consistent perceived\n * loudness; `\"linear\"` is a straight ramp that may dip slightly at the midpoint.\n * @default \"equal-power\"\n */\n curve?: CrossfadeCurve;\n /**\n * `crossOrigin` attribute for the incoming `<audio>` element.\n * Match this to the `crossOrigin` prop on `Ginger.Player` when serving\n * cross-origin audio so the browser can reuse the cached resource.\n */\n crossOrigin?: \"\" | \"anonymous\" | \"use-credentials\";\n /**\n * When `false`, crossfade is completely disabled and playback falls back to\n * the default hard-cut transition.\n * @default true\n */\n enabled?: boolean;\n};\n\nexport type UseGingerCrossfadeResult = {\n /** `true` while a crossfade transition is actively in progress. */\n isCrossfading: boolean;\n /**\n * Progress of the current crossfade from `0` (start) to `1` (complete).\n * Always `0` when idle.\n */\n crossfadeProgress: number;\n};\n\ntype CrossfadeSession = {\n graph: CrossfadeGraph;\n incomingAudio: HTMLAudioElement;\n startedAtIndex: number;\n startTime: number;\n fadeDurationMs: number;\n timeoutId: ReturnType<typeof setTimeout>;\n rafId: number;\n aborted: boolean;\n};\n\n/**\n * Smoothly crossfades between consecutive tracks using the Web Audio API.\n *\n * When the remaining time on the current track falls below `duration`, the hook:\n * 1. Creates a hidden `<audio>` element and begins loading the next track.\n * 2. Routes both the outgoing and incoming elements through `GainNode`s in a\n * shared `AudioContext`.\n * 3. Schedules gain ramps so the outgoing track fades out while the incoming\n * track fades in simultaneously.\n * 4. Dispatches `SET_INDEX` once the ramp completes so the Ginger queue\n * advances to the new track.\n *\n * **Limitations:**\n * - Incompatible with `useGingerEqualizer` and `useGingerLiveAnalyzer` on the\n * same element — the browser only permits one `MediaElementAudioSourceNode`\n * per `<audio>` element.\n * - Requires a prior user gesture before `AudioContext` can be resumed (standard\n * Web Audio policy).\n * - When `repeatMode` is `\"one\"`, the crossfade replays the same track from the\n * beginning, matching the standard `notifyEnded` behaviour.\n *\n * Available as a subpath import:\n * ```ts\n * import { useGingerCrossfade } from \"@lucaismyname/ginger/crossfade\";\n * ```\n */\nexport function useGingerCrossfade(\n options: UseGingerCrossfadeOptions = {},\n): UseGingerCrossfadeResult {\n const { duration = 3, curve = \"equal-power\", crossOrigin, enabled = true } = options;\n\n const { tracks, currentIndex, isPaused, repeatMode, playbackMode, dispatch } =\n useGingerPlayback();\n const { currentTime, duration: trackDuration, audioRef, muted, volume } = useGingerMedia();\n\n const [isCrossfading, setIsCrossfading] = useState(false);\n const [crossfadeProgress, setCrossfadeProgress] = useState(0);\n\n const sessionRef = useRef<CrossfadeSession | null>(null);\n\n const abort = useCallback(() => {\n const session = sessionRef.current;\n if (!session) return;\n session.aborted = true;\n clearTimeout(session.timeoutId);\n cancelAnimationFrame(session.rafId);\n teardownCrossfadeGraph(session.graph);\n session.incomingAudio.pause();\n session.incomingAudio.removeAttribute(\"src\");\n session.incomingAudio.load();\n sessionRef.current = null;\n setIsCrossfading(false);\n setCrossfadeProgress(0);\n }, []);\n\n // Abort if the user pauses or manually advances/changes the track mid-fade.\n useEffect(() => {\n const session = sessionRef.current;\n if (!session) return;\n if (isPaused || currentIndex !== session.startedAtIndex) {\n abort();\n }\n }, [isPaused, currentIndex, abort]);\n\n // Clean up on unmount.\n // biome-ignore lint/correctness/useExhaustiveDependencies: abort is stable; intentional unmount-only cleanup\n useEffect(() => () => abort(), []);\n\n // Keep the incoming element's volume/muted in sync with Ginger state so that\n // the user's volume control applies to the incoming track during the fade.\n useEffect(() => {\n const session = sessionRef.current;\n if (!session) return;\n session.incomingAudio.volume = volume;\n session.incomingAudio.muted = muted;\n }, [volume, muted]);\n\n // Refs for polling-based crossfade trigger (avoids re-running the effect on every time tick).\n const mediaRef = useRef({ currentTime: 0, trackDuration: 0 });\n mediaRef.current = { currentTime, trackDuration };\n\n const crossfadeConfigRef = useRef({\n tracks,\n currentIndex,\n repeatMode,\n playbackMode,\n volume,\n muted,\n });\n crossfadeConfigRef.current = { tracks, currentIndex, repeatMode, playbackMode, volume, muted };\n\n // Main trigger: poll at a reasonable interval to detect when crossfade should start.\n useEffect(() => {\n if (!enabled || isPaused || typeof window === \"undefined\") return;\n\n const POLL_INTERVAL_MS = 250;\n let pollId: ReturnType<typeof setInterval> | null = null;\n\n const tryStartCrossfade = () => {\n if (sessionRef.current) return;\n\n const { currentTime: ct, trackDuration: td } = mediaRef.current;\n if (!(td > 0)) return;\n\n const timeRemaining = td - ct;\n if (timeRemaining > duration || timeRemaining <= 0) return;\n\n const {\n tracks: tr,\n currentIndex: ci,\n repeatMode: rm,\n playbackMode: pm,\n volume: v,\n muted: m,\n } = crossfadeConfigRef.current;\n\n const transition = computeEndedTransition({\n tracks: tr,\n currentIndex: ci,\n repeatMode: rm,\n playbackMode: pm,\n });\n if (transition.kind === \"stop\") return;\n\n const nextIndex = transition.kind === \"replay_same\" ? ci : transition.nextIndex;\n const nextTrack = tr[nextIndex];\n if (!nextTrack?.fileUrl) return;\n\n const mainEl = audioRef.current;\n if (!mainEl) return;\n\n const incomingAudio = document.createElement(\"audio\");\n incomingAudio.preload = \"auto\";\n incomingAudio.volume = v;\n incomingAudio.muted = m;\n if (crossOrigin) incomingAudio.crossOrigin = crossOrigin;\n incomingAudio.src = nextTrack.fileUrl;\n\n let graph: CrossfadeGraph;\n try {\n graph = attachCrossfadeGraph(mainEl, incomingAudio);\n } catch (e) {\n if (process.env.NODE_ENV !== \"production\") {\n console.warn(\n \"[@lucaismyname/ginger/crossfade] Failed to attach crossfade graph. \" +\n \"This may be because the audio element is already connected to a Web Audio graph \" +\n \"(e.g. via useGingerEqualizer or useGingerLiveAnalyzer). \" +\n \"These features are incompatible with useGingerCrossfade.\",\n e,\n );\n }\n return;\n }\n\n void graph.context.resume();\n\n incomingAudio.load();\n void incomingAudio.play().catch(() => {\n // Autoplay may be blocked; gain ramps continue regardless.\n });\n\n scheduleCrossfade(graph, timeRemaining, curve);\n\n const startTime = performance.now();\n const fadeDurationMs = timeRemaining * 1000;\n\n setIsCrossfading(true);\n setCrossfadeProgress(0);\n\n let rafId = 0;\n const tick = () => {\n const elapsed = performance.now() - startTime;\n const progress = Math.min(1, elapsed / fadeDurationMs);\n setCrossfadeProgress(progress);\n if (progress < 1) {\n rafId = requestAnimationFrame(tick);\n }\n };\n rafId = requestAnimationFrame(tick);\n\n const timeoutId = setTimeout(() => {\n const session = sessionRef.current;\n if (!session || session.aborted) return;\n\n dispatch({ type: \"SET_INDEX\", payload: { index: nextIndex, autoPlay: true } });\n\n teardownCrossfadeGraph(graph);\n incomingAudio.pause();\n incomingAudio.removeAttribute(\"src\");\n incomingAudio.load();\n\n sessionRef.current = null;\n setIsCrossfading(false);\n setCrossfadeProgress(0);\n }, fadeDurationMs);\n\n sessionRef.current = {\n graph,\n incomingAudio,\n startedAtIndex: ci,\n startTime,\n fadeDurationMs,\n timeoutId,\n rafId,\n aborted: false,\n };\n\n if (pollId != null) {\n clearInterval(pollId);\n pollId = null;\n }\n };\n\n pollId = setInterval(tryStartCrossfade, POLL_INTERVAL_MS);\n tryStartCrossfade();\n\n return () => {\n if (pollId != null) clearInterval(pollId);\n };\n }, [enabled, isPaused, duration, curve, crossOrigin, audioRef, dispatch]);\n\n return { isCrossfading, crossfadeProgress };\n}\n"],"names":["EQUAL_POWER_CURVE_LENGTH","buildEqualPowerCurves","outCurve","inCurve","i","getAudioContextCtor","attachCrossfadeGraph","outgoing","incoming","Ctor","context","outSource","inSource","outGain","inGain","scheduleCrossfade","graph","durationSec","curve","startTime","endTime","teardownCrossfadeGraph","nodes","node","useGingerCrossfade","options","duration","crossOrigin","enabled","tracks","currentIndex","isPaused","repeatMode","playbackMode","dispatch","useGingerPlayback","currentTime","trackDuration","audioRef","muted","volume","useGingerMedia","isCrossfading","setIsCrossfading","useState","crossfadeProgress","setCrossfadeProgress","sessionRef","useRef","abort","useCallback","session","useEffect","mediaRef","crossfadeConfigRef","POLL_INTERVAL_MS","pollId","tryStartCrossfade","ct","td","timeRemaining","tr","ci","rm","pm","v","m","transition","computeEndedTransition","nextIndex","nextTrack","mainEl","incomingAudio","e","fadeDurationMs","rafId","tick","elapsed","progress","timeoutId"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useGingerCrossfade.d.ts","sourceRoot":"","sources":["../../src/crossfade/useGingerCrossfade.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,cAAc,EAKpB,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EAAE,cAAc,EAAE,CAAC;AAE/B,MAAM,MAAM,yBAAyB,GAAG;IACtC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB;;;;OAIG;IACH,WAAW,CAAC,EAAE,EAAE,GAAG,WAAW,GAAG,iBAAiB,CAAC;IACnD;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,mEAAmE;IACnE,aAAa,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAAC;AAaF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,yBAA8B,GACtC,wBAAwB,
|
|
1
|
+
{"version":3,"file":"useGingerCrossfade.d.ts","sourceRoot":"","sources":["../../src/crossfade/useGingerCrossfade.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,cAAc,EAKpB,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EAAE,cAAc,EAAE,CAAC;AAE/B,MAAM,MAAM,yBAAyB,GAAG;IACtC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB;;;;OAIG;IACH,WAAW,CAAC,EAAE,EAAE,GAAG,WAAW,GAAG,iBAAiB,CAAC;IACnD;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,mEAAmE;IACnE,aAAa,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAAC;AAaF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,yBAA8B,GACtC,wBAAwB,CAkM1B"}
|
package/dist/equalizer/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react"),a=require("../liveAudioGraph-0cpHD_Ic.cjs"),G=require("../useGinger-
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react"),a=require("../liveAudioGraph-0cpHD_Ic.cjs"),G=require("../useGinger-BMRLzjmr.cjs"),R=[{frequency:60},{frequency:250},{frequency:1e3},{frequency:4e3},{frequency:16e3}];function b(p={}){const{enabled:y=!0,bands:h=R}=p,{audioRef:l,state:v}=G.useGinger(),[s,g]=t.useState(h),[S,q]=t.useState(null),c=t.useRef([]),m=t.useRef(s);m.current=s;const B=t.useMemo(()=>s.map(e=>`${e.frequency}:${e.type??"peaking"}:${e.q??1}`).join("|"),[s]);t.useEffect(()=>{const e=l.current;if(!(!e||typeof window>"u")){if(!y){a.setProcessingChain(e,[]),c.current=[];return}try{const n=a.attachLiveAnalyser(e,{fftSize:32,smoothingTimeConstant:0,minDecibels:-100,maxDecibels:0}),{context:r,id:f}=n,i=m.current.map(o=>{const u=r.createBiquadFilter();return u.type=o.type??"peaking",u.frequency.value=o.frequency,u.gain.value=o.gain??0,u.Q.value=o.q??1,u});c.current=i,a.setProcessingChain(e,i),a.detachLiveAnalyser(e,f),q(null)}catch(n){const r=n instanceof Error?n.message:"Failed to create equalizer";q(r),c.current=[]}return()=>{const n=l.current;n&&a.setProcessingChain(n,[]),c.current=[]}}},[y,B,l,v.currentIndex]);const C=t.useCallback((e,n)=>{const r=c.current[e];r&&(r.gain.value=n),g(f=>f.map((d,i)=>i===e?{...d,gain:n}:d))},[]),E=t.useCallback(e=>{g(e)},[]);return{setBandGain:C,setBands:E,bands:s,error:S}}exports.useGingerEqualizer=b;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/equalizer/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState as q, useRef as g, useMemo as R, useEffect as k, useCallback as h } from "react";
|
|
2
2
|
import { s as l, a as x, d as z } from "../liveAudioGraph-DvPaxBCP.js";
|
|
3
|
-
import { u as C } from "../useGinger-
|
|
3
|
+
import { u as C } from "../useGinger-DKrHZ4NU.js";
|
|
4
4
|
const D = [
|
|
5
5
|
{ frequency: 60 },
|
|
6
6
|
{ frequency: 250 },
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const i=require("react"),r=require("../GingerSplitContexts-
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const i=require("react"),r=require("../GingerSplitContexts-KSB0vQ5F.cjs"),s={crossOrigin:"anonymous",format:"Full-file fetch + decode is the portable path; Media Source Extensions help only when you control containerized segments. Prefer HTTP(S) or blob: URLs; cross-origin media needs CORS and matching crossOrigin on <audio>. Codec support (MP3, AAC/MP4, Opus, …) is browser-specific."};function a(e){return!!(e&&typeof e.prototype.decodeAudioData=="function")}function n(){if(typeof globalThis>"u")return{supported:!1,reason:"No global object (probe unsupported in this runtime).",hints:s};const e=globalThis;if(typeof e.window>"u")return{supported:!1,reason:"No window (SSR or non-browser). Run the probe in the browser if you need live results.",hints:s};if(!(typeof e.isSecureContext=="boolean"?e.isSecureContext:e.window.isSecureContext!==!1))return{supported:!1,reason:"A secure context (HTTPS or localhost) is required for Web Audio in most browsers. Gapless decode path would not be available here.",hints:s};const o=e.AudioContext??e.webkitAudioContext;return!o||!a(o)?{supported:!1,reason:"Web Audio API is unavailable or decodeAudioData is missing. A gapless decode path cannot run in this environment.",hints:s}:{supported:!0,reason:`Browser exposes Web Audio decode primitives (${typeof e.MediaSource<"u"?"MediaSource is available for segment-based strategies; Ginger still uses a single <audio> for playback today.":"MediaSource is not exposed; only full-buffer decode (or progressive chunking in user code) applies."}). Ginger does not yet schedule gapless transitions — keep using Ginger.Player and existing queue actions.`,hints:s}}function u(){const{tracks:e}=r.useGingerPlayback();return i.useMemo(()=>({...n(),gingerGaplessPlayback:!1,preloadedTrackIds:e.map(o=>o.id??o.fileUrl)}),[e])}exports.probeGaplessCapability=n;exports.useExperimentalGapless=u;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useMemo as r } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { c as i } from "../GingerSplitContexts-DQ3rESBu.js";
|
|
3
3
|
const s = {
|
|
4
4
|
crossOrigin: "anonymous",
|
|
5
5
|
format: "Full-file fetch + decode is the portable path; Media Source Extensions help only when you control containerized segments. Prefer HTTP(S) or blob: URLs; cross-origin media needs CORS and matching crossOrigin on <audio>. Codec support (MP3, AAC/MP4, Opus, …) is browser-specific."
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";const o=require("react/jsx-runtime"),l=require("react"),b=require("./GingerSplitContexts-KSB0vQ5F.cjs"),R=require("./selectors-CEGlYoFu.cjs"),S=require("./transitions-Dx08t68T.cjs"),ur=l.createContext(null);function tt(){const e=l.useContext(ur);if(!e)throw new Error("Ginger components must be used within <Ginger.Provider>");return e}function sr(e){const{buffered:r,duration:n}=e;return!(n>0)||r.length===0?0:Math.min(1,r.end(r.length-1)/n)}function nt({className:e,style:r,preload:n="metadata",crossOrigin:a,respectReducedMotion:t=!1}){var I;const{audioRef:i,dispatch:s,state:u,notifyEnded:d}=tt(),p=((I=u.tracks[u.currentIndex])==null?void 0:I.fileUrl)??"",m=l.useRef({currentTime:-1,duration:-1,bufferedFraction:-1}),y=l.useRef(""),[M,f]=l.useState(!1);l.useEffect(()=>{if(!t||typeof window>"u")return;const x=window.matchMedia("(prefers-reduced-motion: reduce)"),k=()=>f(x.matches);return k(),x.addEventListener("change",k),()=>x.removeEventListener("change",k)},[t]);const h=(x,k=!1)=>{const C={currentTime:x.currentTime,duration:x.duration,bufferedFraction:sr(x)},A=m.current,ne=M?.5:.25,ae=Math.abs(C.currentTime-A.currentTime)>=ne||Math.abs(C.duration-A.duration)>=.01||Math.abs(C.bufferedFraction-A.bufferedFraction)>=.01;!k&&!ae||(m.current=C,s({type:"MEDIA_TIME_UPDATE",payload:C}))};return l.useEffect(()=>{const x=i.current;x&&(x.volume=u.volume,x.muted=u.muted)},[i,u.volume,u.muted]),l.useEffect(()=>{const x=i.current;if(x){if(!p){x.removeAttribute("src"),m.current={currentTime:-1,duration:-1,bufferedFraction:-1},y.current!==""&&s({type:"MEDIA_SOURCE_CLEARED"}),y.current="";return}x.getAttribute("src")!==p&&(x.src=p,x.load(),m.current={currentTime:-1,duration:-1,bufferedFraction:-1}),y.current=p,x.playbackRate=u.playbackRate}},[i,s,p,u.playbackRate]),o.jsx("audio",{"data-ginger-component":"Player",ref:i,className:e,style:r,preload:n,crossOrigin:a,controls:!1,playsInline:!0,onTimeUpdate:x=>{h(x.currentTarget)},onLoadedMetadata:x=>{const k=x.currentTarget;m.current={currentTime:-1,duration:-1,bufferedFraction:-1},s({type:"MEDIA_LOADED_METADATA",payload:{duration:k.duration,bufferedFraction:sr(k)}})},onSeeking:x=>h(x.currentTarget,!0),onSeeked:x=>h(x.currentTarget,!0),onEnded:()=>d(),onPlay:()=>s({type:"MEDIA_PLAY"}),onPause:()=>s({type:"MEDIA_PAUSE"}),onWaiting:()=>s({type:"MEDIA_WAITING"}),onCanPlay:()=>s({type:"MEDIA_CANPLAY"}),onProgress:x=>h(x.currentTarget,!0),onVolumeChange:x=>{const k=x.currentTarget;s({type:"MEDIA_VOLUME_SYNC",payload:{volume:k.volume,muted:k.muted}})},onError:()=>{var A;const x=i.current,k=(A=x==null?void 0:x.error)==null?void 0:A.code;s({type:"MEDIA_ERROR",payload:{message:k===1?"MEDIA_ERR_ABORTED":k===2?"MEDIA_ERR_NETWORK":k===3?"MEDIA_ERR_DECODE":k===4?"MEDIA_ERR_SRC_NOT_SUPPORTED":"MEDIA_ERR_UNKNOWN"}})}})}const ee={seek:"Seek",volume:"Volume",playbackSpeed:"Playback speed",chaptersList:"Chapters",syncedLyricsList:"Synced lyrics",nextTrack:"Next track",previousTrack:"Previous track",shuffle:"Shuffle",mute:"Mute",unmute:"Unmute",play:"Play",pause:"Pause",repeat:{off:"Repeat off",all:"Repeat all",one:"Repeat one"},playbackRateNormal:"1× normal",playbackRateTimes:e=>`${e}×`};function at(e){return e?{...ee,...e,repeat:{...ee.repeat,...e.repeat}}:ee}const lr=l.createContext(ee);function it({locale:e,children:r}){const n=at(e);return o.jsx(lr.Provider,{value:n,children:r})}function w(){return l.useContext(lr)}function re(e){if(!Number.isFinite(e)||e<0)return"0:00";const r=Math.floor(e),n=r%60,a=Math.floor(r/60),t=a%60,i=Math.floor(r/3600);return i>0?`${i}:${t.toString().padStart(2,"0")}:${n.toString().padStart(2,"0")}`:`${a}:${n.toString().padStart(2,"0")}`}function dr(){const e=b.useGingerPlayback(),r=b.useGingerMedia(),n=w(),a=l.useMemo(()=>b.gingerStateFromContextValues(e,r),[e,r]),t=R.effectiveDuration(a),i=t>0?a.currentTime:0,s=Number.isFinite(i)?i:0,u=t>0?`${re(s)} of ${re(t)}`:re(s),d=p=>{r.seek(Number(p.currentTarget.value))};return{state:a,value:s,min:0,max:t>0?t:1,step:"any",ariaValueText:u,ariaLabel:n.seek,onSeekInput:d,onSeekChange:d}}function gr(){const e=b.useGingerPlayback(),r=b.useGingerMedia(),n=w(),a=l.useMemo(()=>b.gingerStateFromContextValues(e,r),[e,r]),t=i=>{r.setVolume(Number(i.currentTarget.value))};return{state:a,value:a.volume,min:0,max:1,step:"any",ariaValueText:`${Math.round(a.volume*100)}%`,ariaLabel:n.volume,onVolumeInput:t,onVolumeChange:t}}function pr(e){const r=b.useGingerPlayback(),n=w(),a=(e==null?void 0:e.playAriaLabel)??n.play,t=(e==null?void 0:e.pauseAriaLabel)??n.pause;return{isPaused:r.isPaused,toggle:r.togglePlayPause,ariaLabel:r.isPaused?a:t}}function N(e){const{className:r,width:n,height:a,viewBox:t,fill:i,stroke:s,strokeWidth:u,strokeLinecap:d,strokeLinejoin:p,children:m,...y}=e;return o.jsx("svg",{"data-ginger-component":"Wrapper",xmlns:"http://www.w3.org/2000/svg",width:n??24,height:a??24,viewBox:t??"0 0 24 24",fill:"none",stroke:s??"currentColor",strokeWidth:u??2,strokeLinecap:d??"round",strokeLinejoin:p??"round",className:r,"aria-hidden":!0,role:"presentation",...y,children:m})}function De(){return o.jsx(N,{children:o.jsx("path",{"data-ginger-component":"Pause",d:"M6 4h4v16H6zM14 4h4v16h-4z"})})}function Fe(){return o.jsx(N,{children:o.jsx("path",{"data-ginger-component":"Play",d:"M5 3L19 12 5 21 5 3z"})})}function Le(){return o.jsxs(o.Fragment,{children:[o.jsx("path",{d:"m17 2 4 4-4 4"}),o.jsx("path",{d:"M3 11v-1a4 4 0 0 1 4-4h14"}),o.jsx("path",{d:"m7 22-4-4 4-4"}),o.jsx("path",{d:"M21 13v1a4 4 0 0 1-4 4H3"})]})}function Ue({mode:e}){return e==="one"?o.jsxs(N,{children:[o.jsx(Le,{}),o.jsx("path",{"data-ginger-component":"RepeatGlyph",d:"M11 10h1v4"})]}):e==="all"?o.jsx(N,{children:o.jsx(Le,{})}):o.jsxs(N,{children:[o.jsx(Le,{}),o.jsx("line",{"data-ginger-component":"RepeatGlyph",x1:"3",x2:"21",y1:"3",y2:"21"})]})}function Be(){return o.jsxs(N,{children:[o.jsx("path",{d:"m18 14 4 4-4 4"}),o.jsx("path",{d:"m18 2 4 4-4 4"}),o.jsx("path",{"data-ginger-component":"ShuffleIcon",d:"M2 18h1.973a4 4 0 0 0 3.3-1.7l5.454-8.6a4 4 0 0 1 3.3-1.7H22"}),o.jsx("path",{d:"M2 6h1.972a4 4 0 0 1 3.6 2.2"}),o.jsx("path",{d:"M22 18h-6.041a4 4 0 0 1-3.3-1.8l-.359-.45"})]})}function Ve(){return o.jsxs(N,{children:[o.jsx("path",{"data-ginger-component":"SkipBack",d:"M17.971 4.285A2 2 0 0 1 21 6v12a2 2 0 0 1-3.029 1.715l-9.997-5.998a2 2 0 0 1-.003-3.432z"}),o.jsx("path",{d:"M3 20V4"})]})}function Oe(){return o.jsxs(N,{children:[o.jsx("path",{d:"M21 4v16"}),o.jsx("path",{"data-ginger-component":"SkipForward",d:"M6.029 4.285A2 2 0 0 0 3 6v12a2 2 0 0 0 3.029 1.715l9.997-5.998a2 2 0 0 0 .003-3.432z"})]})}function $e(){return o.jsxs(N,{children:[o.jsx("path",{"data-ginger-component":"Volume2",d:"M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"}),o.jsx("path",{d:"M16 9a5 5 0 0 1 0 6"}),o.jsx("path",{d:"M19.364 18.364a9 9 0 0 0 0-12.728"})]})}function Ye(){return o.jsxs(N,{children:[o.jsx("path",{"data-ginger-component":"VolumeX",d:"M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"}),o.jsx("line",{x1:"22",x2:"16",y1:"9",y2:"15"}),o.jsx("line",{x1:"16",x2:"22",y1:"9",y2:"15"})]})}function fr({playLabel:e="Play",pauseLabel:r="Pause",playAriaLabel:n,pauseAriaLabel:a,children:t,type:i="button",onClick:s,...u}){const d=w(),p=typeof e=="string"?e:d.play,m=typeof r=="string"?r:d.pause,y=pr({playAriaLabel:n??p,pauseAriaLabel:a??m});return o.jsx("button",{"data-ginger-component":"PlayPause",...u,type:i,"aria-label":y.ariaLabel,onClick:M=>{y.toggle(),s==null||s(M)},children:t??(y.isPaused?o.jsx(Fe,{}):o.jsx(De,{}))})}fr.displayName="Ginger.Control.PlayPause";function mr({type:e="button",ariaLabel:r,onClick:n,children:a,...t}){const{repeatMode:i,cycleRepeat:s}=b.useGingerPlayback(),d=w().repeat[i];return o.jsx("button",{"data-ginger-component":"Repeat",...t,type:e,"aria-label":r??d,onClick:p=>{s(),n==null||n(p)},children:a??o.jsx(Ue,{mode:i})})}mr.displayName="Ginger.Control.Repeat";function yr({type:e="button",children:r,ariaLabel:n,onClick:a,...t}){const{next:i}=b.useGingerPlayback(),s=w();return o.jsx("button",{"data-ginger-component":"Next",...t,type:e,"aria-label":n??s.nextTrack,onClick:u=>{i(),a==null||a(u)},children:r??o.jsx(Oe,{})})}yr.displayName="Ginger.Control.Next";function xr({type:e="button",children:r,ariaLabel:n,onClick:a,...t}){const{prev:i}=b.useGingerPlayback(),s=w();return o.jsx("button",{"data-ginger-component":"Previous",...t,type:e,"aria-label":n??s.previousTrack,onClick:u=>{i(),a==null||a(u)},children:r??o.jsx(Ve,{})})}xr.displayName="Ginger.Control.Previous";function hr({type:e="button",children:r,ariaLabel:n,onClick:a,...t}){const{isShuffled:i,toggleShuffle:s}=b.useGingerPlayback(),u=w();return o.jsx("button",{"data-ginger-component":"Shuffle",...t,type:e,"aria-pressed":i,"aria-label":n??u.shuffle,onClick:d=>{s(),a==null||a(d)},children:r??o.jsx(Be,{})})}hr.displayName="Ginger.Control.Shuffle";function kr({inputStyle:e,style:r,unstyled:n=!1,ariaLabel:a,...t}){const i=dr(),s=n?{...r,...e}:{width:"100%",...r,...e};return o.jsx("input",{"data-ginger-component":"SeekBar",...t,type:"range",min:i.min,max:i.max,step:i.step,value:i.value,"aria-label":a??i.ariaLabel,"aria-valuetext":i.ariaValueText,onInput:i.onSeekInput,onChange:i.onSeekChange,style:s})}kr.displayName="Ginger.Control.SeekBar";function br({inputStyle:e,style:r,unstyled:n=!1,ariaLabel:a,...t}){const i=gr(),s=n?{...r,...e}:{width:"100%",...r,...e};return o.jsx("input",{"data-ginger-component":"Volume",...t,type:"range",min:i.min,max:i.max,step:i.step,value:i.value,"aria-label":a??i.ariaLabel,"aria-valuetext":i.ariaValueText,onInput:i.onVolumeInput,onChange:i.onVolumeChange,style:s})}br.displayName="Ginger.Control.Volume";function vr({ariaLabel:e,muteLabel:r,unmuteLabel:n,type:a="button",onClick:t,children:i,...s}){const{muted:u,toggleMute:d}=b.useGingerMedia(),p=w();return o.jsx("button",{"data-ginger-component":"Mute",...s,type:a,"aria-pressed":u,"aria-label":e??(u?p.unmute:p.mute),onClick:m=>{d(),t==null||t(m)},children:i??(u?n??o.jsx(Ye,{}):r??o.jsx($e,{}))})}vr.displayName="Ginger.Control.Mute";const ot=[.5,.75,1,1.25,1.5,2];function Tr({rates:e=ot,style:r,ariaLabel:n,children:a,...t}){const{playbackRate:i,setPlaybackRate:s}=b.useGingerMedia(),u=w(),d=l.useMemo(()=>Array.from(new Set([...e,i])).sort((p,m)=>p-m),[e,i]);return o.jsx("select",{"data-ginger-component":"PlaybackRate",...t,"aria-label":n??u.playbackSpeed,value:String(i),style:r,onChange:p=>s(Number(p.currentTarget.value)),children:a??d.map(p=>o.jsx("option",{value:String(p),children:p===1?u.playbackRateNormal:u.playbackRateTimes(p)},p))})}Tr.displayName="Ginger.Control.PlaybackRate";function $(e,r){function n(a){const t=b.useGingerState(),s=(r(t)??"").trim(),{className:u,style:d,fallback:p,empty:m,children:y}=a;if(!s){const M=m??p??null;return M?o.jsx("span",{className:u,style:d,children:M}):null}return y?o.jsx("span",{className:u,style:d,children:y(s,t)}):o.jsx("span",{className:u,style:d,children:s})}return n.displayName=e,n}function Y(e,r){return $(e,n=>r(R.getCurrentTrack(n)))}const st=Y("Ginger.Current.Title",e=>e==null?void 0:e.title),ct=Y("Ginger.Current.Artist",e=>e==null?void 0:e.artist),ut=$("Ginger.Current.Album",e=>R.resolvedAlbumLine(e)),lt=Y("Ginger.Current.Description",e=>e==null?void 0:e.description),dt=$("Ginger.Current.Copyright",e=>{var n;const r=R.getCurrentTrack(e);return(r==null?void 0:r.copyright)??((n=e.playlistMeta)==null?void 0:n.copyright)}),gt=Y("Ginger.Current.Genre",e=>e==null?void 0:e.genre),pt=Y("Ginger.Current.Label",e=>e==null?void 0:e.label),ft=Y("Ginger.Current.Isrc",e=>e==null?void 0:e.isrc),mt=Y("Ginger.Current.TrackNumber",e=>(e==null?void 0:e.trackNumber)!=null?String(e.trackNumber):void 0);function Mr({className:e,style:r,fallback:n,empty:a,children:t,format:i}){var p;const s=b.useGingerState(),u=(p=R.getCurrentTrack(s))==null?void 0:p.year;if(typeof u!="number"||!Number.isFinite(u)){const m=a??n??null;return m?o.jsx("span",{"data-ginger-component":"Year",className:e,style:r,children:m}):null}const d=i?i(u):String(u);return t?o.jsx("span",{"data-ginger-component":"Year",className:e,style:r,children:t(d,s)}):o.jsx("span",{"data-ginger-component":"Year",className:e,style:r,children:d})}Mr.displayName="Ginger.Current.Year";function Er({className:e,style:r,fallback:n,empty:a,children:t,preserveWhitespace:i=!0,unstyled:s=!1}){var y;const u=b.useGingerState(),d=((y=R.getCurrentTrack(u))==null?void 0:y.lyrics)??"",p=i?d.replace(/^\s+|\s+$/g,""):d.trim();if(!p){const M=a??n??null;return M?o.jsx("span",{"data-ginger-component":"Lyrics",className:e,style:r,children:M}):null}const m=!s&&i?{whiteSpace:"pre-wrap"}:void 0;return t?o.jsx("span",{className:e,style:{...m,...r},children:t(p,u)}):o.jsx("span",{className:e,style:{...m,...r},children:p})}Er.displayName="Ginger.Current.Lyrics";const cr=/\[(\d{1,2}):(\d{2})(?:\.(\d{1,3}))?\]/g;function Ir(e){const r=[];for(const n of e.split(/\r?\n/)){const a=[...n.matchAll(cr)];if(a.length===0)continue;const t=n.replace(cr,"").trim();for(const i of a){const s=Number(i[1]??0),u=Number(i[2]??0),d=Number((i[3]??"0").padEnd(3,"0")),p=s*60+u+d/1e3;Number.isFinite(p)&&p>=0&&r.push({time:p,text:t})}}return r.sort((n,a)=>n.time-a.time)}function Sr(){const{tracks:e,currentIndex:r}=b.useGingerPlayback(),{currentTime:n}=b.useGingerMedia(),a=e[r],t=l.useMemo(()=>a?Array.isArray(a.lyricsTimed)&&a.lyricsTimed.length>0?[...a.lyricsTimed].filter(s=>Number.isFinite(s.time)&&s.time>=0).sort((s,u)=>s.time-u.time):typeof a.lyrics=="string"?Ir(a.lyrics):[]:[],[a]),i=l.useMemo(()=>{for(let s=t.length-1;s>=0;s-=1)if(n>=t[s].time)return s;return-1},[n,t]);return{lines:t,activeIndex:i,activeLine:i>=0?t[i]??null:null}}function He({className:e,style:r,fallback:n,empty:a,unstyled:t=!1,activeClassName:i,lineClassName:s,children:u}){const d=b.useGingerState(),p=w(),{lines:m,activeIndex:y}=Sr();if(m.length===0){const f=a??n??null;return f?o.jsx("span",{"data-ginger-component":"LyricsSynced",className:e,style:r,children:f}):null}const M=t?{}:{listStyle:"none",margin:0,padding:0,fontFamily:"var(--ginger-font-family, system-ui, sans-serif)",fontSize:"var(--ginger-font-size, 14px)",color:"var(--ginger-primary-color, #111827)"};return o.jsx("ul",{className:e,style:{...M,...r},"aria-label":p.syncedLyricsList,children:m.map((f,h)=>{const I=h===y;return o.jsx("li",{"aria-current":I?"true":void 0,"data-ginger-active":I||void 0,className:[s,I?i:void 0].filter(Boolean).join(" ")||void 0,style:t?void 0:{padding:"var(--ginger-playlist-row-padding, 4px 8px)",fontWeight:I?600:400,opacity:I?1:.75},children:u?u(f,h,I,d):f.text},`${f.time}-${h}`)})})}He.displayName="Ginger.Current.LyricsSynced";function Pr(){const{tracks:e,currentIndex:r}=b.useGingerPlayback(),{currentTime:n,seek:a}=b.useGingerMedia(),t=l.useMemo(()=>{var u;return[...((u=e[r])==null?void 0:u.chapters)??[]].filter(d=>d&&Number.isFinite(d.startSeconds)&&d.startSeconds>=0).sort((d,p)=>d.startSeconds-p.startSeconds)},[r,e]),i=l.useMemo(()=>{if(t.length===0)return-1;for(let s=t.length-1;s>=0;s-=1)if(n>=t[s].startSeconds)return s;return-1},[n,t]);return{list:t,activeIndex:i,active:i>=0?t[i]??null:null,seekTo:s=>{const u=t[s];u&&a(u.startSeconds)}}}function Qe({className:e,style:r,fallback:n,empty:a,unstyled:t=!1,formatStart:i=re,children:s}){const u=b.useGingerState(),d=w(),{list:p,activeIndex:m,seekTo:y}=Pr();if(p.length===0){const f=a??n??null;return f?o.jsx("span",{"data-ginger-component":"Chapters",className:e,style:r,children:f}):null}const M=t?{}:{listStyle:"none",margin:0,padding:0,fontFamily:"var(--ginger-font-family, system-ui, sans-serif)",fontSize:"var(--ginger-font-size, 14px)",color:"var(--ginger-primary-color, #111827)"};return o.jsx("ul",{className:e,style:{...M,...r},"aria-label":d.chaptersList,children:p.map((f,h)=>{const I=h===m;return o.jsx("li",{children:o.jsx("button",{type:"button","aria-current":I?"true":void 0,"data-ginger-active":I||void 0,onClick:()=>y(h),style:{width:t?void 0:"100%",textAlign:t?void 0:"left",border:t?void 0:"none",background:t?void 0:I?"var(--ginger-playlist-active-bg, rgba(17, 24, 39, 0.06))":"transparent",color:t?void 0:"inherit",font:t?void 0:"inherit",cursor:t?void 0:"pointer",padding:t?void 0:"var(--ginger-playlist-row-padding, 6px 8px)"},children:s?s(f,h,I,u):t?o.jsxs(o.Fragment,{children:[i(f.startSeconds)," ",f.title]}):o.jsxs("span",{children:[o.jsx("span",{style:{opacity:.75,marginRight:"0.35em"},children:i(f.startSeconds)}),f.title]})})},`${f.startSeconds}-${f.title}`)})})}Qe.displayName="Ginger.Current.Chapters";function Rr({visible:e=!1,className:r,style:n,fallback:a,empty:t,children:i}){var d;const s=b.useGingerState();if(!e)return null;const u=((d=R.getCurrentTrack(s))==null?void 0:d.fileUrl)??"";if(!u){const p=t??a??null;return p?o.jsx("span",{className:r,style:n,children:p}):null}return i?o.jsx("span",{"data-ginger-component":"FileUrl",className:r,style:n,children:i(u,s)}):o.jsx("span",{className:r,style:n,children:u})}Rr.displayName="Ginger.Current.FileUrl";function Ar({className:e,style:r,fallback:n,empty:a,sizes:t,loading:i,onError:s,decoding:u,unstyled:d=!1,imgStyle:p}){const m=b.useGingerState(),y=R.getCurrentTrack(m),M=R.resolvedArtwork(m);if(!M){const h=a??n??null;return h?o.jsx("span",{className:e,style:r,children:h}):null}const f=[y==null?void 0:y.title,y==null?void 0:y.artist].filter(Boolean).join(" — ")||"Artwork";return o.jsx("div",{"data-ginger-component":"Artwork",className:e,style:d?{...r}:{background:"var(--ginger-artwork-bg, transparent)",borderRadius:"var(--ginger-artwork-radius, 0)",overflow:"hidden",...r},children:o.jsx("img",{src:M,alt:f,sizes:t,loading:i,decoding:u,onError:s,style:{display:d?void 0:"block",width:d?void 0:"100%",height:d?void 0:"100%",objectFit:d?void 0:"cover",...p}})})}Ar.displayName="Ginger.Current.Artwork";function jr({base:e=0,className:r,style:n,fallback:a,empty:t,children:i}){const s=b.useGingerState();if(s.tracks.length===0){const p=t??a??null;return p?o.jsx("span",{"data-ginger-component":"QueueIndex",className:r,style:n,children:p}):null}const d=String(s.currentIndex+e);return i?o.jsx("span",{"data-ginger-component":"QueueIndex",className:r,style:n,children:i(d,s)}):o.jsx("span",{"data-ginger-component":"QueueIndex",className:r,style:n,children:d})}jr.displayName="Ginger.Current.QueueIndex";function Gr({className:e,style:r,fallback:n,empty:a,children:t}){const i=b.useGingerState(),s=String(i.tracks.length);if(i.tracks.length===0){const u=a??n??null;return u?o.jsx("span",{className:e,style:r,children:u}):null}return t?o.jsx("span",{"data-ginger-component":"QueueLength",className:e,style:r,children:t(s,i)}):o.jsx("span",{"data-ginger-component":"QueueLength",className:e,style:r,children:s})}Gr.displayName="Ginger.Current.QueueLength";function wr({base:e=0,separator:r=" / ",className:n,style:a,fallback:t,empty:i,children:s}){const u=b.useGingerState(),d=u.tracks.length;if(d===0){const M=i??t??null;return M?o.jsx("span",{className:n,style:a,children:M}):null}const p=String(u.currentIndex+e),m=String(d),y=`${p}${r}${m}`;return s?o.jsx("span",{"data-ginger-component":"QueuePosition",className:n,style:a,children:s({index:p,length:m,label:y},u)}):o.jsx("span",{"data-ginger-component":"QueuePosition",className:n,style:a,children:y})}wr.displayName="Ginger.Current.QueuePosition";function ze(e,r,n){const{className:a,style:t,fallback:i,empty:s,children:u,format:d=re}=n;if(!(e>=0)||!Number.isFinite(e)){const m=s??i??null;return m?o.jsx("span",{"data-ginger-component":"TimeText",className:a,style:t,children:m}):null}const p=d(e);return u?o.jsx("span",{"data-ginger-component":"TimeText",className:a,style:t,children:u(p,r)}):o.jsx("span",{"data-ginger-component":"TimeText",className:a,style:t,children:p})}function Cr(e){const r=b.useGingerState();return ze(r.currentTime,r,e)}Cr.displayName="Ginger.Current.Elapsed";function Nr(e){const r=b.useGingerState();return ze(R.effectiveDuration(r),r,e)}Nr.displayName="Ginger.Current.Duration";function Lr(e){const r=b.useGingerState();return ze(R.effectiveRemaining(r),r,e)}Lr.displayName="Ginger.Current.Remaining";function _r({className:e,style:r,fallback:n,empty:a,children:t}){const i=b.useGingerState(),s=R.effectiveDuration(i),u=R.progressFraction(i);if(!(s>0)){const d=a??n??null;return d?o.jsx("span",{"data-ginger-component":"Progress",className:e,style:r,children:d}):null}return t?o.jsx("span",{"data-ginger-component":"Progress",className:e,style:r,children:t({fraction:u,currentTime:i.currentTime,duration:s},i)}):o.jsx("span",{"data-ginger-component":"Progress",className:e,style:r,children:`${Math.round(u*100)}%`})}_r.displayName="Ginger.Current.Progress";function Dr({className:e,style:r,height:n=4,showBuffered:a=!1,unstyled:t=!1}){const i=b.useGingerState(),s=`${Math.round(R.progressFraction(i)*100)}%`,u=`${Math.round(Math.min(1,Math.max(0,i.bufferedFraction))*100)}%`;return o.jsxs("div",{"data-ginger-component":"TimeRail",className:e,style:t?{...r}:{width:"100%",height:n,background:"var(--ginger-muted-color, #e5e7eb)",borderRadius:999,overflow:"hidden",position:"relative",...r},"aria-hidden":!0,children:[a?o.jsx("div",{"data-ginger-component":"TimeRail",style:{position:t?void 0:"absolute",left:t?void 0:0,top:t?void 0:0,height:t?void 0:"100%",width:u,background:t?void 0:"var(--ginger-buffer-color, rgba(107, 114, 128, 0.35))"}}):null,o.jsx("div",{"data-ginger-component":"TimeRail",style:{position:t?void 0:"relative",width:s,height:t?void 0:"100%",background:t?void 0:"var(--ginger-primary-color, #111827)"}})]})}Dr.displayName="Ginger.Current.TimeRail";function Fr({className:e,style:r,height:n=4,unstyled:a=!1}){const t=b.useGingerState(),i=`${Math.round(Math.min(1,Math.max(0,t.bufferedFraction))*100)}%`;return o.jsx("div",{"data-ginger-component":"BufferRail",className:e,style:a?{...r}:{width:"100%",height:n,background:"var(--ginger-muted-color, #e5e7eb)",borderRadius:999,overflow:"hidden",...r},"aria-hidden":!0,children:o.jsx("div",{"data-ginger-component":"BufferRail",style:{width:i,height:a?void 0:"100%",background:a?void 0:"var(--ginger-buffer-color, rgba(107, 114, 128, 0.35))"}})})}Fr.displayName="Ginger.Current.BufferRail";function Ur({className:e,style:r,fallback:n,empty:a,children:t}){const i=b.useGingerState(),s=R.derivePlaybackUiState(i);return t?o.jsx("span",{"data-ginger-component":"PlaybackState",className:e,style:r,children:t(s,i)}):o.jsx("span",{"data-ginger-component":"PlaybackState",className:e,style:r,children:s})}Ur.displayName="Ginger.Current.PlaybackState";function Br({className:e,style:r,fallback:n,empty:a,live:t="polite",children:i}){const s=b.useGingerState(),u=s.errorMessage??"";if(!u){const d=a??n??null;return d?o.jsx("span",{"data-ginger-component":"ErrorMessage",className:e,style:r,children:d}):null}return i?o.jsx("span",{"data-ginger-component":"ErrorMessage",className:e,style:r,"aria-live":t,children:i(u,s)}):o.jsx("span",{"data-ginger-component":"ErrorMessage",className:e,style:r,"aria-live":t,children:u})}Br.displayName="Ginger.Current.ErrorMessage";const _e=l.createContext(null);function yt(){const e=l.useContext(_e);if(!e)throw new Error("Ginger.Playlist.Track must be used inside <Ginger.Playlist>");return e}function Vr({children:e,unstyled:r=!1,rowStyle:n,renderTrack:a,playOnSelect:t=!0,style:i,...s}){const{tracks:u,currentIndex:d,playTrackAt:p,selectTrackAt:m}=b.useGingerPlayback(),y=r?{...i}:{listStyle:"none",margin:0,padding:0,fontFamily:"var(--ginger-font-family, system-ui, sans-serif)",fontSize:"var(--ginger-font-size, 14px)",color:"var(--ginger-primary-color, #111827)",...i};return e!==void 0?o.jsx(_e.Provider,{value:{playOnSelect:t},children:o.jsx("ul",{"data-ginger-component":"Playlist",style:y,...s,children:e})}):o.jsx(_e.Provider,{value:{playOnSelect:t},children:o.jsx("ul",{"data-ginger-component":"Playlist",style:y,...s,children:u.map((f,h)=>{const I=h===d;return o.jsx("li",{children:o.jsx("button",{type:"button",onClick:()=>{t?p(h):m(h)},style:{width:r?void 0:"100%",textAlign:r?void 0:"left",border:r?void 0:"none",background:r?void 0:I?"var(--ginger-playlist-active-bg, rgba(17, 24, 39, 0.06))":"transparent",color:r?void 0:"inherit",font:r?void 0:"inherit",cursor:r?void 0:"pointer",padding:r?void 0:"var(--ginger-playlist-row-padding, 6px 8px)",...n},children:a?a(f,h,I):o.jsxs("span",{children:[f.title,f.artist?` — ${f.artist}`:""]})})},`${h}-${S.trackIdentity(f)}`)})})})}Vr.displayName="Ginger.Playlist";function Or({index:e,unstyled:r=!1,className:n,style:a,children:t,liProps:i,onClick:s,...u}){const{playOnSelect:d}=yt(),{tracks:p,currentIndex:m,playTrackAt:y,selectTrackAt:M}=b.useGingerPlayback(),f=e===m,h=p[e],I=h!=null?o.jsxs("span",{"data-ginger-component":"PlaylistTrack",children:[h.title,h.artist?` — ${h.artist}`:""]}):null;return o.jsx("li",{...i,"data-ginger-component":"PlaylistTrack",children:o.jsx("button",{type:"button","data-ginger-component":"PlaylistTrackButton","aria-current":f?"true":void 0,"data-ginger-active":f||void 0,className:n,style:{width:r?void 0:"100%",textAlign:r?void 0:"left",border:r?void 0:"none",background:r?void 0:f?"var(--ginger-playlist-active-bg, rgba(17, 24, 39, 0.06))":"transparent",color:r?void 0:"inherit",font:r?void 0:"inherit",cursor:r?void 0:"pointer",padding:r?void 0:"var(--ginger-playlist-row-padding, 6px 8px)",...a},...u,onClick:x=>{s==null||s(x),!x.defaultPrevented&&(d?y(e):M(e))},children:t??I})})}Or.displayName="Ginger.Playlist.Track";const xt=Object.assign(Vr,{Track:Or}),ht=$("Ginger.Queue.Title",e=>{var r;return(r=e.playlistMeta)==null?void 0:r.title}),kt=$("Ginger.Queue.Subtitle",e=>{var r;return(r=e.playlistMeta)==null?void 0:r.subtitle}),bt=$("Ginger.Queue.Description",e=>{var r;return(r=e.playlistMeta)==null?void 0:r.description}),vt=$("Ginger.Queue.Copyright",e=>{var r;return(r=e.playlistMeta)==null?void 0:r.copyright});function $r({className:e,style:r,fallback:n,empty:a,unstyled:t=!1,imgStyle:i}){var p,m;const s=b.useGingerState(),u=(p=s.playlistMeta)==null?void 0:p.artworkUrl;if(!u){const y=a??n??null;return y?o.jsx("span",{"data-ginger-component":"Artwork",className:e,style:r,children:y}):null}const d=((m=s.playlistMeta)==null?void 0:m.title)??"Playlist artwork";return o.jsx("span",{"data-ginger-component":"Artwork",className:e,style:t?{...r}:{display:"inline-block",background:"var(--ginger-artwork-bg, #f3f4f6)",borderRadius:"var(--ginger-artwork-radius, 6px)",overflow:"hidden",...r},children:o.jsx("img",{src:u,alt:d,style:{display:t?void 0:"block",width:t?void 0:"100%",height:t?void 0:"100%",objectFit:t?void 0:"cover",...i}})})}$r.displayName="Ginger.Queue.Artwork";function te(e){return Number.isFinite(e)?Math.min(1,Math.max(0,e)):1}function we(e){return Number.isFinite(e)?Math.min(4,Math.max(.25,e)):1}const O={currentTime:0,duration:0,bufferedFraction:0,isBuffering:!1,errorMessage:null},Tt={...O,volume:1,muted:!1,playbackRate:1};function We(e){const r=[...e.tracks];let n=S.clampIndex(e.currentIndex??0,r.length),a=null,t=r;return e.isShuffled&&r.length>1&&(a=[...r],t=S.shuffleWithAnchor(r,n),n=0),{tracks:t,currentIndex:n,playbackMode:e.playbackMode??"playlist",isPaused:e.isPaused??!0,isShuffled:!!(e.isShuffled&&t.length>1),repeatMode:e.repeatMode??"off",originalTracks:a,playlistMeta:e.playlistMeta??null,...Tt,volume:te(e.volume??1),muted:e.muted??!1,playbackRate:we(e.playbackRate??1)}}function Yr(e,r){switch(r.type){case"INIT":{const{tracks:n,currentIndex:a,playlistMeta:t,isPaused:i,isShuffled:s,repeatMode:u,playbackMode:d,volume:p,muted:m,playbackRate:y}=r.payload;return We({tracks:n,currentIndex:a,playlistMeta:t??null,isPaused:i??!0,isShuffled:s??!1,repeatMode:u??"off",playbackMode:d??"playlist",volume:p,muted:m,playbackRate:y})}case"SET_QUEUE":{const{tracks:n,currentIndex:a}=r.payload,t=[...n],i=S.clampIndex(a??e.currentIndex,t.length);return{...e,tracks:t,currentIndex:i,isShuffled:!1,originalTracks:null,...O}}case"INSERT_TRACK":{const n=r.payload.index??e.tracks.length,a=S.insertTrackAt(e.tracks,r.payload.track,n);if(r.payload.autoPlay){const s=S.clampIndex(n,a.length);return{...e,tracks:a,currentIndex:s,isShuffled:!1,originalTracks:null,isPaused:!1,...O}}const t=n<=e.currentIndex?e.currentIndex+1:e.currentIndex,i=e.isShuffled&&e.originalTracks?S.insertTrackAt(e.originalTracks,r.payload.track,e.originalTracks.length):e.originalTracks;return{...e,tracks:a,originalTracks:i,currentIndex:S.clampIndex(t,a.length)}}case"REMOVE_TRACK":{const n=r.payload.index,a=S.removeTrackAt(e.tracks,n),t=n<e.currentIndex?e.currentIndex-1:n===e.currentIndex?Math.min(e.currentIndex,Math.max(0,a.length-1)):e.currentIndex,i=e.isShuffled&&e.originalTracks?(()=>{const u=e.tracks[n],d=S.findIndexByTrackIdentity(e.originalTracks,u);return S.removeTrackAt(e.originalTracks,d)})():e.originalTracks,s=e.isShuffled&&a.length>1;return{...e,tracks:a,isShuffled:s,originalTracks:s?i:null,currentIndex:S.clampIndex(t,a.length),...n===e.currentIndex?O:{}}}case"MOVE_TRACK":{const{fromIndex:n,toIndex:a}=r.payload,t=S.moveTrack(e.tracks,n,a);let i=e.currentIndex;return e.currentIndex===n?i=a:n<e.currentIndex&&a>=e.currentIndex?i-=1:n>e.currentIndex&&a<=e.currentIndex&&(i+=1),{...e,tracks:t,isShuffled:!1,originalTracks:null,currentIndex:S.clampIndex(i,t.length)}}case"ADD_NEXT":{const n=S.addNextTrack(e.tracks,e.currentIndex,r.payload.track),a=e.isShuffled&&e.originalTracks?(()=>{const t=e.tracks[e.currentIndex],i=S.findIndexByTrackIdentity(e.originalTracks,t);return S.addNextTrack(e.originalTracks,i,r.payload.track)})():e.originalTracks;return{...e,tracks:n,originalTracks:a}}case"SET_INDEX":{const n=S.clampIndex(r.payload.index,e.tracks.length),a=r.payload.autoPlay,t=a===!0?!1:a===!1?!0:e.isPaused;return{...e,currentIndex:n,...O,isPaused:t}}case"PLAY":return{...e,isPaused:!1};case"PAUSE":return{...e,isPaused:!0};case"TOGGLE_PAUSE":return{...e,isPaused:!e.isPaused};case"SET_REPEAT":return{...e,repeatMode:r.payload};case"CYCLE_REPEAT":return{...e,repeatMode:S.cycleRepeatMode(e.repeatMode)};case"TOGGLE_SHUFFLE":{if(e.tracks.length<=1)return{...e,isShuffled:!1,originalTracks:null};if(!e.isShuffled){const i=[...e.tracks],s=S.shuffleWithAnchor(i,e.currentIndex);return{...e,isShuffled:!0,originalTracks:i,tracks:s,currentIndex:0}}const n=e.originalTracks?[...e.originalTracks]:[...e.tracks],a=e.tracks[e.currentIndex],t=S.findIndexByTrackIdentity(n,a);return{...e,isShuffled:!1,originalTracks:null,tracks:n,currentIndex:S.clampIndex(t,n.length)}}case"NEXT":{const n=S.computeNextIndex(e),a=n===e.currentIndex;return{...e,currentIndex:n,...a?{}:O,isPaused:a?e.isPaused:!1}}case"PREV":{const n=S.computePrevIndex(e),a=n===e.currentIndex;return{...e,currentIndex:n,...a?{}:O,isPaused:a?e.isPaused:!1}}case"MEDIA_TIME_UPDATE":return{...e,currentTime:r.payload.currentTime,duration:Number.isFinite(r.payload.duration)?r.payload.duration:e.duration,bufferedFraction:r.payload.bufferedFraction,isBuffering:!1};case"MEDIA_LOADED_METADATA":return{...e,duration:Number.isFinite(r.payload.duration)?r.payload.duration:e.duration,bufferedFraction:r.payload.bufferedFraction,errorMessage:null};case"SET_PLAYLIST_META":return{...e,playlistMeta:r.payload};case"SET_PLAYBACK_MODE":return{...e,playbackMode:r.payload};case"MEDIA_ERROR":return{...e,errorMessage:r.payload.message,isPaused:!0,isBuffering:!1};case"MEDIA_WAITING":return{...e,isBuffering:!0};case"MEDIA_CANPLAY":return{...e,isBuffering:!1,errorMessage:null};case"MEDIA_PLAY":return{...e,isPaused:!1,isBuffering:!1};case"MEDIA_PAUSE":return{...e,isPaused:!0};case"RESET_MEDIA_TIMES":return{...e,currentTime:0,duration:0,bufferedFraction:0};case"MEDIA_SOURCE_CLEARED":return{...e,...O};case"SET_VOLUME":return{...e,volume:te(r.payload)};case"SET_MUTED":return{...e,muted:r.payload};case"TOGGLE_MUTE":return{...e,muted:!e.muted};case"SET_PLAYBACK_RATE":return{...e,playbackRate:we(r.payload)};case"MEDIA_VOLUME_SYNC":{const{volume:n,muted:a}=r.payload,t=te(n);return t===e.volume&&a===e.muted?e:{...e,volume:t,muted:a}}default:return e}}function Ge(){return typeof navigator>"u"||!("mediaSession"in navigator)?null:navigator.mediaSession}function Mt(e,r,n,a={}){var M;const t=l.useRef(r);t.current=r;const i=r.tracks[r.currentIndex],s=i==null?void 0:i.title,u=i==null?void 0:i.artist,d=i==null?void 0:i.album,p=S.resolveArtworkUrl(i,(M=r.playlistMeta)==null?void 0:M.artworkUrl);l.useEffect(()=>{const f=Ge();!e||!f||(f.metadata=new MediaMetadata({title:s??"Unknown track",artist:u,album:d,artwork:p?[{src:p}]:void 0}))},[e,s,u,d,p]),l.useEffect(()=>{const f=Ge();!e||!f||(f.playbackState=r.isPaused?"paused":"playing")},[e,r.isPaused]);const m=a.seekForwardSeconds,y=a.seekBackwardSeconds;l.useEffect(()=>{const f=Ge();if(!(!e||!f)){try{f.setActionHandler("play",n.play),f.setActionHandler("pause",n.pause),f.setActionHandler("nexttrack",n.next),f.setActionHandler("previoustrack",n.prev),f.setActionHandler("seekto",h=>{typeof h.seekTime=="number"&&Number.isFinite(h.seekTime)&&n.seek(h.seekTime)}),typeof m=="number"&&m>0?f.setActionHandler("seekforward",()=>{const h=t.current,I=h.currentTime+m,x=h.duration,k=Number.isFinite(x)&&x>0?x:I;n.seek(Math.min(I,k))}):f.setActionHandler("seekforward",null),typeof y=="number"&&y>0?f.setActionHandler("seekbackward",()=>{const h=t.current;n.seek(Math.max(0,h.currentTime-y))}):f.setActionHandler("seekbackward",null)}catch{}return()=>{try{f.setActionHandler("play",null),f.setActionHandler("pause",null),f.setActionHandler("nexttrack",null),f.setActionHandler("previoustrack",null),f.setActionHandler("seekto",null),f.setActionHandler("seekforward",null),f.setActionHandler("seekbackward",null)}catch{}}}},[e,n,m,y]),l.useEffect(()=>{var k,C;const f=Ge();if(!e||!f||!a.positionState)return;const h=r.duration,I=r.currentTime,x=r.playbackRate;try{if(!Number.isFinite(h)||h<=0){(k=f.setPositionState)==null||k.call(f);return}(C=f.setPositionState)==null||C.call(f,{duration:h,playbackRate:Number.isFinite(x)&&x>0?x:1,position:Math.min(Math.max(0,I),h)})}catch{}return()=>{var A;try{(A=f.setPositionState)==null||A.call(f)}catch{}}},[e,a.positionState,r.currentTime,r.duration,r.playbackRate])}const Et={"--ginger-primary-color":"#111827","--ginger-muted-color":"#6b7280","--ginger-font-size":"14px","--ginger-font-family":"system-ui, sans-serif","--ginger-playlist-row-padding":"6px 8px","--ginger-artwork-radius":"6px","--ginger-artwork-bg":"#f3f4f6","--ginger-playlist-active-bg":"rgba(17, 24, 39, 0.06)","--ginger-buffer-color":"rgba(107, 114, 128, 0.35)","--ginger-focus-ring":"0 0 0 2px rgba(59, 130, 246, 0.45)"};function It({children:e,initialTracks:r=[],initialIndex:n=0,initialPlaylistMeta:a=null,initialShuffle:t=!1,initialRepeatMode:i="off",initialPlaybackMode:s="playlist",initialPaused:u=!0,initialVolume:d=1,initialMuted:p=!1,initialPlaybackRate:m=1,initialStateKey:y,locale:M,mediaSession:f=!1,beforePlay:h,onPlayBlocked:I,retryOnError:x,persistence:k,hydrateOnMount:C=!1,resumeOnTrackChange:A=!1,unstyled:ne=!1,asChild:ae=!1,className:Xe,style:Ce,dir:Hr,prevRestartThresholdSeconds:Ke=3,onTrackChange:ie,onPlay:oe,onPause:se,onQueueEnd:ce,onError:ue,onVolumeChange:le,onPlaybackRateChange:de,onSeek:U}){var or;const L=l.useRef(null),[c,v]=l.useReducer(Yr,void 0,()=>We({tracks:r,currentIndex:n,playlistMeta:a,isPaused:u,isShuffled:t,repeatMode:i,playbackMode:s,volume:d,muted:p,playbackRate:m})),J=l.useRef(c),Ne=l.useRef({tracks:r,currentIndex:n,playlistMeta:a,isPaused:u,isShuffled:t,repeatMode:i,playbackMode:s,volume:d,muted:p,playbackRate:m});Ne.current={tracks:r,currentIndex:n,playlistMeta:a,isPaused:u,isShuffled:t,repeatMode:i,playbackMode:s,volume:d,muted:p,playbackRate:m};const Z=l.useRef(void 0);l.useEffect(()=>{if(y===void 0){Z.current=void 0;return}if(Z.current===void 0){Z.current=y;return}if(Z.current===y)return;Z.current=y;const g=Ne.current;v({type:"INIT",payload:{tracks:g.tracks,currentIndex:g.currentIndex,playlistMeta:g.playlistMeta,isPaused:g.isPaused,isShuffled:g.isShuffled,repeatMode:g.repeatMode,playbackMode:g.playbackMode,volume:g.volume,muted:g.muted,playbackRate:g.playbackRate}})},[y]),l.useEffect(()=>{J.current=c},[c]);const qe=c.tracks[c.currentIndex]??null;l.useEffect(()=>{ie==null||ie(qe,c.currentIndex)},[qe,c.currentIndex,ie]),l.useEffect(()=>{c.errorMessage&&(ue==null||ue(c.errorMessage))},[c.errorMessage,ue]);const ge=l.useRef(0),Je=l.useRef(void 0),j=x?typeof x=="object"?x:{}:null,Ze=(j==null?void 0:j.maxRetries)??3,er=(j==null?void 0:j.delayMs)??1500,rr=(j==null?void 0:j.retryableErrors)??["MEDIA_ERR_NETWORK"],tr=(j==null?void 0:j.skipOnUnrecoverable)??!1;l.useEffect(()=>{var T;const g=(T=c.tracks[c.currentIndex])==null?void 0:T.fileUrl;Je.current!==g&&(ge.current=0,Je.current=g)},[c.currentIndex,c.tracks]),l.useEffect(()=>{if(!j||!c.errorMessage)return;if(!rr.some(G=>c.errorMessage.includes(G))){if(tr&&c.tracks.length>1){const G=setTimeout(()=>v({type:"NEXT"}),500);return()=>clearTimeout(G)}return}if(ge.current>=Ze)return;const T=ge.current,E=er*2**T,P=setTimeout(()=>{ge.current=T+1,v({type:"MEDIA_CANPLAY"});const G=L.current;G&&(G.load(),v({type:"PLAY"}))},E);return()=>clearTimeout(P)},[j,Ze,er,rr,tr,c.errorMessage,c.tracks.length]);const pe=l.useRef(void 0);l.useEffect(()=>{if(pe.current===void 0){pe.current=c.isPaused;return}pe.current!==c.isPaused&&(c.isPaused?se==null||se():oe==null||oe()),pe.current=c.isPaused},[c.isPaused,se,oe]);const fe=l.useRef(void 0),me=l.useRef(void 0);l.useEffect(()=>{if(fe.current===void 0||me.current===void 0){fe.current=c.volume,me.current=c.muted;return}(fe.current!==c.volume||me.current!==c.muted)&&(le==null||le(c.volume,c.muted)),fe.current=c.volume,me.current=c.muted},[c.volume,c.muted,le]);const ye=l.useRef(void 0);l.useEffect(()=>{if(ye.current===void 0){ye.current=c.playbackRate;return}ye.current!==c.playbackRate&&(de==null||de(c.playbackRate)),ye.current=c.playbackRate},[c.playbackRate,de]);const B=l.useCallback(()=>{v({type:"PLAY"})},[]),V=l.useCallback(()=>{var g;v({type:"PAUSE"}),(g=L.current)==null||g.pause()},[]),xe=l.useCallback(()=>{J.current.isPaused?B():V()},[V,B]),_=l.useCallback(g=>{const T=L.current;T&&Number.isFinite(g)&&(T.currentTime=Math.max(0,g),U==null||U(Math.max(0,g)))},[U]),H=l.useCallback(g=>{v({type:"SET_VOLUME",payload:te(g)})},[]),Q=l.useCallback(g=>{v({type:"SET_MUTED",payload:g})},[]),z=l.useCallback(()=>{v({type:"TOGGLE_MUTE"})},[]),W=l.useCallback(g=>{v({type:"SET_PLAYBACK_RATE",payload:we(g)})},[]),X=l.useCallback(()=>{v({type:"NEXT"})},[]),K=l.useCallback(()=>{const g=L.current,T=Ke??3;g&&T>0&&g.currentTime>T?(g.currentTime=0,U==null||U(0)):v({type:"PREV"})},[Ke,U]),he=l.useCallback(g=>{v({type:"SET_REPEAT",payload:g})},[]),ke=l.useCallback(()=>{v({type:"CYCLE_REPEAT"})},[]),be=l.useCallback(()=>{v({type:"TOGGLE_SHUFFLE"})},[]),ve=l.useCallback((g,T)=>{v({type:"SET_QUEUE",payload:{tracks:g,currentIndex:T}})},[]),Te=l.useCallback((g,T,E)=>{v({type:"INSERT_TRACK",payload:{track:g,index:T,autoPlay:E}})},[]),Me=l.useCallback(g=>{v({type:"REMOVE_TRACK",payload:{index:g}})},[]),Ee=l.useCallback((g,T)=>{v({type:"MOVE_TRACK",payload:{fromIndex:g,toIndex:T}})},[]),Ie=l.useCallback(g=>{v({type:"ADD_NEXT",payload:{track:g}})},[]),Se=l.useCallback(g=>{v({type:"SET_INDEX",payload:{index:g,autoPlay:!0}})},[]),Pe=l.useCallback(g=>{v({type:"SET_INDEX",payload:{index:g,autoPlay:!1}})},[]),Re=l.useCallback(g=>{v({type:"SET_PLAYLIST_META",payload:g})},[]),Ae=l.useCallback(g=>{v({type:"SET_PLAYBACK_MODE",payload:g})},[]),je=l.useCallback(g=>{v({type:"INIT",payload:g})},[]);l.useEffect(()=>{if(!(!k||!C))try{const g=k.get("ginger:volume"),T=k.get("ginger:muted"),E=k.get("ginger:playbackRate"),P=k.get("ginger:repeatMode"),G=k.get("ginger:currentIndex"),F=Ne.current;v({type:"INIT",payload:{tracks:F.tracks,playlistMeta:F.playlistMeta,isPaused:F.isPaused,isShuffled:F.isShuffled,playbackMode:F.playbackMode,currentIndex:typeof G=="number"?G:F.currentIndex,repeatMode:P==="off"||P==="all"||P==="one"?P:F.repeatMode,volume:typeof g=="number"?g:F.volume,muted:typeof T=="boolean"?T:F.muted,playbackRate:typeof E=="number"?E:F.playbackRate}})}catch(g){process.env.NODE_ENV!=="production"&&console.warn("[@lucaismyname/ginger] persistence.get() threw during hydration:",g)}},[C,k]),l.useEffect(()=>{if(k)try{k.set("ginger:volume",c.volume),k.set("ginger:muted",c.muted),k.set("ginger:playbackRate",c.playbackRate),k.set("ginger:repeatMode",c.repeatMode),k.set("ginger:currentIndex",c.currentIndex)}catch(g){process.env.NODE_ENV!=="production"&&console.warn("[@lucaismyname/ginger] persistence.set() threw:",g)}},[k,c.volume,c.muted,c.playbackRate,c.repeatMode,c.currentIndex]),l.useEffect(()=>{if(!k||!A)return;const g=c.tracks[c.currentIndex];if(!g)return;const T=`ginger:resume:${S.trackIdentity(g)}`;try{const E=k.get(T);typeof E=="number"&&Number.isFinite(E)&&_(E)}catch(E){process.env.NODE_ENV!=="production"&&console.warn("[@lucaismyname/ginger] persistence.get() threw during resume:",E)}},[k,A,c.currentIndex,c.tracks,_]),l.useEffect(()=>{if(!k||!A)return;const g=setInterval(()=>{const T=J.current,E=T.tracks[T.currentIndex];if(!E||!(T.currentTime>=0))return;const P=`ginger:resume:${S.trackIdentity(E)}`;try{k.set(P,T.currentTime)}catch(G){process.env.NODE_ENV!=="production"&&console.warn("[@lucaismyname/ginger] persistence.set() threw during resume save:",G)}},5e3);return()=>clearInterval(g)},[k,A]);const Qr=(or=c.tracks[c.currentIndex])==null?void 0:or.fileUrl;l.useEffect(()=>{const g=L.current;if(!g)return;if(c.isPaused){g.pause();return}if(g.ended&&S.computeEndedTransition(J.current).kind==="stop"){v({type:"PAUSE"});return}let T=!1;return(async()=>{if(h){let E=!1;try{E=await h()}catch(P){const G=P instanceof Error?P.message:"beforePlay rejected";v({type:"MEDIA_ERROR",payload:{message:G}});return}if(!E){T||(v({type:"PAUSE"}),I==null||I());return}}T||g.play().catch(E=>{const P=E instanceof Error?E.message:typeof E=="string"?E:"Playback failed (e.g. autoplay blocked or unavailable source)";v({type:"MEDIA_ERROR",payload:{message:P}})})})(),()=>{T=!0}},[h,Qr,I,c.isPaused]);const q=l.useCallback(()=>{var E;const g=S.computeEndedTransition(J.current);if(g.kind==="replay_same"){const P=L.current;P&&(P.currentTime=0),v({type:"PLAY"});return}if(g.kind==="stop"){(E=L.current)==null||E.pause(),v({type:"PAUSE"}),ce==null||ce();return}const T=g.nextIndex;v({type:"SET_INDEX",payload:{index:T,autoPlay:!0}})},[ce]),zr=l.useMemo(()=>({play:B,pause:V,next:X,prev:K,seek:_}),[B,V,X,K,_]),Wr=typeof f=="object"?!0:!!f,Xr=l.useMemo(()=>typeof f=="object"?f:{},[f]);Mt(Wr,c,zr,Xr);const nr=Hr??(M!=null&&M.seek&&/[\u0590-\u08FF]/.test(M.seek)?"rtl":"ltr"),Kr=l.useMemo(()=>({state:c,dispatch:v,audioRef:L,notifyEnded:q,init:je,play:B,pause:V,togglePlayPause:xe,seek:_,setVolume:H,setMuted:Q,toggleMute:z,setPlaybackRate:W,next:X,prev:K,setRepeatMode:he,cycleRepeat:ke,toggleShuffle:be,setQueue:ve,insertTrackAt:Te,removeTrackAt:Me,moveTrack:Ee,enqueueNext:Ie,playTrackAt:Se,selectTrackAt:Pe,setPlaylistMeta:Re,setPlaybackMode:Ae}),[ke,je,X,q,V,B,Se,Te,Me,Ee,Ie,Pe,K,_,Q,W,ve,he,Re,Ae,H,c,z,xe,be]),qr=l.useMemo(()=>({tracks:c.tracks,currentIndex:c.currentIndex,isPaused:c.isPaused,isShuffled:c.isShuffled,repeatMode:c.repeatMode,originalTracks:c.originalTracks,playlistMeta:c.playlistMeta,init:je,play:B,pause:V,togglePlayPause:xe,next:X,prev:K,setRepeatMode:he,cycleRepeat:ke,toggleShuffle:be,playbackMode:c.playbackMode,setQueue:ve,insertTrackAt:Te,removeTrackAt:Me,moveTrack:Ee,enqueueNext:Ie,playTrackAt:Se,selectTrackAt:Pe,setPlaylistMeta:Re,setPlaybackMode:Ae,dispatch:v}),[c.tracks,c.currentIndex,c.isPaused,c.isShuffled,c.repeatMode,c.playbackMode,c.originalTracks,c.playlistMeta,je,B,V,xe,X,K,he,ke,be,ve,Te,Me,Ee,Ie,Se,Pe,Re,Ae]),Jr=l.useMemo(()=>({currentTime:c.currentTime,duration:c.duration,bufferedFraction:c.bufferedFraction,isBuffering:c.isBuffering,errorMessage:c.errorMessage,volume:c.volume,muted:c.muted,playbackRate:c.playbackRate,seek:_,setVolume:H,setMuted:Q,toggleMute:z,setPlaybackRate:W,audioRef:L,notifyEnded:q,dispatch:v}),[c.currentTime,c.duration,c.bufferedFraction,c.isBuffering,c.errorMessage,c.volume,c.muted,c.playbackRate,_,H,Q,z,W,q]),Zr=l.useMemo(()=>({currentTime:c.currentTime,duration:c.duration,bufferedFraction:c.bufferedFraction,isBuffering:c.isBuffering,errorMessage:c.errorMessage}),[c.currentTime,c.duration,c.bufferedFraction,c.isBuffering,c.errorMessage]),et=l.useMemo(()=>({volume:c.volume,muted:c.muted,playbackRate:c.playbackRate,seek:_,setVolume:H,setMuted:Q,toggleMute:z,setPlaybackRate:W,audioRef:L,notifyEnded:q,dispatch:v}),[c.volume,c.muted,c.playbackRate,_,H,Q,z,W,q]),ar=R.derivePlaybackUiState(c),ir=l.useMemo(()=>ne?Ce:{...Et,...Ce},[Ce,ne]),D=l.useMemo(()=>({className:Xe,style:ir,"data-ginger-playback":ar,dir:nr}),[Xe,ir,ar,nr]),rt=l.useMemo(()=>{if(!ae)return o.jsx("div",{className:D.className,style:D.style,"data-ginger-playback":D["data-ginger-playback"],dir:D.dir,children:e});const g=l.Children.only(e);if(!l.isValidElement(g))throw new Error("Ginger.Provider asChild expects a single React element child.");const T=g,E=T.props.style;return l.cloneElement(T,{className:St(T.props.className,D.className),style:E&&typeof E=="object"?{...E,...D.style}:D.style,"data-ginger-playback":D["data-ginger-playback"],dir:D.dir})},[ae,e,D]);return o.jsx(it,{locale:M,children:o.jsx(b.GingerPlaybackContext.Provider,{value:qr,children:o.jsx(b.GingerTimeContext.Provider,{value:Zr,children:o.jsx(b.GingerMediaControlContext.Provider,{value:et,children:o.jsx(b.GingerMediaContext.Provider,{value:Jr,children:o.jsx(ur.Provider,{value:Kr,children:rt})})})})})})}function St(e,r){const n=[e,r].filter(Boolean).join(" ");return n===""?void 0:n}const Pt={Provider:It,Player:nt,Current:{Title:st,Artist:ct,Album:ut,Description:lt,Copyright:dt,Genre:gt,Label:pt,Isrc:ft,TrackNumber:mt,Year:Mr,Lyrics:Er,LyricsSynced:He,Chapters:Qe,FileUrl:Rr,Artwork:Ar,QueueIndex:jr,QueueLength:Gr,QueuePosition:wr,Elapsed:Cr,Duration:Nr,Remaining:Lr,Progress:_r,TimeRail:Dr,BufferRail:Fr,PlaybackState:Ur,ErrorMessage:Br},Queue:{Title:ht,Subtitle:kt,Description:bt,Copyright:vt,Artwork:$r},Control:{PlayPause:fr,Repeat:mr,Next:yr,Previous:xr,Shuffle:hr,SeekBar:kr,Volume:br,Mute:vr,PlaybackRate:Tr},Icon:{Play:Fe,Pause:De,SkipForward:Oe,SkipBack:Ve,Shuffle:Be,Volume2:$e,VolumeX:Ye,RepeatGlyph:Ue,Wrapper:N},Playlist:xt};exports.Chapters=Qe;exports.Ginger=Pt;exports.LyricsSynced=He;exports.Pause=De;exports.Play=Fe;exports.RepeatGlyph=Ue;exports.ShuffleIcon=Be;exports.SkipBack=Ve;exports.SkipForward=Oe;exports.Volume2=$e;exports.VolumeX=Ye;exports.Wrapper=N;exports.clampPlaybackRate=we;exports.clampVolume=te;exports.createInitialState=We;exports.defaultGingerLocale=ee;exports.gingerReducer=Yr;exports.parseLrc=Ir;exports.useGingerChapters=Pr;exports.useGingerLocale=w;exports.useGingerLyricsSync=Sr;exports.usePlayPauseBinding=pr;exports.useSeekBarBinding=dr;exports.useVolumeSlider=gr;
|
|
2
|
+
//# sourceMappingURL=ginger-Bzjmat52.cjs.map
|