@lucaismyname/ginger 0.0.17 → 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +76 -0
  2. package/dist/client.cjs +1 -1
  3. package/dist/client.js +2 -2
  4. package/dist/client.test.d.ts +2 -0
  5. package/dist/client.test.d.ts.map +1 -0
  6. package/dist/components/controls/Controls.d.ts +26 -13
  7. package/dist/components/controls/Controls.d.ts.map +1 -1
  8. package/dist/experimental-gapless/index.cjs.map +1 -1
  9. package/dist/experimental-gapless/index.d.ts +3 -1
  10. package/dist/experimental-gapless/index.d.ts.map +1 -1
  11. package/dist/experimental-gapless/index.js.map +1 -1
  12. package/dist/{ginger-F4UDi2Qf.js → ginger-8x4WzVmw.js} +196 -144
  13. package/dist/ginger-8x4WzVmw.js.map +1 -0
  14. package/dist/ginger-CMbd3s8C.cjs +2 -0
  15. package/dist/ginger-CMbd3s8C.cjs.map +1 -0
  16. package/dist/index.cjs +1 -1
  17. package/dist/index.d.ts +44 -2
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +2 -2
  20. package/dist/testing/helpers.d.ts +29 -0
  21. package/dist/testing/helpers.d.ts.map +1 -1
  22. package/dist/testing/helpers.test.d.ts +2 -0
  23. package/dist/testing/helpers.test.d.ts.map +1 -0
  24. package/dist/testing/index.cjs +48 -43
  25. package/dist/testing/index.cjs.map +1 -1
  26. package/dist/testing/index.d.ts +1 -1
  27. package/dist/testing/index.d.ts.map +1 -1
  28. package/dist/testing/index.js +6888 -3749
  29. package/dist/testing/index.js.map +1 -1
  30. package/dist/testing/index.test.d.ts +2 -0
  31. package/dist/testing/index.test.d.ts.map +1 -0
  32. package/dist/testing/renderGinger.d.ts.map +1 -1
  33. package/dist/{useNextTrackPrefetch-BeAKS3SR.cjs → useNextTrackPrefetch-CFoUynDv.cjs} +2 -2
  34. package/dist/{useNextTrackPrefetch-BeAKS3SR.cjs.map → useNextTrackPrefetch-CFoUynDv.cjs.map} +1 -1
  35. package/dist/{useNextTrackPrefetch-gKls_h_N.js → useNextTrackPrefetch-CV1khU0h.js} +2 -2
  36. package/dist/{useNextTrackPrefetch-gKls_h_N.js.map → useNextTrackPrefetch-CV1khU0h.js.map} +1 -1
  37. package/package.json +9 -1
  38. package/dist/ginger-DWOyCog6.cjs +0 -2
  39. package/dist/ginger-DWOyCog6.cjs.map +0 -1
  40. package/dist/ginger-F4UDi2Qf.js.map +0 -1
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../src/testing/index.test.tsx"],"names":[],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"renderGinger.d.ts","sourceRoot":"","sources":["../../src/testing/renderGinger.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,WAAW,CAAC;AAE7D,MAAM,MAAM,mBAAmB,GAAG,2BAA2B,GAAG;IAC9D,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;CAClB,CAAC;AAEF,wBAAgB,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,GAAE,mBAAwB,gIAQ5E"}
1
+ {"version":3,"file":"renderGinger.d.ts","sourceRoot":"","sources":["../../src/testing/renderGinger.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,WAAW,CAAC;AAE7D,MAAM,MAAM,mBAAmB,GAAG,2BAA2B,GAAG;IAC9D,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;CAClB,CAAC;AAEF,wBAAgB,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,GAAE,mBAAwB,gIAQ5E"}
@@ -1,2 +1,2 @@
1
- "use strict";const o=require("react"),v=require("./GingerSplitContexts-C7puo0M7.cjs"),P=require("./ginger-DWOyCog6.cjs");function K(){const t=v.useGingerPlayback(),e=v.useGingerMedia();return o.useMemo(()=>{const n=v.gingerStateFromContextValues(t,e);return{state:n,currentTrack:P.getCurrentTrack(n),playbackUi:P.derivePlaybackUiState(n),duration:P.effectiveDuration(n),remaining:P.effectiveRemaining(n),progress:P.progressFraction(n),artworkUrl:P.resolvedArtwork(n),albumLine:P.resolvedAlbumLine(n),play:t.play,pause:t.pause,togglePlayPause:t.togglePlayPause,seek:e.seek,setVolume:e.setVolume,setMuted:e.setMuted,toggleMute:e.toggleMute,setPlaybackRate:e.setPlaybackRate,next:t.next,prev:t.prev,setRepeatMode:t.setRepeatMode,cycleRepeat:t.cycleRepeat,toggleShuffle:t.toggleShuffle,setQueue:t.setQueue,insertTrackAt:t.insertTrackAt,removeTrackAt:t.removeTrackAt,moveTrack:t.moveTrack,enqueueNext:t.enqueueNext,playTrackAt:t.playTrackAt,selectTrackAt:t.selectTrackAt,setPlaylistMeta:t.setPlaylistMeta,setPlaybackMode:t.setPlaybackMode,init:t.init,audioRef:e.audioRef,dispatch:t.dispatch}},[t,e])}const G=new WeakMap;function O(t){const e=2**Math.round(Math.log2(t));return Math.min(32768,Math.max(32,e))}function V(t,e){let n=G.get(t);if(!n){const d=window.AudioContext??window.webkitAudioContext;if(!d)throw new Error("Web Audio API is not available");const i=new d,m=i.createMediaElementSource(t);n={context:i,source:m,consumers:new Map,nextId:0},G.set(t,n)}const{context:s,source:c}=n,r=s.createAnalyser();r.fftSize=O(e.fftSize),r.smoothingTimeConstant=e.smoothingTimeConstant,r.minDecibels=e.minDecibels,r.maxDecibels=e.maxDecibels,c.connect(r);const a=n.consumers.size===0;a&&r.connect(s.destination);const l=n.nextId;return n.nextId+=1,n.consumers.set(l,{analyser:r,isPlaybackSink:a}),{id:l,context:s,analyser:r}}function U(t,e){const n=G.get(t);if(!n)return;const s=n.consumers.get(e);if(!s)return;const{analyser:c,isPlaybackSink:r}=s;if(c.disconnect(),n.consumers.delete(e),n.consumers.size===0){try{n.source.disconnect()}catch{}n.context.close(),G.delete(t);return}if(r){const a=n.consumers.values().next().value;a&&(a.analyser.connect(n.context.destination),a.isPlaybackSink=!0)}}const q=new Uint8Array(0),I=new Uint8Array(0);function Q(t={}){const{enabled:e=!0,fftSize:n=2048,smoothingTimeConstant:s=.8,minDecibels:c=-100,maxDecibels:r=-30}=t,{audioRef:a,state:l}=K(),d=o.useMemo(()=>({fftSize:n,smoothingTimeConstant:s,minDecibels:c,maxDecibels:r}),[n,s,c,r]),[i,m]=o.useState(0),[p,u]=o.useState(null),[R,k]=o.useState(!1),[S,h]=o.useState({frequencyBinCount:0,sampleRate:0}),g=o.useRef(q),x=o.useRef(I),X=o.useCallback(async()=>{const A=M.current;A&&A.state==="suspended"&&await A.resume()},[]),M=o.useRef(null),L=o.useRef(null);return o.useLayoutEffect(()=>{if(!e||typeof window>"u")return;let A=!1,C=null,E=null,D=0;const F=()=>{const f=M.current;f&&k(f.state==="suspended")},z=()=>{if(A)return;const f=L.current,w=g.current,y=x.current;f&&w.length>0&&y.length>0&&(f.getByteFrequencyData(w),f.getByteTimeDomainData(y),m(T=>T+1)),D=requestAnimationFrame(z)},B=()=>{const f=a.current;if(!f||A)return"no-element";try{const{id:w,context:y,analyser:T}=V(f,d);C=w,E=f,M.current=y,L.current=T,k(y.state==="suspended"),u(null),y.addEventListener("statechange",F);const b=T.frequencyBinCount,H=T.fftSize;return g.current=new Uint8Array(b),x.current=new Uint8Array(H),h({frequencyBinCount:b,sampleRate:y.sampleRate}),D=requestAnimationFrame(z),"ok"}catch(w){const y=w instanceof Error?w.message:"Failed to attach live analyser";return u(y),M.current=null,L.current=null,g.current=q,x.current=I,h({frequencyBinCount:0,sampleRate:0}),"error"}},N=B();if(N!=="ok"){let f=0;const w=120;let y=0;const T=()=>{if(A)return;const b=B();b==="ok"||b==="error"||(y+=1,!(y>=w)&&(f=requestAnimationFrame(T)))};return N==="no-element"&&(f=requestAnimationFrame(T)),()=>{var b;A=!0,cancelAnimationFrame(f),cancelAnimationFrame(D),C!=null&&E&&U(E,C),(b=M.current)==null||b.removeEventListener("statechange",F),M.current=null,L.current=null,g.current=q,x.current=I}}return()=>{var f;A=!0,cancelAnimationFrame(D),C!=null&&E&&U(E,C),(f=M.current)==null||f.removeEventListener("statechange",F),M.current=null,L.current=null,g.current=q,x.current=I,h({frequencyBinCount:0,sampleRate:0})}},[e,a,d,l.currentIndex]),{frequencyData:g.current,timeDomainData:x.current,frequencyBinCount:S.frequencyBinCount,sampleRate:S.sampleRate,isSuspended:R,error:p,resume:X}}function W(t=!0,e={}){const{togglePlayPause:n,next:s,prev:c}=v.useGingerPlayback(),{toggleMute:r}=v.useGingerMedia(),a=e.mute;o.useEffect(()=>{if(!t||typeof window>"u")return;const l=(e.playPause??" ").toLowerCase(),d=(e.next??"ArrowRight").toLowerCase(),i=(e.previous??"ArrowLeft").toLowerCase(),m=a==null?void 0:a.toLowerCase(),p=u=>{const R=u.target;if(R&&(["INPUT","TEXTAREA","SELECT"].includes(R.tagName)||R.isContentEditable))return;const k=u.key.toLowerCase();k===l?(u.preventDefault(),n()):k===d?(u.preventDefault(),s()):k===i?(u.preventDefault(),c()):m&&k===m&&(u.preventDefault(),r())};return window.addEventListener("keydown",p),()=>window.removeEventListener("keydown",p)},[e.next,e.playPause,e.previous,t,a,s,c,r,n])}function j(t){const{durationMs:e,stopAfterTracks:n,respectPause:s=!0,enabled:c=!0,onFire:r}=t,{currentIndex:a,pause:l,isPaused:d}=v.useGingerPlayback(),i=o.useRef(n??0),m=o.useRef(a);o.useEffect(()=>{i.current=n??0},[n]),o.useEffect(()=>{if(!c||!e||e<=0||s&&d)return;const p=setTimeout(()=>{l(),r==null||r()},e);return()=>clearTimeout(p)},[e,c,d,r,l,s]),o.useEffect(()=>{if(!c||!n||n<=0)return;const p=m.current;m.current=a,a!==p&&(i.current-=1,i.current<=0&&(l(),r==null||r()))},[a,c,r,l,n])}function J(t=!1){const e=v.useGingerState(),n=o.useRef(e);o.useEffect(()=>{if(!t||typeof console>"u")return;const s=n.current;s!==e&&console.debug("[ginger]",{from:{currentIndex:s.currentIndex,isPaused:s.isPaused,currentTime:s.currentTime,repeatMode:s.repeatMode},to:{currentIndex:e.currentIndex,isPaused:e.isPaused,currentTime:e.currentTime,repeatMode:e.repeatMode}}),n.current=e},[t,e])}function Y(t){return Math.max(0,Math.min(1,t))}function Z(t){const e=v.useGingerMedia(),n=v.useGingerPlayback(),{seek:s}=e,[c,r]=o.useState(0),[a,l]=o.useState(!1),d=P.progressFraction(v.gingerStateFromContextValues(n,e)),i=a?c:d,m=o.useCallback(p=>{if(!(t>0))return;const u=p.currentTarget,R=u.getBoundingClientRect(),k=g=>{const x=Y((g-R.left)/R.width);r(x),s(x*t)};l(!0),u.setPointerCapture(p.pointerId),k(p.clientX);const S=g=>k(g.clientX),h=g=>{k(g.clientX),l(!1),u.releasePointerCapture(p.pointerId),u.removeEventListener("pointermove",S),u.removeEventListener("pointerup",h),u.removeEventListener("pointercancel",h)};u.addEventListener("pointermove",S),u.addEventListener("pointerup",h),u.addEventListener("pointercancel",h)},[t,s]);return{fraction:c,displayFraction:i,isDragging:a,onPointerDown:m}}function _(t={}){const{enabled:e=!0,crossOrigin:n}=t,{tracks:s,currentIndex:c,repeatMode:r,playbackMode:a}=v.useGingerPlayback();o.useEffect(()=>{var m;if(!e||typeof document>"u")return;const l=P.computeNextIndex({tracks:s,currentIndex:c,repeatMode:r,playbackMode:a});if(l===c)return;const d=((m=s[l])==null?void 0:m.fileUrl)??"";if(!d)return;const i=document.createElement("audio");return i.preload="auto",n&&(i.crossOrigin=n),i.src=d,i.load(),()=>{i.removeAttribute("src"),i.load()}},[e,n,s,c,r,a])}exports.attachLiveAnalyser=V;exports.detachLiveAnalyser=U;exports.useGinger=K;exports.useGingerDebugLog=J;exports.useGingerKeyboardShortcuts=W;exports.useGingerLiveAnalyzer=Q;exports.useGingerSleepTimer=j;exports.useNextTrackPrefetch=_;exports.useSeekDrag=Z;
2
- //# sourceMappingURL=useNextTrackPrefetch-BeAKS3SR.cjs.map
1
+ "use strict";const o=require("react"),v=require("./GingerSplitContexts-C7puo0M7.cjs"),P=require("./ginger-CMbd3s8C.cjs");function K(){const t=v.useGingerPlayback(),e=v.useGingerMedia();return o.useMemo(()=>{const n=v.gingerStateFromContextValues(t,e);return{state:n,currentTrack:P.getCurrentTrack(n),playbackUi:P.derivePlaybackUiState(n),duration:P.effectiveDuration(n),remaining:P.effectiveRemaining(n),progress:P.progressFraction(n),artworkUrl:P.resolvedArtwork(n),albumLine:P.resolvedAlbumLine(n),play:t.play,pause:t.pause,togglePlayPause:t.togglePlayPause,seek:e.seek,setVolume:e.setVolume,setMuted:e.setMuted,toggleMute:e.toggleMute,setPlaybackRate:e.setPlaybackRate,next:t.next,prev:t.prev,setRepeatMode:t.setRepeatMode,cycleRepeat:t.cycleRepeat,toggleShuffle:t.toggleShuffle,setQueue:t.setQueue,insertTrackAt:t.insertTrackAt,removeTrackAt:t.removeTrackAt,moveTrack:t.moveTrack,enqueueNext:t.enqueueNext,playTrackAt:t.playTrackAt,selectTrackAt:t.selectTrackAt,setPlaylistMeta:t.setPlaylistMeta,setPlaybackMode:t.setPlaybackMode,init:t.init,audioRef:e.audioRef,dispatch:t.dispatch}},[t,e])}const G=new WeakMap;function O(t){const e=2**Math.round(Math.log2(t));return Math.min(32768,Math.max(32,e))}function V(t,e){let n=G.get(t);if(!n){const d=window.AudioContext??window.webkitAudioContext;if(!d)throw new Error("Web Audio API is not available");const i=new d,m=i.createMediaElementSource(t);n={context:i,source:m,consumers:new Map,nextId:0},G.set(t,n)}const{context:s,source:c}=n,r=s.createAnalyser();r.fftSize=O(e.fftSize),r.smoothingTimeConstant=e.smoothingTimeConstant,r.minDecibels=e.minDecibels,r.maxDecibels=e.maxDecibels,c.connect(r);const a=n.consumers.size===0;a&&r.connect(s.destination);const l=n.nextId;return n.nextId+=1,n.consumers.set(l,{analyser:r,isPlaybackSink:a}),{id:l,context:s,analyser:r}}function U(t,e){const n=G.get(t);if(!n)return;const s=n.consumers.get(e);if(!s)return;const{analyser:c,isPlaybackSink:r}=s;if(c.disconnect(),n.consumers.delete(e),n.consumers.size===0){try{n.source.disconnect()}catch{}n.context.close(),G.delete(t);return}if(r){const a=n.consumers.values().next().value;a&&(a.analyser.connect(n.context.destination),a.isPlaybackSink=!0)}}const q=new Uint8Array(0),I=new Uint8Array(0);function Q(t={}){const{enabled:e=!0,fftSize:n=2048,smoothingTimeConstant:s=.8,minDecibels:c=-100,maxDecibels:r=-30}=t,{audioRef:a,state:l}=K(),d=o.useMemo(()=>({fftSize:n,smoothingTimeConstant:s,minDecibels:c,maxDecibels:r}),[n,s,c,r]),[i,m]=o.useState(0),[p,u]=o.useState(null),[R,k]=o.useState(!1),[S,h]=o.useState({frequencyBinCount:0,sampleRate:0}),g=o.useRef(q),x=o.useRef(I),X=o.useCallback(async()=>{const A=M.current;A&&A.state==="suspended"&&await A.resume()},[]),M=o.useRef(null),L=o.useRef(null);return o.useLayoutEffect(()=>{if(!e||typeof window>"u")return;let A=!1,C=null,E=null,D=0;const F=()=>{const f=M.current;f&&k(f.state==="suspended")},z=()=>{if(A)return;const f=L.current,w=g.current,y=x.current;f&&w.length>0&&y.length>0&&(f.getByteFrequencyData(w),f.getByteTimeDomainData(y),m(T=>T+1)),D=requestAnimationFrame(z)},B=()=>{const f=a.current;if(!f||A)return"no-element";try{const{id:w,context:y,analyser:T}=V(f,d);C=w,E=f,M.current=y,L.current=T,k(y.state==="suspended"),u(null),y.addEventListener("statechange",F);const b=T.frequencyBinCount,H=T.fftSize;return g.current=new Uint8Array(b),x.current=new Uint8Array(H),h({frequencyBinCount:b,sampleRate:y.sampleRate}),D=requestAnimationFrame(z),"ok"}catch(w){const y=w instanceof Error?w.message:"Failed to attach live analyser";return u(y),M.current=null,L.current=null,g.current=q,x.current=I,h({frequencyBinCount:0,sampleRate:0}),"error"}},N=B();if(N!=="ok"){let f=0;const w=120;let y=0;const T=()=>{if(A)return;const b=B();b==="ok"||b==="error"||(y+=1,!(y>=w)&&(f=requestAnimationFrame(T)))};return N==="no-element"&&(f=requestAnimationFrame(T)),()=>{var b;A=!0,cancelAnimationFrame(f),cancelAnimationFrame(D),C!=null&&E&&U(E,C),(b=M.current)==null||b.removeEventListener("statechange",F),M.current=null,L.current=null,g.current=q,x.current=I}}return()=>{var f;A=!0,cancelAnimationFrame(D),C!=null&&E&&U(E,C),(f=M.current)==null||f.removeEventListener("statechange",F),M.current=null,L.current=null,g.current=q,x.current=I,h({frequencyBinCount:0,sampleRate:0})}},[e,a,d,l.currentIndex]),{frequencyData:g.current,timeDomainData:x.current,frequencyBinCount:S.frequencyBinCount,sampleRate:S.sampleRate,isSuspended:R,error:p,resume:X}}function W(t=!0,e={}){const{togglePlayPause:n,next:s,prev:c}=v.useGingerPlayback(),{toggleMute:r}=v.useGingerMedia(),a=e.mute;o.useEffect(()=>{if(!t||typeof window>"u")return;const l=(e.playPause??" ").toLowerCase(),d=(e.next??"ArrowRight").toLowerCase(),i=(e.previous??"ArrowLeft").toLowerCase(),m=a==null?void 0:a.toLowerCase(),p=u=>{const R=u.target;if(R&&(["INPUT","TEXTAREA","SELECT"].includes(R.tagName)||R.isContentEditable))return;const k=u.key.toLowerCase();k===l?(u.preventDefault(),n()):k===d?(u.preventDefault(),s()):k===i?(u.preventDefault(),c()):m&&k===m&&(u.preventDefault(),r())};return window.addEventListener("keydown",p),()=>window.removeEventListener("keydown",p)},[e.next,e.playPause,e.previous,t,a,s,c,r,n])}function j(t){const{durationMs:e,stopAfterTracks:n,respectPause:s=!0,enabled:c=!0,onFire:r}=t,{currentIndex:a,pause:l,isPaused:d}=v.useGingerPlayback(),i=o.useRef(n??0),m=o.useRef(a);o.useEffect(()=>{i.current=n??0},[n]),o.useEffect(()=>{if(!c||!e||e<=0||s&&d)return;const p=setTimeout(()=>{l(),r==null||r()},e);return()=>clearTimeout(p)},[e,c,d,r,l,s]),o.useEffect(()=>{if(!c||!n||n<=0)return;const p=m.current;m.current=a,a!==p&&(i.current-=1,i.current<=0&&(l(),r==null||r()))},[a,c,r,l,n])}function J(t=!1){const e=v.useGingerState(),n=o.useRef(e);o.useEffect(()=>{if(!t||typeof console>"u")return;const s=n.current;s!==e&&console.debug("[ginger]",{from:{currentIndex:s.currentIndex,isPaused:s.isPaused,currentTime:s.currentTime,repeatMode:s.repeatMode},to:{currentIndex:e.currentIndex,isPaused:e.isPaused,currentTime:e.currentTime,repeatMode:e.repeatMode}}),n.current=e},[t,e])}function Y(t){return Math.max(0,Math.min(1,t))}function Z(t){const e=v.useGingerMedia(),n=v.useGingerPlayback(),{seek:s}=e,[c,r]=o.useState(0),[a,l]=o.useState(!1),d=P.progressFraction(v.gingerStateFromContextValues(n,e)),i=a?c:d,m=o.useCallback(p=>{if(!(t>0))return;const u=p.currentTarget,R=u.getBoundingClientRect(),k=g=>{const x=Y((g-R.left)/R.width);r(x),s(x*t)};l(!0),u.setPointerCapture(p.pointerId),k(p.clientX);const S=g=>k(g.clientX),h=g=>{k(g.clientX),l(!1),u.releasePointerCapture(p.pointerId),u.removeEventListener("pointermove",S),u.removeEventListener("pointerup",h),u.removeEventListener("pointercancel",h)};u.addEventListener("pointermove",S),u.addEventListener("pointerup",h),u.addEventListener("pointercancel",h)},[t,s]);return{fraction:c,displayFraction:i,isDragging:a,onPointerDown:m}}function _(t={}){const{enabled:e=!0,crossOrigin:n}=t,{tracks:s,currentIndex:c,repeatMode:r,playbackMode:a}=v.useGingerPlayback();o.useEffect(()=>{var m;if(!e||typeof document>"u")return;const l=P.computeNextIndex({tracks:s,currentIndex:c,repeatMode:r,playbackMode:a});if(l===c)return;const d=((m=s[l])==null?void 0:m.fileUrl)??"";if(!d)return;const i=document.createElement("audio");return i.preload="auto",n&&(i.crossOrigin=n),i.src=d,i.load(),()=>{i.removeAttribute("src"),i.load()}},[e,n,s,c,r,a])}exports.attachLiveAnalyser=V;exports.detachLiveAnalyser=U;exports.useGinger=K;exports.useGingerDebugLog=J;exports.useGingerKeyboardShortcuts=W;exports.useGingerLiveAnalyzer=Q;exports.useGingerSleepTimer=j;exports.useNextTrackPrefetch=_;exports.useSeekDrag=Z;
2
+ //# sourceMappingURL=useNextTrackPrefetch-CFoUynDv.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"useNextTrackPrefetch-BeAKS3SR.cjs","sources":["../src/hooks/useGinger.ts","../src/analyzer/liveAudioGraph.ts","../src/analyzer/useGingerLiveAnalyzer.ts","../src/hooks/useGingerKeyboardShortcuts.ts","../src/hooks/useGingerSleepTimer.ts","../src/hooks/useGingerDebugLog.ts","../src/hooks/useSeekDrag.ts","../src/hooks/useNextTrackPrefetch.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { gingerStateFromContextValues, useGingerMedia, useGingerPlayback } from \"../context/GingerSplitContexts\";\nimport {\n derivePlaybackUiState,\n effectiveDuration,\n effectiveRemaining,\n getCurrentTrack,\n progressFraction,\n resolvedAlbumLine,\n resolvedArtwork,\n} from \"../internal/selectors\";\n\nexport function useGinger() {\n const pb = useGingerPlayback();\n const md = useGingerMedia();\n\n return useMemo(\n () => {\n const state = gingerStateFromContextValues(pb, md);\n return {\n state,\n currentTrack: getCurrentTrack(state),\n playbackUi: derivePlaybackUiState(state),\n duration: effectiveDuration(state),\n remaining: effectiveRemaining(state),\n progress: progressFraction(state),\n artworkUrl: resolvedArtwork(state),\n albumLine: resolvedAlbumLine(state),\n play: pb.play,\n pause: pb.pause,\n togglePlayPause: pb.togglePlayPause,\n seek: md.seek,\n setVolume: md.setVolume,\n setMuted: md.setMuted,\n toggleMute: md.toggleMute,\n setPlaybackRate: md.setPlaybackRate,\n next: pb.next,\n prev: pb.prev,\n setRepeatMode: pb.setRepeatMode,\n cycleRepeat: pb.cycleRepeat,\n toggleShuffle: pb.toggleShuffle,\n setQueue: pb.setQueue,\n insertTrackAt: pb.insertTrackAt,\n removeTrackAt: pb.removeTrackAt,\n moveTrack: pb.moveTrack,\n enqueueNext: pb.enqueueNext,\n playTrackAt: pb.playTrackAt,\n selectTrackAt: pb.selectTrackAt,\n setPlaylistMeta: pb.setPlaylistMeta,\n setPlaybackMode: pb.setPlaybackMode,\n init: pb.init,\n audioRef: md.audioRef,\n dispatch: pb.dispatch,\n };\n },\n [pb, md],\n );\n}\n","/** One MediaElementAudioSourceNode per HTMLAudioElement; multiple AnalyserNodes may tap the source. */\n\nexport type LiveAnalyserOptions = {\n fftSize: number;\n smoothingTimeConstant: number;\n minDecibels: number;\n maxDecibels: number;\n};\n\ntype Consumer = {\n analyser: AnalyserNode;\n /** This analyser is wired to `audioContext.destination` so the graph is audible. */\n isPlaybackSink: boolean;\n};\n\ntype ElementEntry = {\n context: AudioContext;\n source: MediaElementAudioSourceNode;\n consumers: Map<number, Consumer>;\n nextId: number;\n};\n\nconst entries = new WeakMap<HTMLAudioElement, ElementEntry>();\n\nfunction clampFftSize(n: number): number {\n const p = 2 ** Math.round(Math.log2(n));\n return Math.min(32768, Math.max(32, p));\n}\n\nexport function attachLiveAnalyser(\n element: HTMLAudioElement,\n options: LiveAnalyserOptions,\n): { id: number; context: AudioContext; analyser: AnalyserNode } {\n let entry = entries.get(element);\n if (!entry) {\n const Context = window.AudioContext ?? (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Context) {\n throw new Error(\"Web Audio API is not available\");\n }\n const context = new Context();\n const source = context.createMediaElementSource(element);\n entry = { context, source, consumers: new Map(), nextId: 0 };\n entries.set(element, entry);\n }\n\n const { context, source } = entry;\n const analyser = context.createAnalyser();\n analyser.fftSize = clampFftSize(options.fftSize);\n analyser.smoothingTimeConstant = options.smoothingTimeConstant;\n analyser.minDecibels = options.minDecibels;\n analyser.maxDecibels = options.maxDecibels;\n\n source.connect(analyser);\n\n const isFirst = entry.consumers.size === 0;\n if (isFirst) {\n analyser.connect(context.destination);\n }\n\n const id = entry.nextId;\n entry.nextId += 1;\n entry.consumers.set(id, { analyser, isPlaybackSink: isFirst });\n\n return { id, context, analyser };\n}\n\nexport function detachLiveAnalyser(element: HTMLAudioElement, id: number): void {\n const entry = entries.get(element);\n if (!entry) return;\n\n const consumer = entry.consumers.get(id);\n if (!consumer) return;\n\n const { analyser, isPlaybackSink } = consumer;\n analyser.disconnect();\n entry.consumers.delete(id);\n\n if (entry.consumers.size === 0) {\n try {\n entry.source.disconnect();\n } catch {\n // ignore\n }\n void entry.context.close();\n entries.delete(element);\n return;\n }\n\n if (isPlaybackSink) {\n const first = entry.consumers.values().next().value as Consumer | undefined;\n if (first) {\n first.analyser.connect(entry.context.destination);\n first.isPlaybackSink = true;\n }\n }\n}\n","import { useCallback, useLayoutEffect, useMemo, useRef, useState } from \"react\";\nimport { useGinger } from \"../hooks/useGinger\";\nimport { attachLiveAnalyser, detachLiveAnalyser, type LiveAnalyserOptions } from \"./liveAudioGraph\";\n\nexport type UseGingerLiveAnalyzerOptions = {\n /** When false, the analyser is detached and no frames are read. Default true. */\n enabled?: boolean;\n fftSize?: number;\n smoothingTimeConstant?: number;\n minDecibels?: number;\n maxDecibels?: number;\n};\n\nexport type UseGingerLiveAnalyzerResult = {\n /** Byte frequency data (0–255); length equals `frequencyBinCount`. Updated each animation frame while enabled. */\n frequencyData: Uint8Array;\n /** Byte time-domain data (0–255); length equals `fftSize`. */\n timeDomainData: Uint8Array;\n frequencyBinCount: number;\n sampleRate: number;\n isSuspended: boolean;\n error: string | null;\n resume: () => Promise<void>;\n};\n\nconst emptyFreq = new Uint8Array(0);\nconst emptyTime = new Uint8Array(0);\n\nexport function useGingerLiveAnalyzer(options: UseGingerLiveAnalyzerOptions = {}): UseGingerLiveAnalyzerResult {\n const {\n enabled = true,\n fftSize = 2048,\n smoothingTimeConstant = 0.8,\n minDecibels = -100,\n maxDecibels = -30,\n } = options;\n\n const { audioRef, state } = useGinger();\n const opts = useMemo<LiveAnalyserOptions>(\n () => ({\n fftSize,\n smoothingTimeConstant,\n minDecibels,\n maxDecibels,\n }),\n [fftSize, smoothingTimeConstant, minDecibels, maxDecibels],\n );\n\n const [frame, setFrame] = useState(0);\n const [error, setError] = useState<string | null>(null);\n const [isSuspended, setIsSuspended] = useState(false);\n const [meta, setMeta] = useState({ frequencyBinCount: 0, sampleRate: 0 });\n\n const freqRef = useRef<Uint8Array>(emptyFreq);\n const timeRef = useRef<Uint8Array>(emptyTime);\n\n const resume = useCallback(async () => {\n const ctx = contextHolderRef.current;\n if (ctx && ctx.state === \"suspended\") {\n await ctx.resume();\n }\n }, []);\n\n const contextHolderRef = useRef<AudioContext | null>(null);\n const analyserHolderRef = useRef<AnalyserNode | null>(null);\n\n useLayoutEffect(() => {\n if (!enabled || typeof window === \"undefined\") {\n return;\n }\n\n let cancelled = false;\n let consumerId: number | null = null;\n let element: HTMLAudioElement | null = null;\n let rafId = 0;\n\n const onStateChange = () => {\n const ctx = contextHolderRef.current;\n if (ctx) setIsSuspended(ctx.state === \"suspended\");\n };\n\n const runLoop = () => {\n if (cancelled) return;\n const a = analyserHolderRef.current;\n const fq = freqRef.current;\n const td = timeRef.current;\n if (a && fq.length > 0 && td.length > 0) {\n a.getByteFrequencyData(fq as Uint8Array<ArrayBuffer>);\n a.getByteTimeDomainData(td as Uint8Array<ArrayBuffer>);\n setFrame((n) => n + 1);\n }\n rafId = requestAnimationFrame(runLoop);\n };\n\n type AttachOutcome = \"ok\" | \"no-element\" | \"error\";\n\n const attach = (): AttachOutcome => {\n const el = audioRef.current;\n if (!el || cancelled) return \"no-element\";\n try {\n const { id, context, analyser } = attachLiveAnalyser(el, opts);\n consumerId = id;\n element = el;\n contextHolderRef.current = context;\n analyserHolderRef.current = analyser;\n setIsSuspended(context.state === \"suspended\");\n setError(null);\n\n context.addEventListener(\"statechange\", onStateChange);\n\n const n = analyser.frequencyBinCount;\n const fft = analyser.fftSize;\n freqRef.current = new Uint8Array(n);\n timeRef.current = new Uint8Array(fft);\n setMeta({ frequencyBinCount: n, sampleRate: context.sampleRate });\n\n rafId = requestAnimationFrame(runLoop);\n return \"ok\";\n } catch (e) {\n const msg = e instanceof Error ? e.message : \"Failed to attach live analyser\";\n setError(msg);\n contextHolderRef.current = null;\n analyserHolderRef.current = null;\n freqRef.current = emptyFreq;\n timeRef.current = emptyTime;\n setMeta({ frequencyBinCount: 0, sampleRate: 0 });\n return \"error\";\n }\n };\n\n const first = attach();\n if (first !== \"ok\") {\n let retryRaf = 0;\n const maxAttempts = 120;\n let attempts = 0;\n\n const retryLoop = () => {\n if (cancelled) return;\n const out = attach();\n if (out === \"ok\" || out === \"error\") return;\n attempts += 1;\n if (attempts >= maxAttempts) return;\n retryRaf = requestAnimationFrame(retryLoop);\n };\n\n if (first === \"no-element\") {\n retryRaf = requestAnimationFrame(retryLoop);\n }\n\n return () => {\n cancelled = true;\n cancelAnimationFrame(retryRaf);\n cancelAnimationFrame(rafId);\n if (consumerId != null && element) {\n detachLiveAnalyser(element, consumerId);\n }\n contextHolderRef.current?.removeEventListener(\"statechange\", onStateChange);\n contextHolderRef.current = null;\n analyserHolderRef.current = null;\n freqRef.current = emptyFreq;\n timeRef.current = emptyTime;\n };\n }\n\n return () => {\n cancelled = true;\n cancelAnimationFrame(rafId);\n if (consumerId != null && element) {\n detachLiveAnalyser(element, consumerId);\n }\n contextHolderRef.current?.removeEventListener(\"statechange\", onStateChange);\n contextHolderRef.current = null;\n analyserHolderRef.current = null;\n freqRef.current = emptyFreq;\n timeRef.current = emptyTime;\n setMeta({ frequencyBinCount: 0, sampleRate: 0 });\n };\n }, [enabled, audioRef, opts, state.currentIndex]);\n\n void frame;\n\n return {\n frequencyData: freqRef.current,\n timeDomainData: timeRef.current,\n frequencyBinCount: meta.frequencyBinCount,\n sampleRate: meta.sampleRate,\n isSuspended,\n error,\n resume,\n };\n}\n","import { useEffect } from \"react\";\nimport { useGingerMedia, useGingerPlayback } from \"../context/GingerSplitContexts\";\n\nexport type GingerKeyboardShortcutBindings = {\n playPause?: string;\n next?: string;\n previous?: string;\n mute?: string;\n};\n\nexport function useGingerKeyboardShortcuts(\n enabled = true,\n bindings: GingerKeyboardShortcutBindings = {},\n): void {\n const { togglePlayPause, next, prev } = useGingerPlayback();\n const { toggleMute } = useGingerMedia();\n\n const muteBinding = bindings.mute;\n\n useEffect(() => {\n if (!enabled || typeof window === \"undefined\") return;\n const playPause = (bindings.playPause ?? \" \").toLowerCase();\n const nextKey = (bindings.next ?? \"ArrowRight\").toLowerCase();\n const prevKey = (bindings.previous ?? \"ArrowLeft\").toLowerCase();\n const muteKey = muteBinding?.toLowerCase();\n\n const onKeyDown = (event: KeyboardEvent) => {\n const target = event.target as HTMLElement | null;\n if (target && ([\"INPUT\", \"TEXTAREA\", \"SELECT\"].includes(target.tagName) || target.isContentEditable)) return;\n const key = event.key.toLowerCase();\n if (key === playPause) {\n event.preventDefault();\n togglePlayPause();\n } else if (key === nextKey) {\n event.preventDefault();\n next();\n } else if (key === prevKey) {\n event.preventDefault();\n prev();\n } else if (muteKey && key === muteKey) {\n event.preventDefault();\n toggleMute();\n }\n };\n window.addEventListener(\"keydown\", onKeyDown);\n return () => window.removeEventListener(\"keydown\", onKeyDown);\n }, [bindings.next, bindings.playPause, bindings.previous, enabled, muteBinding, next, prev, toggleMute, togglePlayPause]);\n}\n","import { useEffect, useRef } from \"react\";\nimport { useGingerPlayback } from \"../context/GingerSplitContexts\";\n\nexport type GingerSleepTimerOptions = {\n durationMs?: number;\n stopAfterTracks?: number;\n respectPause?: boolean;\n enabled?: boolean;\n onFire?: () => void;\n};\n\nexport function useGingerSleepTimer(options: GingerSleepTimerOptions): void {\n const { durationMs, stopAfterTracks, respectPause = true, enabled = true, onFire } = options;\n const { currentIndex, pause, isPaused } = useGingerPlayback();\n const remainingTracksRef = useRef(stopAfterTracks ?? 0);\n const prevIndexRef = useRef(currentIndex);\n\n useEffect(() => {\n remainingTracksRef.current = stopAfterTracks ?? 0;\n }, [stopAfterTracks]);\n\n useEffect(() => {\n if (!enabled || !durationMs || durationMs <= 0) return;\n if (respectPause && isPaused) return;\n const id = setTimeout(() => {\n pause();\n onFire?.();\n }, durationMs);\n return () => clearTimeout(id);\n }, [durationMs, enabled, isPaused, onFire, pause, respectPause]);\n\n useEffect(() => {\n if (!enabled || !stopAfterTracks || stopAfterTracks <= 0) return;\n const prev = prevIndexRef.current;\n prevIndexRef.current = currentIndex;\n if (currentIndex === prev) return;\n remainingTracksRef.current -= 1;\n if (remainingTracksRef.current <= 0) {\n pause();\n onFire?.();\n }\n }, [currentIndex, enabled, onFire, pause, stopAfterTracks]);\n}\n","import { useEffect, useRef } from \"react\";\nimport { useGingerState } from \"../context/GingerSplitContexts\";\n\nexport function useGingerDebugLog(enabled = false): void {\n const state = useGingerState();\n const prevRef = useRef(state);\n\n useEffect(() => {\n if (!enabled || typeof console === \"undefined\") return;\n const prev = prevRef.current;\n if (prev !== state) {\n console.debug(\"[ginger]\", {\n from: {\n currentIndex: prev.currentIndex,\n isPaused: prev.isPaused,\n currentTime: prev.currentTime,\n repeatMode: prev.repeatMode,\n },\n to: {\n currentIndex: state.currentIndex,\n isPaused: state.isPaused,\n currentTime: state.currentTime,\n repeatMode: state.repeatMode,\n },\n });\n }\n prevRef.current = state;\n }, [enabled, state]);\n}\n","import { useCallback, useState } from \"react\";\nimport type { PointerEvent as ReactPointerEvent } from \"react\";\nimport { useGingerMedia, useGingerPlayback, gingerStateFromContextValues } from \"../context/GingerSplitContexts\";\nimport { progressFraction } from \"../internal/selectors\";\n\nexport type SeekDragState = {\n /** Raw drag fraction — only updated during an active drag gesture. */\n fraction: number;\n /** Blended fraction: follows live playback when idle, drag position when dragging. */\n displayFraction: number;\n isDragging: boolean;\n onPointerDown: (event: ReactPointerEvent<HTMLElement>) => void;\n};\n\nfunction clamp01(value: number): number {\n return Math.max(0, Math.min(1, value));\n}\n\nexport function useSeekDrag(duration: number): SeekDragState {\n const media = useGingerMedia();\n const playback = useGingerPlayback();\n const { seek } = media;\n const [fraction, setFraction] = useState(0);\n const [isDragging, setIsDragging] = useState(false);\n\n const liveFraction = progressFraction(gingerStateFromContextValues(playback, media));\n const displayFraction = isDragging ? fraction : liveFraction;\n\n const onPointerDown = useCallback(\n (event: ReactPointerEvent<HTMLElement>) => {\n if (!(duration > 0)) return;\n const target = event.currentTarget;\n const rect = target.getBoundingClientRect();\n const update = (clientX: number) => {\n const ratio = clamp01((clientX - rect.left) / rect.width);\n setFraction(ratio);\n seek(ratio * duration);\n };\n setIsDragging(true);\n target.setPointerCapture(event.pointerId);\n update(event.clientX);\n const onMove = (moveEvent: PointerEvent) => update(moveEvent.clientX);\n const onUp = (upEvent: PointerEvent) => {\n update(upEvent.clientX);\n setIsDragging(false);\n target.releasePointerCapture(event.pointerId);\n target.removeEventListener(\"pointermove\", onMove);\n target.removeEventListener(\"pointerup\", onUp);\n target.removeEventListener(\"pointercancel\", onUp);\n };\n target.addEventListener(\"pointermove\", onMove);\n target.addEventListener(\"pointerup\", onUp);\n target.addEventListener(\"pointercancel\", onUp);\n },\n [duration, seek],\n );\n\n return { fraction, displayFraction, isDragging, onPointerDown };\n}\n","import { useEffect } from \"react\";\nimport { useGingerPlayback } from \"../context/GingerSplitContexts\";\nimport { computeNextIndex } from \"../core/transitions\";\n\nexport type UseNextTrackPrefetchOptions = {\n /** When false, no prefetch runs. Default true. */\n enabled?: boolean;\n /**\n * Match `crossOrigin` on `Ginger.Player` when `fileUrl` is cross-origin so the browser\n * can reuse cached media consistently.\n */\n crossOrigin?: \"\" | \"anonymous\" | \"use-credentials\" | undefined;\n};\n\n/**\n * Warms the browser cache for the **logical** next track (same rules as the Next control:\n * `computeNextIndex` from queue, repeat, and playback mode) using a detached `HTMLAudioElement`\n * with `preload=\"auto\"`. Safe to call alongside `Ginger.Player`; it does not replace main playback.\n */\nexport function useNextTrackPrefetch(options: UseNextTrackPrefetchOptions = {}): void {\n const { enabled = true, crossOrigin } = options;\n const { tracks, currentIndex, repeatMode, playbackMode } = useGingerPlayback();\n\n useEffect(() => {\n if (!enabled || typeof document === \"undefined\") return;\n const nextIndex = computeNextIndex({ tracks, currentIndex, repeatMode, playbackMode });\n if (nextIndex === currentIndex) return;\n const nextUrl = tracks[nextIndex]?.fileUrl ?? \"\";\n if (!nextUrl) return;\n\n const audio = document.createElement(\"audio\");\n audio.preload = \"auto\";\n if (crossOrigin) audio.crossOrigin = crossOrigin;\n audio.src = nextUrl;\n audio.load();\n\n return () => {\n audio.removeAttribute(\"src\");\n audio.load();\n };\n }, [enabled, crossOrigin, tracks, currentIndex, repeatMode, playbackMode]);\n}\n"],"names":["useGinger","pb","useGingerPlayback","md","useGingerMedia","useMemo","state","gingerStateFromContextValues","getCurrentTrack","derivePlaybackUiState","effectiveDuration","effectiveRemaining","progressFraction","resolvedArtwork","resolvedAlbumLine","entries","clampFftSize","n","p","attachLiveAnalyser","element","options","entry","Context","context","source","analyser","isFirst","id","detachLiveAnalyser","consumer","isPlaybackSink","first","emptyFreq","emptyTime","useGingerLiveAnalyzer","enabled","fftSize","smoothingTimeConstant","minDecibels","maxDecibels","audioRef","opts","frame","setFrame","useState","error","setError","isSuspended","setIsSuspended","meta","setMeta","freqRef","useRef","timeRef","resume","useCallback","ctx","contextHolderRef","analyserHolderRef","useLayoutEffect","cancelled","consumerId","rafId","onStateChange","runLoop","a","fq","td","attach","el","fft","e","msg","retryRaf","maxAttempts","attempts","retryLoop","out","_a","useGingerKeyboardShortcuts","bindings","togglePlayPause","next","prev","toggleMute","muteBinding","useEffect","playPause","nextKey","prevKey","muteKey","onKeyDown","event","target","key","useGingerSleepTimer","durationMs","stopAfterTracks","respectPause","onFire","currentIndex","pause","isPaused","remainingTracksRef","prevIndexRef","useGingerDebugLog","useGingerState","prevRef","clamp01","value","useSeekDrag","duration","media","playback","seek","fraction","setFraction","isDragging","setIsDragging","liveFraction","displayFraction","onPointerDown","rect","update","clientX","ratio","onMove","moveEvent","onUp","upEvent","useNextTrackPrefetch","crossOrigin","tracks","repeatMode","playbackMode","nextIndex","computeNextIndex","nextUrl","audio"],"mappings":"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"}
1
+ {"version":3,"file":"useNextTrackPrefetch-CFoUynDv.cjs","sources":["../src/hooks/useGinger.ts","../src/analyzer/liveAudioGraph.ts","../src/analyzer/useGingerLiveAnalyzer.ts","../src/hooks/useGingerKeyboardShortcuts.ts","../src/hooks/useGingerSleepTimer.ts","../src/hooks/useGingerDebugLog.ts","../src/hooks/useSeekDrag.ts","../src/hooks/useNextTrackPrefetch.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { gingerStateFromContextValues, useGingerMedia, useGingerPlayback } from \"../context/GingerSplitContexts\";\nimport {\n derivePlaybackUiState,\n effectiveDuration,\n effectiveRemaining,\n getCurrentTrack,\n progressFraction,\n resolvedAlbumLine,\n resolvedArtwork,\n} from \"../internal/selectors\";\n\nexport function useGinger() {\n const pb = useGingerPlayback();\n const md = useGingerMedia();\n\n return useMemo(\n () => {\n const state = gingerStateFromContextValues(pb, md);\n return {\n state,\n currentTrack: getCurrentTrack(state),\n playbackUi: derivePlaybackUiState(state),\n duration: effectiveDuration(state),\n remaining: effectiveRemaining(state),\n progress: progressFraction(state),\n artworkUrl: resolvedArtwork(state),\n albumLine: resolvedAlbumLine(state),\n play: pb.play,\n pause: pb.pause,\n togglePlayPause: pb.togglePlayPause,\n seek: md.seek,\n setVolume: md.setVolume,\n setMuted: md.setMuted,\n toggleMute: md.toggleMute,\n setPlaybackRate: md.setPlaybackRate,\n next: pb.next,\n prev: pb.prev,\n setRepeatMode: pb.setRepeatMode,\n cycleRepeat: pb.cycleRepeat,\n toggleShuffle: pb.toggleShuffle,\n setQueue: pb.setQueue,\n insertTrackAt: pb.insertTrackAt,\n removeTrackAt: pb.removeTrackAt,\n moveTrack: pb.moveTrack,\n enqueueNext: pb.enqueueNext,\n playTrackAt: pb.playTrackAt,\n selectTrackAt: pb.selectTrackAt,\n setPlaylistMeta: pb.setPlaylistMeta,\n setPlaybackMode: pb.setPlaybackMode,\n init: pb.init,\n audioRef: md.audioRef,\n dispatch: pb.dispatch,\n };\n },\n [pb, md],\n );\n}\n","/** One MediaElementAudioSourceNode per HTMLAudioElement; multiple AnalyserNodes may tap the source. */\n\nexport type LiveAnalyserOptions = {\n fftSize: number;\n smoothingTimeConstant: number;\n minDecibels: number;\n maxDecibels: number;\n};\n\ntype Consumer = {\n analyser: AnalyserNode;\n /** This analyser is wired to `audioContext.destination` so the graph is audible. */\n isPlaybackSink: boolean;\n};\n\ntype ElementEntry = {\n context: AudioContext;\n source: MediaElementAudioSourceNode;\n consumers: Map<number, Consumer>;\n nextId: number;\n};\n\nconst entries = new WeakMap<HTMLAudioElement, ElementEntry>();\n\nfunction clampFftSize(n: number): number {\n const p = 2 ** Math.round(Math.log2(n));\n return Math.min(32768, Math.max(32, p));\n}\n\nexport function attachLiveAnalyser(\n element: HTMLAudioElement,\n options: LiveAnalyserOptions,\n): { id: number; context: AudioContext; analyser: AnalyserNode } {\n let entry = entries.get(element);\n if (!entry) {\n const Context = window.AudioContext ?? (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Context) {\n throw new Error(\"Web Audio API is not available\");\n }\n const context = new Context();\n const source = context.createMediaElementSource(element);\n entry = { context, source, consumers: new Map(), nextId: 0 };\n entries.set(element, entry);\n }\n\n const { context, source } = entry;\n const analyser = context.createAnalyser();\n analyser.fftSize = clampFftSize(options.fftSize);\n analyser.smoothingTimeConstant = options.smoothingTimeConstant;\n analyser.minDecibels = options.minDecibels;\n analyser.maxDecibels = options.maxDecibels;\n\n source.connect(analyser);\n\n const isFirst = entry.consumers.size === 0;\n if (isFirst) {\n analyser.connect(context.destination);\n }\n\n const id = entry.nextId;\n entry.nextId += 1;\n entry.consumers.set(id, { analyser, isPlaybackSink: isFirst });\n\n return { id, context, analyser };\n}\n\nexport function detachLiveAnalyser(element: HTMLAudioElement, id: number): void {\n const entry = entries.get(element);\n if (!entry) return;\n\n const consumer = entry.consumers.get(id);\n if (!consumer) return;\n\n const { analyser, isPlaybackSink } = consumer;\n analyser.disconnect();\n entry.consumers.delete(id);\n\n if (entry.consumers.size === 0) {\n try {\n entry.source.disconnect();\n } catch {\n // ignore\n }\n void entry.context.close();\n entries.delete(element);\n return;\n }\n\n if (isPlaybackSink) {\n const first = entry.consumers.values().next().value as Consumer | undefined;\n if (first) {\n first.analyser.connect(entry.context.destination);\n first.isPlaybackSink = true;\n }\n }\n}\n","import { useCallback, useLayoutEffect, useMemo, useRef, useState } from \"react\";\nimport { useGinger } from \"../hooks/useGinger\";\nimport { attachLiveAnalyser, detachLiveAnalyser, type LiveAnalyserOptions } from \"./liveAudioGraph\";\n\nexport type UseGingerLiveAnalyzerOptions = {\n /** When false, the analyser is detached and no frames are read. Default true. */\n enabled?: boolean;\n fftSize?: number;\n smoothingTimeConstant?: number;\n minDecibels?: number;\n maxDecibels?: number;\n};\n\nexport type UseGingerLiveAnalyzerResult = {\n /** Byte frequency data (0–255); length equals `frequencyBinCount`. Updated each animation frame while enabled. */\n frequencyData: Uint8Array;\n /** Byte time-domain data (0–255); length equals `fftSize`. */\n timeDomainData: Uint8Array;\n frequencyBinCount: number;\n sampleRate: number;\n isSuspended: boolean;\n error: string | null;\n resume: () => Promise<void>;\n};\n\nconst emptyFreq = new Uint8Array(0);\nconst emptyTime = new Uint8Array(0);\n\nexport function useGingerLiveAnalyzer(options: UseGingerLiveAnalyzerOptions = {}): UseGingerLiveAnalyzerResult {\n const {\n enabled = true,\n fftSize = 2048,\n smoothingTimeConstant = 0.8,\n minDecibels = -100,\n maxDecibels = -30,\n } = options;\n\n const { audioRef, state } = useGinger();\n const opts = useMemo<LiveAnalyserOptions>(\n () => ({\n fftSize,\n smoothingTimeConstant,\n minDecibels,\n maxDecibels,\n }),\n [fftSize, smoothingTimeConstant, minDecibels, maxDecibels],\n );\n\n const [frame, setFrame] = useState(0);\n const [error, setError] = useState<string | null>(null);\n const [isSuspended, setIsSuspended] = useState(false);\n const [meta, setMeta] = useState({ frequencyBinCount: 0, sampleRate: 0 });\n\n const freqRef = useRef<Uint8Array>(emptyFreq);\n const timeRef = useRef<Uint8Array>(emptyTime);\n\n const resume = useCallback(async () => {\n const ctx = contextHolderRef.current;\n if (ctx && ctx.state === \"suspended\") {\n await ctx.resume();\n }\n }, []);\n\n const contextHolderRef = useRef<AudioContext | null>(null);\n const analyserHolderRef = useRef<AnalyserNode | null>(null);\n\n useLayoutEffect(() => {\n if (!enabled || typeof window === \"undefined\") {\n return;\n }\n\n let cancelled = false;\n let consumerId: number | null = null;\n let element: HTMLAudioElement | null = null;\n let rafId = 0;\n\n const onStateChange = () => {\n const ctx = contextHolderRef.current;\n if (ctx) setIsSuspended(ctx.state === \"suspended\");\n };\n\n const runLoop = () => {\n if (cancelled) return;\n const a = analyserHolderRef.current;\n const fq = freqRef.current;\n const td = timeRef.current;\n if (a && fq.length > 0 && td.length > 0) {\n a.getByteFrequencyData(fq as Uint8Array<ArrayBuffer>);\n a.getByteTimeDomainData(td as Uint8Array<ArrayBuffer>);\n setFrame((n) => n + 1);\n }\n rafId = requestAnimationFrame(runLoop);\n };\n\n type AttachOutcome = \"ok\" | \"no-element\" | \"error\";\n\n const attach = (): AttachOutcome => {\n const el = audioRef.current;\n if (!el || cancelled) return \"no-element\";\n try {\n const { id, context, analyser } = attachLiveAnalyser(el, opts);\n consumerId = id;\n element = el;\n contextHolderRef.current = context;\n analyserHolderRef.current = analyser;\n setIsSuspended(context.state === \"suspended\");\n setError(null);\n\n context.addEventListener(\"statechange\", onStateChange);\n\n const n = analyser.frequencyBinCount;\n const fft = analyser.fftSize;\n freqRef.current = new Uint8Array(n);\n timeRef.current = new Uint8Array(fft);\n setMeta({ frequencyBinCount: n, sampleRate: context.sampleRate });\n\n rafId = requestAnimationFrame(runLoop);\n return \"ok\";\n } catch (e) {\n const msg = e instanceof Error ? e.message : \"Failed to attach live analyser\";\n setError(msg);\n contextHolderRef.current = null;\n analyserHolderRef.current = null;\n freqRef.current = emptyFreq;\n timeRef.current = emptyTime;\n setMeta({ frequencyBinCount: 0, sampleRate: 0 });\n return \"error\";\n }\n };\n\n const first = attach();\n if (first !== \"ok\") {\n let retryRaf = 0;\n const maxAttempts = 120;\n let attempts = 0;\n\n const retryLoop = () => {\n if (cancelled) return;\n const out = attach();\n if (out === \"ok\" || out === \"error\") return;\n attempts += 1;\n if (attempts >= maxAttempts) return;\n retryRaf = requestAnimationFrame(retryLoop);\n };\n\n if (first === \"no-element\") {\n retryRaf = requestAnimationFrame(retryLoop);\n }\n\n return () => {\n cancelled = true;\n cancelAnimationFrame(retryRaf);\n cancelAnimationFrame(rafId);\n if (consumerId != null && element) {\n detachLiveAnalyser(element, consumerId);\n }\n contextHolderRef.current?.removeEventListener(\"statechange\", onStateChange);\n contextHolderRef.current = null;\n analyserHolderRef.current = null;\n freqRef.current = emptyFreq;\n timeRef.current = emptyTime;\n };\n }\n\n return () => {\n cancelled = true;\n cancelAnimationFrame(rafId);\n if (consumerId != null && element) {\n detachLiveAnalyser(element, consumerId);\n }\n contextHolderRef.current?.removeEventListener(\"statechange\", onStateChange);\n contextHolderRef.current = null;\n analyserHolderRef.current = null;\n freqRef.current = emptyFreq;\n timeRef.current = emptyTime;\n setMeta({ frequencyBinCount: 0, sampleRate: 0 });\n };\n }, [enabled, audioRef, opts, state.currentIndex]);\n\n void frame;\n\n return {\n frequencyData: freqRef.current,\n timeDomainData: timeRef.current,\n frequencyBinCount: meta.frequencyBinCount,\n sampleRate: meta.sampleRate,\n isSuspended,\n error,\n resume,\n };\n}\n","import { useEffect } from \"react\";\nimport { useGingerMedia, useGingerPlayback } from \"../context/GingerSplitContexts\";\n\nexport type GingerKeyboardShortcutBindings = {\n playPause?: string;\n next?: string;\n previous?: string;\n mute?: string;\n};\n\nexport function useGingerKeyboardShortcuts(\n enabled = true,\n bindings: GingerKeyboardShortcutBindings = {},\n): void {\n const { togglePlayPause, next, prev } = useGingerPlayback();\n const { toggleMute } = useGingerMedia();\n\n const muteBinding = bindings.mute;\n\n useEffect(() => {\n if (!enabled || typeof window === \"undefined\") return;\n const playPause = (bindings.playPause ?? \" \").toLowerCase();\n const nextKey = (bindings.next ?? \"ArrowRight\").toLowerCase();\n const prevKey = (bindings.previous ?? \"ArrowLeft\").toLowerCase();\n const muteKey = muteBinding?.toLowerCase();\n\n const onKeyDown = (event: KeyboardEvent) => {\n const target = event.target as HTMLElement | null;\n if (target && ([\"INPUT\", \"TEXTAREA\", \"SELECT\"].includes(target.tagName) || target.isContentEditable)) return;\n const key = event.key.toLowerCase();\n if (key === playPause) {\n event.preventDefault();\n togglePlayPause();\n } else if (key === nextKey) {\n event.preventDefault();\n next();\n } else if (key === prevKey) {\n event.preventDefault();\n prev();\n } else if (muteKey && key === muteKey) {\n event.preventDefault();\n toggleMute();\n }\n };\n window.addEventListener(\"keydown\", onKeyDown);\n return () => window.removeEventListener(\"keydown\", onKeyDown);\n }, [bindings.next, bindings.playPause, bindings.previous, enabled, muteBinding, next, prev, toggleMute, togglePlayPause]);\n}\n","import { useEffect, useRef } from \"react\";\nimport { useGingerPlayback } from \"../context/GingerSplitContexts\";\n\nexport type GingerSleepTimerOptions = {\n durationMs?: number;\n stopAfterTracks?: number;\n respectPause?: boolean;\n enabled?: boolean;\n onFire?: () => void;\n};\n\nexport function useGingerSleepTimer(options: GingerSleepTimerOptions): void {\n const { durationMs, stopAfterTracks, respectPause = true, enabled = true, onFire } = options;\n const { currentIndex, pause, isPaused } = useGingerPlayback();\n const remainingTracksRef = useRef(stopAfterTracks ?? 0);\n const prevIndexRef = useRef(currentIndex);\n\n useEffect(() => {\n remainingTracksRef.current = stopAfterTracks ?? 0;\n }, [stopAfterTracks]);\n\n useEffect(() => {\n if (!enabled || !durationMs || durationMs <= 0) return;\n if (respectPause && isPaused) return;\n const id = setTimeout(() => {\n pause();\n onFire?.();\n }, durationMs);\n return () => clearTimeout(id);\n }, [durationMs, enabled, isPaused, onFire, pause, respectPause]);\n\n useEffect(() => {\n if (!enabled || !stopAfterTracks || stopAfterTracks <= 0) return;\n const prev = prevIndexRef.current;\n prevIndexRef.current = currentIndex;\n if (currentIndex === prev) return;\n remainingTracksRef.current -= 1;\n if (remainingTracksRef.current <= 0) {\n pause();\n onFire?.();\n }\n }, [currentIndex, enabled, onFire, pause, stopAfterTracks]);\n}\n","import { useEffect, useRef } from \"react\";\nimport { useGingerState } from \"../context/GingerSplitContexts\";\n\nexport function useGingerDebugLog(enabled = false): void {\n const state = useGingerState();\n const prevRef = useRef(state);\n\n useEffect(() => {\n if (!enabled || typeof console === \"undefined\") return;\n const prev = prevRef.current;\n if (prev !== state) {\n console.debug(\"[ginger]\", {\n from: {\n currentIndex: prev.currentIndex,\n isPaused: prev.isPaused,\n currentTime: prev.currentTime,\n repeatMode: prev.repeatMode,\n },\n to: {\n currentIndex: state.currentIndex,\n isPaused: state.isPaused,\n currentTime: state.currentTime,\n repeatMode: state.repeatMode,\n },\n });\n }\n prevRef.current = state;\n }, [enabled, state]);\n}\n","import { useCallback, useState } from \"react\";\nimport type { PointerEvent as ReactPointerEvent } from \"react\";\nimport { useGingerMedia, useGingerPlayback, gingerStateFromContextValues } from \"../context/GingerSplitContexts\";\nimport { progressFraction } from \"../internal/selectors\";\n\nexport type SeekDragState = {\n /** Raw drag fraction — only updated during an active drag gesture. */\n fraction: number;\n /** Blended fraction: follows live playback when idle, drag position when dragging. */\n displayFraction: number;\n isDragging: boolean;\n onPointerDown: (event: ReactPointerEvent<HTMLElement>) => void;\n};\n\nfunction clamp01(value: number): number {\n return Math.max(0, Math.min(1, value));\n}\n\nexport function useSeekDrag(duration: number): SeekDragState {\n const media = useGingerMedia();\n const playback = useGingerPlayback();\n const { seek } = media;\n const [fraction, setFraction] = useState(0);\n const [isDragging, setIsDragging] = useState(false);\n\n const liveFraction = progressFraction(gingerStateFromContextValues(playback, media));\n const displayFraction = isDragging ? fraction : liveFraction;\n\n const onPointerDown = useCallback(\n (event: ReactPointerEvent<HTMLElement>) => {\n if (!(duration > 0)) return;\n const target = event.currentTarget;\n const rect = target.getBoundingClientRect();\n const update = (clientX: number) => {\n const ratio = clamp01((clientX - rect.left) / rect.width);\n setFraction(ratio);\n seek(ratio * duration);\n };\n setIsDragging(true);\n target.setPointerCapture(event.pointerId);\n update(event.clientX);\n const onMove = (moveEvent: PointerEvent) => update(moveEvent.clientX);\n const onUp = (upEvent: PointerEvent) => {\n update(upEvent.clientX);\n setIsDragging(false);\n target.releasePointerCapture(event.pointerId);\n target.removeEventListener(\"pointermove\", onMove);\n target.removeEventListener(\"pointerup\", onUp);\n target.removeEventListener(\"pointercancel\", onUp);\n };\n target.addEventListener(\"pointermove\", onMove);\n target.addEventListener(\"pointerup\", onUp);\n target.addEventListener(\"pointercancel\", onUp);\n },\n [duration, seek],\n );\n\n return { fraction, displayFraction, isDragging, onPointerDown };\n}\n","import { useEffect } from \"react\";\nimport { useGingerPlayback } from \"../context/GingerSplitContexts\";\nimport { computeNextIndex } from \"../core/transitions\";\n\nexport type UseNextTrackPrefetchOptions = {\n /** When false, no prefetch runs. Default true. */\n enabled?: boolean;\n /**\n * Match `crossOrigin` on `Ginger.Player` when `fileUrl` is cross-origin so the browser\n * can reuse cached media consistently.\n */\n crossOrigin?: \"\" | \"anonymous\" | \"use-credentials\" | undefined;\n};\n\n/**\n * Warms the browser cache for the **logical** next track (same rules as the Next control:\n * `computeNextIndex` from queue, repeat, and playback mode) using a detached `HTMLAudioElement`\n * with `preload=\"auto\"`. Safe to call alongside `Ginger.Player`; it does not replace main playback.\n */\nexport function useNextTrackPrefetch(options: UseNextTrackPrefetchOptions = {}): void {\n const { enabled = true, crossOrigin } = options;\n const { tracks, currentIndex, repeatMode, playbackMode } = useGingerPlayback();\n\n useEffect(() => {\n if (!enabled || typeof document === \"undefined\") return;\n const nextIndex = computeNextIndex({ tracks, currentIndex, repeatMode, playbackMode });\n if (nextIndex === currentIndex) return;\n const nextUrl = tracks[nextIndex]?.fileUrl ?? \"\";\n if (!nextUrl) return;\n\n const audio = document.createElement(\"audio\");\n audio.preload = \"auto\";\n if (crossOrigin) audio.crossOrigin = crossOrigin;\n audio.src = nextUrl;\n audio.load();\n\n return () => {\n audio.removeAttribute(\"src\");\n audio.load();\n };\n }, [enabled, crossOrigin, tracks, currentIndex, repeatMode, playbackMode]);\n}\n"],"names":["useGinger","pb","useGingerPlayback","md","useGingerMedia","useMemo","state","gingerStateFromContextValues","getCurrentTrack","derivePlaybackUiState","effectiveDuration","effectiveRemaining","progressFraction","resolvedArtwork","resolvedAlbumLine","entries","clampFftSize","n","p","attachLiveAnalyser","element","options","entry","Context","context","source","analyser","isFirst","id","detachLiveAnalyser","consumer","isPlaybackSink","first","emptyFreq","emptyTime","useGingerLiveAnalyzer","enabled","fftSize","smoothingTimeConstant","minDecibels","maxDecibels","audioRef","opts","frame","setFrame","useState","error","setError","isSuspended","setIsSuspended","meta","setMeta","freqRef","useRef","timeRef","resume","useCallback","ctx","contextHolderRef","analyserHolderRef","useLayoutEffect","cancelled","consumerId","rafId","onStateChange","runLoop","a","fq","td","attach","el","fft","e","msg","retryRaf","maxAttempts","attempts","retryLoop","out","_a","useGingerKeyboardShortcuts","bindings","togglePlayPause","next","prev","toggleMute","muteBinding","useEffect","playPause","nextKey","prevKey","muteKey","onKeyDown","event","target","key","useGingerSleepTimer","durationMs","stopAfterTracks","respectPause","onFire","currentIndex","pause","isPaused","remainingTracksRef","prevIndexRef","useGingerDebugLog","useGingerState","prevRef","clamp01","value","useSeekDrag","duration","media","playback","seek","fraction","setFraction","isDragging","setIsDragging","liveFraction","displayFraction","onPointerDown","rect","update","clientX","ratio","onMove","moveEvent","onUp","upEvent","useNextTrackPrefetch","crossOrigin","tracks","repeatMode","playbackMode","nextIndex","computeNextIndex","nextUrl","audio"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { useMemo as V, useState as R, useRef as M, useCallback as H, useLayoutEffect as J, useEffect as b } from "react";
2
2
  import { b as S, u as z, g as O, c as Y } from "./GingerSplitContexts-BzBExb95.js";
3
- import { r as Z, j as _, k as Q, l as $, m as ee, b as te, n as ne, o as re } from "./ginger-F4UDi2Qf.js";
3
+ import { r as Z, j as _, k as Q, l as $, m as ee, b as te, n as ne, o as re } from "./ginger-8x4WzVmw.js";
4
4
  function se() {
5
5
  const t = S(), e = z();
6
6
  return V(
@@ -254,4 +254,4 @@ export {
254
254
  ye as h,
255
255
  se as u
256
256
  };
257
- //# sourceMappingURL=useNextTrackPrefetch-gKls_h_N.js.map
257
+ //# sourceMappingURL=useNextTrackPrefetch-CV1khU0h.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useNextTrackPrefetch-gKls_h_N.js","sources":["../src/hooks/useGinger.ts","../src/analyzer/liveAudioGraph.ts","../src/analyzer/useGingerLiveAnalyzer.ts","../src/hooks/useGingerKeyboardShortcuts.ts","../src/hooks/useGingerSleepTimer.ts","../src/hooks/useGingerDebugLog.ts","../src/hooks/useSeekDrag.ts","../src/hooks/useNextTrackPrefetch.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { gingerStateFromContextValues, useGingerMedia, useGingerPlayback } from \"../context/GingerSplitContexts\";\nimport {\n derivePlaybackUiState,\n effectiveDuration,\n effectiveRemaining,\n getCurrentTrack,\n progressFraction,\n resolvedAlbumLine,\n resolvedArtwork,\n} from \"../internal/selectors\";\n\nexport function useGinger() {\n const pb = useGingerPlayback();\n const md = useGingerMedia();\n\n return useMemo(\n () => {\n const state = gingerStateFromContextValues(pb, md);\n return {\n state,\n currentTrack: getCurrentTrack(state),\n playbackUi: derivePlaybackUiState(state),\n duration: effectiveDuration(state),\n remaining: effectiveRemaining(state),\n progress: progressFraction(state),\n artworkUrl: resolvedArtwork(state),\n albumLine: resolvedAlbumLine(state),\n play: pb.play,\n pause: pb.pause,\n togglePlayPause: pb.togglePlayPause,\n seek: md.seek,\n setVolume: md.setVolume,\n setMuted: md.setMuted,\n toggleMute: md.toggleMute,\n setPlaybackRate: md.setPlaybackRate,\n next: pb.next,\n prev: pb.prev,\n setRepeatMode: pb.setRepeatMode,\n cycleRepeat: pb.cycleRepeat,\n toggleShuffle: pb.toggleShuffle,\n setQueue: pb.setQueue,\n insertTrackAt: pb.insertTrackAt,\n removeTrackAt: pb.removeTrackAt,\n moveTrack: pb.moveTrack,\n enqueueNext: pb.enqueueNext,\n playTrackAt: pb.playTrackAt,\n selectTrackAt: pb.selectTrackAt,\n setPlaylistMeta: pb.setPlaylistMeta,\n setPlaybackMode: pb.setPlaybackMode,\n init: pb.init,\n audioRef: md.audioRef,\n dispatch: pb.dispatch,\n };\n },\n [pb, md],\n );\n}\n","/** One MediaElementAudioSourceNode per HTMLAudioElement; multiple AnalyserNodes may tap the source. */\n\nexport type LiveAnalyserOptions = {\n fftSize: number;\n smoothingTimeConstant: number;\n minDecibels: number;\n maxDecibels: number;\n};\n\ntype Consumer = {\n analyser: AnalyserNode;\n /** This analyser is wired to `audioContext.destination` so the graph is audible. */\n isPlaybackSink: boolean;\n};\n\ntype ElementEntry = {\n context: AudioContext;\n source: MediaElementAudioSourceNode;\n consumers: Map<number, Consumer>;\n nextId: number;\n};\n\nconst entries = new WeakMap<HTMLAudioElement, ElementEntry>();\n\nfunction clampFftSize(n: number): number {\n const p = 2 ** Math.round(Math.log2(n));\n return Math.min(32768, Math.max(32, p));\n}\n\nexport function attachLiveAnalyser(\n element: HTMLAudioElement,\n options: LiveAnalyserOptions,\n): { id: number; context: AudioContext; analyser: AnalyserNode } {\n let entry = entries.get(element);\n if (!entry) {\n const Context = window.AudioContext ?? (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Context) {\n throw new Error(\"Web Audio API is not available\");\n }\n const context = new Context();\n const source = context.createMediaElementSource(element);\n entry = { context, source, consumers: new Map(), nextId: 0 };\n entries.set(element, entry);\n }\n\n const { context, source } = entry;\n const analyser = context.createAnalyser();\n analyser.fftSize = clampFftSize(options.fftSize);\n analyser.smoothingTimeConstant = options.smoothingTimeConstant;\n analyser.minDecibels = options.minDecibels;\n analyser.maxDecibels = options.maxDecibels;\n\n source.connect(analyser);\n\n const isFirst = entry.consumers.size === 0;\n if (isFirst) {\n analyser.connect(context.destination);\n }\n\n const id = entry.nextId;\n entry.nextId += 1;\n entry.consumers.set(id, { analyser, isPlaybackSink: isFirst });\n\n return { id, context, analyser };\n}\n\nexport function detachLiveAnalyser(element: HTMLAudioElement, id: number): void {\n const entry = entries.get(element);\n if (!entry) return;\n\n const consumer = entry.consumers.get(id);\n if (!consumer) return;\n\n const { analyser, isPlaybackSink } = consumer;\n analyser.disconnect();\n entry.consumers.delete(id);\n\n if (entry.consumers.size === 0) {\n try {\n entry.source.disconnect();\n } catch {\n // ignore\n }\n void entry.context.close();\n entries.delete(element);\n return;\n }\n\n if (isPlaybackSink) {\n const first = entry.consumers.values().next().value as Consumer | undefined;\n if (first) {\n first.analyser.connect(entry.context.destination);\n first.isPlaybackSink = true;\n }\n }\n}\n","import { useCallback, useLayoutEffect, useMemo, useRef, useState } from \"react\";\nimport { useGinger } from \"../hooks/useGinger\";\nimport { attachLiveAnalyser, detachLiveAnalyser, type LiveAnalyserOptions } from \"./liveAudioGraph\";\n\nexport type UseGingerLiveAnalyzerOptions = {\n /** When false, the analyser is detached and no frames are read. Default true. */\n enabled?: boolean;\n fftSize?: number;\n smoothingTimeConstant?: number;\n minDecibels?: number;\n maxDecibels?: number;\n};\n\nexport type UseGingerLiveAnalyzerResult = {\n /** Byte frequency data (0–255); length equals `frequencyBinCount`. Updated each animation frame while enabled. */\n frequencyData: Uint8Array;\n /** Byte time-domain data (0–255); length equals `fftSize`. */\n timeDomainData: Uint8Array;\n frequencyBinCount: number;\n sampleRate: number;\n isSuspended: boolean;\n error: string | null;\n resume: () => Promise<void>;\n};\n\nconst emptyFreq = new Uint8Array(0);\nconst emptyTime = new Uint8Array(0);\n\nexport function useGingerLiveAnalyzer(options: UseGingerLiveAnalyzerOptions = {}): UseGingerLiveAnalyzerResult {\n const {\n enabled = true,\n fftSize = 2048,\n smoothingTimeConstant = 0.8,\n minDecibels = -100,\n maxDecibels = -30,\n } = options;\n\n const { audioRef, state } = useGinger();\n const opts = useMemo<LiveAnalyserOptions>(\n () => ({\n fftSize,\n smoothingTimeConstant,\n minDecibels,\n maxDecibels,\n }),\n [fftSize, smoothingTimeConstant, minDecibels, maxDecibels],\n );\n\n const [frame, setFrame] = useState(0);\n const [error, setError] = useState<string | null>(null);\n const [isSuspended, setIsSuspended] = useState(false);\n const [meta, setMeta] = useState({ frequencyBinCount: 0, sampleRate: 0 });\n\n const freqRef = useRef<Uint8Array>(emptyFreq);\n const timeRef = useRef<Uint8Array>(emptyTime);\n\n const resume = useCallback(async () => {\n const ctx = contextHolderRef.current;\n if (ctx && ctx.state === \"suspended\") {\n await ctx.resume();\n }\n }, []);\n\n const contextHolderRef = useRef<AudioContext | null>(null);\n const analyserHolderRef = useRef<AnalyserNode | null>(null);\n\n useLayoutEffect(() => {\n if (!enabled || typeof window === \"undefined\") {\n return;\n }\n\n let cancelled = false;\n let consumerId: number | null = null;\n let element: HTMLAudioElement | null = null;\n let rafId = 0;\n\n const onStateChange = () => {\n const ctx = contextHolderRef.current;\n if (ctx) setIsSuspended(ctx.state === \"suspended\");\n };\n\n const runLoop = () => {\n if (cancelled) return;\n const a = analyserHolderRef.current;\n const fq = freqRef.current;\n const td = timeRef.current;\n if (a && fq.length > 0 && td.length > 0) {\n a.getByteFrequencyData(fq as Uint8Array<ArrayBuffer>);\n a.getByteTimeDomainData(td as Uint8Array<ArrayBuffer>);\n setFrame((n) => n + 1);\n }\n rafId = requestAnimationFrame(runLoop);\n };\n\n type AttachOutcome = \"ok\" | \"no-element\" | \"error\";\n\n const attach = (): AttachOutcome => {\n const el = audioRef.current;\n if (!el || cancelled) return \"no-element\";\n try {\n const { id, context, analyser } = attachLiveAnalyser(el, opts);\n consumerId = id;\n element = el;\n contextHolderRef.current = context;\n analyserHolderRef.current = analyser;\n setIsSuspended(context.state === \"suspended\");\n setError(null);\n\n context.addEventListener(\"statechange\", onStateChange);\n\n const n = analyser.frequencyBinCount;\n const fft = analyser.fftSize;\n freqRef.current = new Uint8Array(n);\n timeRef.current = new Uint8Array(fft);\n setMeta({ frequencyBinCount: n, sampleRate: context.sampleRate });\n\n rafId = requestAnimationFrame(runLoop);\n return \"ok\";\n } catch (e) {\n const msg = e instanceof Error ? e.message : \"Failed to attach live analyser\";\n setError(msg);\n contextHolderRef.current = null;\n analyserHolderRef.current = null;\n freqRef.current = emptyFreq;\n timeRef.current = emptyTime;\n setMeta({ frequencyBinCount: 0, sampleRate: 0 });\n return \"error\";\n }\n };\n\n const first = attach();\n if (first !== \"ok\") {\n let retryRaf = 0;\n const maxAttempts = 120;\n let attempts = 0;\n\n const retryLoop = () => {\n if (cancelled) return;\n const out = attach();\n if (out === \"ok\" || out === \"error\") return;\n attempts += 1;\n if (attempts >= maxAttempts) return;\n retryRaf = requestAnimationFrame(retryLoop);\n };\n\n if (first === \"no-element\") {\n retryRaf = requestAnimationFrame(retryLoop);\n }\n\n return () => {\n cancelled = true;\n cancelAnimationFrame(retryRaf);\n cancelAnimationFrame(rafId);\n if (consumerId != null && element) {\n detachLiveAnalyser(element, consumerId);\n }\n contextHolderRef.current?.removeEventListener(\"statechange\", onStateChange);\n contextHolderRef.current = null;\n analyserHolderRef.current = null;\n freqRef.current = emptyFreq;\n timeRef.current = emptyTime;\n };\n }\n\n return () => {\n cancelled = true;\n cancelAnimationFrame(rafId);\n if (consumerId != null && element) {\n detachLiveAnalyser(element, consumerId);\n }\n contextHolderRef.current?.removeEventListener(\"statechange\", onStateChange);\n contextHolderRef.current = null;\n analyserHolderRef.current = null;\n freqRef.current = emptyFreq;\n timeRef.current = emptyTime;\n setMeta({ frequencyBinCount: 0, sampleRate: 0 });\n };\n }, [enabled, audioRef, opts, state.currentIndex]);\n\n void frame;\n\n return {\n frequencyData: freqRef.current,\n timeDomainData: timeRef.current,\n frequencyBinCount: meta.frequencyBinCount,\n sampleRate: meta.sampleRate,\n isSuspended,\n error,\n resume,\n };\n}\n","import { useEffect } from \"react\";\nimport { useGingerMedia, useGingerPlayback } from \"../context/GingerSplitContexts\";\n\nexport type GingerKeyboardShortcutBindings = {\n playPause?: string;\n next?: string;\n previous?: string;\n mute?: string;\n};\n\nexport function useGingerKeyboardShortcuts(\n enabled = true,\n bindings: GingerKeyboardShortcutBindings = {},\n): void {\n const { togglePlayPause, next, prev } = useGingerPlayback();\n const { toggleMute } = useGingerMedia();\n\n const muteBinding = bindings.mute;\n\n useEffect(() => {\n if (!enabled || typeof window === \"undefined\") return;\n const playPause = (bindings.playPause ?? \" \").toLowerCase();\n const nextKey = (bindings.next ?? \"ArrowRight\").toLowerCase();\n const prevKey = (bindings.previous ?? \"ArrowLeft\").toLowerCase();\n const muteKey = muteBinding?.toLowerCase();\n\n const onKeyDown = (event: KeyboardEvent) => {\n const target = event.target as HTMLElement | null;\n if (target && ([\"INPUT\", \"TEXTAREA\", \"SELECT\"].includes(target.tagName) || target.isContentEditable)) return;\n const key = event.key.toLowerCase();\n if (key === playPause) {\n event.preventDefault();\n togglePlayPause();\n } else if (key === nextKey) {\n event.preventDefault();\n next();\n } else if (key === prevKey) {\n event.preventDefault();\n prev();\n } else if (muteKey && key === muteKey) {\n event.preventDefault();\n toggleMute();\n }\n };\n window.addEventListener(\"keydown\", onKeyDown);\n return () => window.removeEventListener(\"keydown\", onKeyDown);\n }, [bindings.next, bindings.playPause, bindings.previous, enabled, muteBinding, next, prev, toggleMute, togglePlayPause]);\n}\n","import { useEffect, useRef } from \"react\";\nimport { useGingerPlayback } from \"../context/GingerSplitContexts\";\n\nexport type GingerSleepTimerOptions = {\n durationMs?: number;\n stopAfterTracks?: number;\n respectPause?: boolean;\n enabled?: boolean;\n onFire?: () => void;\n};\n\nexport function useGingerSleepTimer(options: GingerSleepTimerOptions): void {\n const { durationMs, stopAfterTracks, respectPause = true, enabled = true, onFire } = options;\n const { currentIndex, pause, isPaused } = useGingerPlayback();\n const remainingTracksRef = useRef(stopAfterTracks ?? 0);\n const prevIndexRef = useRef(currentIndex);\n\n useEffect(() => {\n remainingTracksRef.current = stopAfterTracks ?? 0;\n }, [stopAfterTracks]);\n\n useEffect(() => {\n if (!enabled || !durationMs || durationMs <= 0) return;\n if (respectPause && isPaused) return;\n const id = setTimeout(() => {\n pause();\n onFire?.();\n }, durationMs);\n return () => clearTimeout(id);\n }, [durationMs, enabled, isPaused, onFire, pause, respectPause]);\n\n useEffect(() => {\n if (!enabled || !stopAfterTracks || stopAfterTracks <= 0) return;\n const prev = prevIndexRef.current;\n prevIndexRef.current = currentIndex;\n if (currentIndex === prev) return;\n remainingTracksRef.current -= 1;\n if (remainingTracksRef.current <= 0) {\n pause();\n onFire?.();\n }\n }, [currentIndex, enabled, onFire, pause, stopAfterTracks]);\n}\n","import { useEffect, useRef } from \"react\";\nimport { useGingerState } from \"../context/GingerSplitContexts\";\n\nexport function useGingerDebugLog(enabled = false): void {\n const state = useGingerState();\n const prevRef = useRef(state);\n\n useEffect(() => {\n if (!enabled || typeof console === \"undefined\") return;\n const prev = prevRef.current;\n if (prev !== state) {\n console.debug(\"[ginger]\", {\n from: {\n currentIndex: prev.currentIndex,\n isPaused: prev.isPaused,\n currentTime: prev.currentTime,\n repeatMode: prev.repeatMode,\n },\n to: {\n currentIndex: state.currentIndex,\n isPaused: state.isPaused,\n currentTime: state.currentTime,\n repeatMode: state.repeatMode,\n },\n });\n }\n prevRef.current = state;\n }, [enabled, state]);\n}\n","import { useCallback, useState } from \"react\";\nimport type { PointerEvent as ReactPointerEvent } from \"react\";\nimport { useGingerMedia, useGingerPlayback, gingerStateFromContextValues } from \"../context/GingerSplitContexts\";\nimport { progressFraction } from \"../internal/selectors\";\n\nexport type SeekDragState = {\n /** Raw drag fraction — only updated during an active drag gesture. */\n fraction: number;\n /** Blended fraction: follows live playback when idle, drag position when dragging. */\n displayFraction: number;\n isDragging: boolean;\n onPointerDown: (event: ReactPointerEvent<HTMLElement>) => void;\n};\n\nfunction clamp01(value: number): number {\n return Math.max(0, Math.min(1, value));\n}\n\nexport function useSeekDrag(duration: number): SeekDragState {\n const media = useGingerMedia();\n const playback = useGingerPlayback();\n const { seek } = media;\n const [fraction, setFraction] = useState(0);\n const [isDragging, setIsDragging] = useState(false);\n\n const liveFraction = progressFraction(gingerStateFromContextValues(playback, media));\n const displayFraction = isDragging ? fraction : liveFraction;\n\n const onPointerDown = useCallback(\n (event: ReactPointerEvent<HTMLElement>) => {\n if (!(duration > 0)) return;\n const target = event.currentTarget;\n const rect = target.getBoundingClientRect();\n const update = (clientX: number) => {\n const ratio = clamp01((clientX - rect.left) / rect.width);\n setFraction(ratio);\n seek(ratio * duration);\n };\n setIsDragging(true);\n target.setPointerCapture(event.pointerId);\n update(event.clientX);\n const onMove = (moveEvent: PointerEvent) => update(moveEvent.clientX);\n const onUp = (upEvent: PointerEvent) => {\n update(upEvent.clientX);\n setIsDragging(false);\n target.releasePointerCapture(event.pointerId);\n target.removeEventListener(\"pointermove\", onMove);\n target.removeEventListener(\"pointerup\", onUp);\n target.removeEventListener(\"pointercancel\", onUp);\n };\n target.addEventListener(\"pointermove\", onMove);\n target.addEventListener(\"pointerup\", onUp);\n target.addEventListener(\"pointercancel\", onUp);\n },\n [duration, seek],\n );\n\n return { fraction, displayFraction, isDragging, onPointerDown };\n}\n","import { useEffect } from \"react\";\nimport { useGingerPlayback } from \"../context/GingerSplitContexts\";\nimport { computeNextIndex } from \"../core/transitions\";\n\nexport type UseNextTrackPrefetchOptions = {\n /** When false, no prefetch runs. Default true. */\n enabled?: boolean;\n /**\n * Match `crossOrigin` on `Ginger.Player` when `fileUrl` is cross-origin so the browser\n * can reuse cached media consistently.\n */\n crossOrigin?: \"\" | \"anonymous\" | \"use-credentials\" | undefined;\n};\n\n/**\n * Warms the browser cache for the **logical** next track (same rules as the Next control:\n * `computeNextIndex` from queue, repeat, and playback mode) using a detached `HTMLAudioElement`\n * with `preload=\"auto\"`. Safe to call alongside `Ginger.Player`; it does not replace main playback.\n */\nexport function useNextTrackPrefetch(options: UseNextTrackPrefetchOptions = {}): void {\n const { enabled = true, crossOrigin } = options;\n const { tracks, currentIndex, repeatMode, playbackMode } = useGingerPlayback();\n\n useEffect(() => {\n if (!enabled || typeof document === \"undefined\") return;\n const nextIndex = computeNextIndex({ tracks, currentIndex, repeatMode, playbackMode });\n if (nextIndex === currentIndex) return;\n const nextUrl = tracks[nextIndex]?.fileUrl ?? \"\";\n if (!nextUrl) return;\n\n const audio = document.createElement(\"audio\");\n audio.preload = \"auto\";\n if (crossOrigin) audio.crossOrigin = crossOrigin;\n audio.src = nextUrl;\n audio.load();\n\n return () => {\n audio.removeAttribute(\"src\");\n audio.load();\n };\n }, [enabled, crossOrigin, tracks, currentIndex, repeatMode, playbackMode]);\n}\n"],"names":["useGinger","pb","useGingerPlayback","md","useGingerMedia","useMemo","state","gingerStateFromContextValues","getCurrentTrack","derivePlaybackUiState","effectiveDuration","effectiveRemaining","progressFraction","resolvedArtwork","resolvedAlbumLine","entries","clampFftSize","n","p","attachLiveAnalyser","element","options","entry","Context","context","source","analyser","isFirst","id","detachLiveAnalyser","consumer","isPlaybackSink","first","emptyFreq","emptyTime","useGingerLiveAnalyzer","enabled","fftSize","smoothingTimeConstant","minDecibels","maxDecibels","audioRef","opts","frame","setFrame","useState","error","setError","isSuspended","setIsSuspended","meta","setMeta","freqRef","useRef","timeRef","resume","useCallback","ctx","contextHolderRef","analyserHolderRef","useLayoutEffect","cancelled","consumerId","rafId","onStateChange","runLoop","a","fq","td","attach","el","fft","e","msg","retryRaf","maxAttempts","attempts","retryLoop","out","_a","useGingerKeyboardShortcuts","bindings","togglePlayPause","next","prev","toggleMute","muteBinding","useEffect","playPause","nextKey","prevKey","muteKey","onKeyDown","event","target","key","useGingerSleepTimer","durationMs","stopAfterTracks","respectPause","onFire","currentIndex","pause","isPaused","remainingTracksRef","prevIndexRef","useGingerDebugLog","useGingerState","prevRef","clamp01","value","useSeekDrag","duration","media","playback","seek","fraction","setFraction","isDragging","setIsDragging","liveFraction","displayFraction","onPointerDown","rect","update","clientX","ratio","onMove","moveEvent","onUp","upEvent","useNextTrackPrefetch","crossOrigin","tracks","repeatMode","playbackMode","nextIndex","computeNextIndex","nextUrl","audio"],"mappings":"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"}
1
+ {"version":3,"file":"useNextTrackPrefetch-CV1khU0h.js","sources":["../src/hooks/useGinger.ts","../src/analyzer/liveAudioGraph.ts","../src/analyzer/useGingerLiveAnalyzer.ts","../src/hooks/useGingerKeyboardShortcuts.ts","../src/hooks/useGingerSleepTimer.ts","../src/hooks/useGingerDebugLog.ts","../src/hooks/useSeekDrag.ts","../src/hooks/useNextTrackPrefetch.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { gingerStateFromContextValues, useGingerMedia, useGingerPlayback } from \"../context/GingerSplitContexts\";\nimport {\n derivePlaybackUiState,\n effectiveDuration,\n effectiveRemaining,\n getCurrentTrack,\n progressFraction,\n resolvedAlbumLine,\n resolvedArtwork,\n} from \"../internal/selectors\";\n\nexport function useGinger() {\n const pb = useGingerPlayback();\n const md = useGingerMedia();\n\n return useMemo(\n () => {\n const state = gingerStateFromContextValues(pb, md);\n return {\n state,\n currentTrack: getCurrentTrack(state),\n playbackUi: derivePlaybackUiState(state),\n duration: effectiveDuration(state),\n remaining: effectiveRemaining(state),\n progress: progressFraction(state),\n artworkUrl: resolvedArtwork(state),\n albumLine: resolvedAlbumLine(state),\n play: pb.play,\n pause: pb.pause,\n togglePlayPause: pb.togglePlayPause,\n seek: md.seek,\n setVolume: md.setVolume,\n setMuted: md.setMuted,\n toggleMute: md.toggleMute,\n setPlaybackRate: md.setPlaybackRate,\n next: pb.next,\n prev: pb.prev,\n setRepeatMode: pb.setRepeatMode,\n cycleRepeat: pb.cycleRepeat,\n toggleShuffle: pb.toggleShuffle,\n setQueue: pb.setQueue,\n insertTrackAt: pb.insertTrackAt,\n removeTrackAt: pb.removeTrackAt,\n moveTrack: pb.moveTrack,\n enqueueNext: pb.enqueueNext,\n playTrackAt: pb.playTrackAt,\n selectTrackAt: pb.selectTrackAt,\n setPlaylistMeta: pb.setPlaylistMeta,\n setPlaybackMode: pb.setPlaybackMode,\n init: pb.init,\n audioRef: md.audioRef,\n dispatch: pb.dispatch,\n };\n },\n [pb, md],\n );\n}\n","/** One MediaElementAudioSourceNode per HTMLAudioElement; multiple AnalyserNodes may tap the source. */\n\nexport type LiveAnalyserOptions = {\n fftSize: number;\n smoothingTimeConstant: number;\n minDecibels: number;\n maxDecibels: number;\n};\n\ntype Consumer = {\n analyser: AnalyserNode;\n /** This analyser is wired to `audioContext.destination` so the graph is audible. */\n isPlaybackSink: boolean;\n};\n\ntype ElementEntry = {\n context: AudioContext;\n source: MediaElementAudioSourceNode;\n consumers: Map<number, Consumer>;\n nextId: number;\n};\n\nconst entries = new WeakMap<HTMLAudioElement, ElementEntry>();\n\nfunction clampFftSize(n: number): number {\n const p = 2 ** Math.round(Math.log2(n));\n return Math.min(32768, Math.max(32, p));\n}\n\nexport function attachLiveAnalyser(\n element: HTMLAudioElement,\n options: LiveAnalyserOptions,\n): { id: number; context: AudioContext; analyser: AnalyserNode } {\n let entry = entries.get(element);\n if (!entry) {\n const Context = window.AudioContext ?? (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Context) {\n throw new Error(\"Web Audio API is not available\");\n }\n const context = new Context();\n const source = context.createMediaElementSource(element);\n entry = { context, source, consumers: new Map(), nextId: 0 };\n entries.set(element, entry);\n }\n\n const { context, source } = entry;\n const analyser = context.createAnalyser();\n analyser.fftSize = clampFftSize(options.fftSize);\n analyser.smoothingTimeConstant = options.smoothingTimeConstant;\n analyser.minDecibels = options.minDecibels;\n analyser.maxDecibels = options.maxDecibels;\n\n source.connect(analyser);\n\n const isFirst = entry.consumers.size === 0;\n if (isFirst) {\n analyser.connect(context.destination);\n }\n\n const id = entry.nextId;\n entry.nextId += 1;\n entry.consumers.set(id, { analyser, isPlaybackSink: isFirst });\n\n return { id, context, analyser };\n}\n\nexport function detachLiveAnalyser(element: HTMLAudioElement, id: number): void {\n const entry = entries.get(element);\n if (!entry) return;\n\n const consumer = entry.consumers.get(id);\n if (!consumer) return;\n\n const { analyser, isPlaybackSink } = consumer;\n analyser.disconnect();\n entry.consumers.delete(id);\n\n if (entry.consumers.size === 0) {\n try {\n entry.source.disconnect();\n } catch {\n // ignore\n }\n void entry.context.close();\n entries.delete(element);\n return;\n }\n\n if (isPlaybackSink) {\n const first = entry.consumers.values().next().value as Consumer | undefined;\n if (first) {\n first.analyser.connect(entry.context.destination);\n first.isPlaybackSink = true;\n }\n }\n}\n","import { useCallback, useLayoutEffect, useMemo, useRef, useState } from \"react\";\nimport { useGinger } from \"../hooks/useGinger\";\nimport { attachLiveAnalyser, detachLiveAnalyser, type LiveAnalyserOptions } from \"./liveAudioGraph\";\n\nexport type UseGingerLiveAnalyzerOptions = {\n /** When false, the analyser is detached and no frames are read. Default true. */\n enabled?: boolean;\n fftSize?: number;\n smoothingTimeConstant?: number;\n minDecibels?: number;\n maxDecibels?: number;\n};\n\nexport type UseGingerLiveAnalyzerResult = {\n /** Byte frequency data (0–255); length equals `frequencyBinCount`. Updated each animation frame while enabled. */\n frequencyData: Uint8Array;\n /** Byte time-domain data (0–255); length equals `fftSize`. */\n timeDomainData: Uint8Array;\n frequencyBinCount: number;\n sampleRate: number;\n isSuspended: boolean;\n error: string | null;\n resume: () => Promise<void>;\n};\n\nconst emptyFreq = new Uint8Array(0);\nconst emptyTime = new Uint8Array(0);\n\nexport function useGingerLiveAnalyzer(options: UseGingerLiveAnalyzerOptions = {}): UseGingerLiveAnalyzerResult {\n const {\n enabled = true,\n fftSize = 2048,\n smoothingTimeConstant = 0.8,\n minDecibels = -100,\n maxDecibels = -30,\n } = options;\n\n const { audioRef, state } = useGinger();\n const opts = useMemo<LiveAnalyserOptions>(\n () => ({\n fftSize,\n smoothingTimeConstant,\n minDecibels,\n maxDecibels,\n }),\n [fftSize, smoothingTimeConstant, minDecibels, maxDecibels],\n );\n\n const [frame, setFrame] = useState(0);\n const [error, setError] = useState<string | null>(null);\n const [isSuspended, setIsSuspended] = useState(false);\n const [meta, setMeta] = useState({ frequencyBinCount: 0, sampleRate: 0 });\n\n const freqRef = useRef<Uint8Array>(emptyFreq);\n const timeRef = useRef<Uint8Array>(emptyTime);\n\n const resume = useCallback(async () => {\n const ctx = contextHolderRef.current;\n if (ctx && ctx.state === \"suspended\") {\n await ctx.resume();\n }\n }, []);\n\n const contextHolderRef = useRef<AudioContext | null>(null);\n const analyserHolderRef = useRef<AnalyserNode | null>(null);\n\n useLayoutEffect(() => {\n if (!enabled || typeof window === \"undefined\") {\n return;\n }\n\n let cancelled = false;\n let consumerId: number | null = null;\n let element: HTMLAudioElement | null = null;\n let rafId = 0;\n\n const onStateChange = () => {\n const ctx = contextHolderRef.current;\n if (ctx) setIsSuspended(ctx.state === \"suspended\");\n };\n\n const runLoop = () => {\n if (cancelled) return;\n const a = analyserHolderRef.current;\n const fq = freqRef.current;\n const td = timeRef.current;\n if (a && fq.length > 0 && td.length > 0) {\n a.getByteFrequencyData(fq as Uint8Array<ArrayBuffer>);\n a.getByteTimeDomainData(td as Uint8Array<ArrayBuffer>);\n setFrame((n) => n + 1);\n }\n rafId = requestAnimationFrame(runLoop);\n };\n\n type AttachOutcome = \"ok\" | \"no-element\" | \"error\";\n\n const attach = (): AttachOutcome => {\n const el = audioRef.current;\n if (!el || cancelled) return \"no-element\";\n try {\n const { id, context, analyser } = attachLiveAnalyser(el, opts);\n consumerId = id;\n element = el;\n contextHolderRef.current = context;\n analyserHolderRef.current = analyser;\n setIsSuspended(context.state === \"suspended\");\n setError(null);\n\n context.addEventListener(\"statechange\", onStateChange);\n\n const n = analyser.frequencyBinCount;\n const fft = analyser.fftSize;\n freqRef.current = new Uint8Array(n);\n timeRef.current = new Uint8Array(fft);\n setMeta({ frequencyBinCount: n, sampleRate: context.sampleRate });\n\n rafId = requestAnimationFrame(runLoop);\n return \"ok\";\n } catch (e) {\n const msg = e instanceof Error ? e.message : \"Failed to attach live analyser\";\n setError(msg);\n contextHolderRef.current = null;\n analyserHolderRef.current = null;\n freqRef.current = emptyFreq;\n timeRef.current = emptyTime;\n setMeta({ frequencyBinCount: 0, sampleRate: 0 });\n return \"error\";\n }\n };\n\n const first = attach();\n if (first !== \"ok\") {\n let retryRaf = 0;\n const maxAttempts = 120;\n let attempts = 0;\n\n const retryLoop = () => {\n if (cancelled) return;\n const out = attach();\n if (out === \"ok\" || out === \"error\") return;\n attempts += 1;\n if (attempts >= maxAttempts) return;\n retryRaf = requestAnimationFrame(retryLoop);\n };\n\n if (first === \"no-element\") {\n retryRaf = requestAnimationFrame(retryLoop);\n }\n\n return () => {\n cancelled = true;\n cancelAnimationFrame(retryRaf);\n cancelAnimationFrame(rafId);\n if (consumerId != null && element) {\n detachLiveAnalyser(element, consumerId);\n }\n contextHolderRef.current?.removeEventListener(\"statechange\", onStateChange);\n contextHolderRef.current = null;\n analyserHolderRef.current = null;\n freqRef.current = emptyFreq;\n timeRef.current = emptyTime;\n };\n }\n\n return () => {\n cancelled = true;\n cancelAnimationFrame(rafId);\n if (consumerId != null && element) {\n detachLiveAnalyser(element, consumerId);\n }\n contextHolderRef.current?.removeEventListener(\"statechange\", onStateChange);\n contextHolderRef.current = null;\n analyserHolderRef.current = null;\n freqRef.current = emptyFreq;\n timeRef.current = emptyTime;\n setMeta({ frequencyBinCount: 0, sampleRate: 0 });\n };\n }, [enabled, audioRef, opts, state.currentIndex]);\n\n void frame;\n\n return {\n frequencyData: freqRef.current,\n timeDomainData: timeRef.current,\n frequencyBinCount: meta.frequencyBinCount,\n sampleRate: meta.sampleRate,\n isSuspended,\n error,\n resume,\n };\n}\n","import { useEffect } from \"react\";\nimport { useGingerMedia, useGingerPlayback } from \"../context/GingerSplitContexts\";\n\nexport type GingerKeyboardShortcutBindings = {\n playPause?: string;\n next?: string;\n previous?: string;\n mute?: string;\n};\n\nexport function useGingerKeyboardShortcuts(\n enabled = true,\n bindings: GingerKeyboardShortcutBindings = {},\n): void {\n const { togglePlayPause, next, prev } = useGingerPlayback();\n const { toggleMute } = useGingerMedia();\n\n const muteBinding = bindings.mute;\n\n useEffect(() => {\n if (!enabled || typeof window === \"undefined\") return;\n const playPause = (bindings.playPause ?? \" \").toLowerCase();\n const nextKey = (bindings.next ?? \"ArrowRight\").toLowerCase();\n const prevKey = (bindings.previous ?? \"ArrowLeft\").toLowerCase();\n const muteKey = muteBinding?.toLowerCase();\n\n const onKeyDown = (event: KeyboardEvent) => {\n const target = event.target as HTMLElement | null;\n if (target && ([\"INPUT\", \"TEXTAREA\", \"SELECT\"].includes(target.tagName) || target.isContentEditable)) return;\n const key = event.key.toLowerCase();\n if (key === playPause) {\n event.preventDefault();\n togglePlayPause();\n } else if (key === nextKey) {\n event.preventDefault();\n next();\n } else if (key === prevKey) {\n event.preventDefault();\n prev();\n } else if (muteKey && key === muteKey) {\n event.preventDefault();\n toggleMute();\n }\n };\n window.addEventListener(\"keydown\", onKeyDown);\n return () => window.removeEventListener(\"keydown\", onKeyDown);\n }, [bindings.next, bindings.playPause, bindings.previous, enabled, muteBinding, next, prev, toggleMute, togglePlayPause]);\n}\n","import { useEffect, useRef } from \"react\";\nimport { useGingerPlayback } from \"../context/GingerSplitContexts\";\n\nexport type GingerSleepTimerOptions = {\n durationMs?: number;\n stopAfterTracks?: number;\n respectPause?: boolean;\n enabled?: boolean;\n onFire?: () => void;\n};\n\nexport function useGingerSleepTimer(options: GingerSleepTimerOptions): void {\n const { durationMs, stopAfterTracks, respectPause = true, enabled = true, onFire } = options;\n const { currentIndex, pause, isPaused } = useGingerPlayback();\n const remainingTracksRef = useRef(stopAfterTracks ?? 0);\n const prevIndexRef = useRef(currentIndex);\n\n useEffect(() => {\n remainingTracksRef.current = stopAfterTracks ?? 0;\n }, [stopAfterTracks]);\n\n useEffect(() => {\n if (!enabled || !durationMs || durationMs <= 0) return;\n if (respectPause && isPaused) return;\n const id = setTimeout(() => {\n pause();\n onFire?.();\n }, durationMs);\n return () => clearTimeout(id);\n }, [durationMs, enabled, isPaused, onFire, pause, respectPause]);\n\n useEffect(() => {\n if (!enabled || !stopAfterTracks || stopAfterTracks <= 0) return;\n const prev = prevIndexRef.current;\n prevIndexRef.current = currentIndex;\n if (currentIndex === prev) return;\n remainingTracksRef.current -= 1;\n if (remainingTracksRef.current <= 0) {\n pause();\n onFire?.();\n }\n }, [currentIndex, enabled, onFire, pause, stopAfterTracks]);\n}\n","import { useEffect, useRef } from \"react\";\nimport { useGingerState } from \"../context/GingerSplitContexts\";\n\nexport function useGingerDebugLog(enabled = false): void {\n const state = useGingerState();\n const prevRef = useRef(state);\n\n useEffect(() => {\n if (!enabled || typeof console === \"undefined\") return;\n const prev = prevRef.current;\n if (prev !== state) {\n console.debug(\"[ginger]\", {\n from: {\n currentIndex: prev.currentIndex,\n isPaused: prev.isPaused,\n currentTime: prev.currentTime,\n repeatMode: prev.repeatMode,\n },\n to: {\n currentIndex: state.currentIndex,\n isPaused: state.isPaused,\n currentTime: state.currentTime,\n repeatMode: state.repeatMode,\n },\n });\n }\n prevRef.current = state;\n }, [enabled, state]);\n}\n","import { useCallback, useState } from \"react\";\nimport type { PointerEvent as ReactPointerEvent } from \"react\";\nimport { useGingerMedia, useGingerPlayback, gingerStateFromContextValues } from \"../context/GingerSplitContexts\";\nimport { progressFraction } from \"../internal/selectors\";\n\nexport type SeekDragState = {\n /** Raw drag fraction — only updated during an active drag gesture. */\n fraction: number;\n /** Blended fraction: follows live playback when idle, drag position when dragging. */\n displayFraction: number;\n isDragging: boolean;\n onPointerDown: (event: ReactPointerEvent<HTMLElement>) => void;\n};\n\nfunction clamp01(value: number): number {\n return Math.max(0, Math.min(1, value));\n}\n\nexport function useSeekDrag(duration: number): SeekDragState {\n const media = useGingerMedia();\n const playback = useGingerPlayback();\n const { seek } = media;\n const [fraction, setFraction] = useState(0);\n const [isDragging, setIsDragging] = useState(false);\n\n const liveFraction = progressFraction(gingerStateFromContextValues(playback, media));\n const displayFraction = isDragging ? fraction : liveFraction;\n\n const onPointerDown = useCallback(\n (event: ReactPointerEvent<HTMLElement>) => {\n if (!(duration > 0)) return;\n const target = event.currentTarget;\n const rect = target.getBoundingClientRect();\n const update = (clientX: number) => {\n const ratio = clamp01((clientX - rect.left) / rect.width);\n setFraction(ratio);\n seek(ratio * duration);\n };\n setIsDragging(true);\n target.setPointerCapture(event.pointerId);\n update(event.clientX);\n const onMove = (moveEvent: PointerEvent) => update(moveEvent.clientX);\n const onUp = (upEvent: PointerEvent) => {\n update(upEvent.clientX);\n setIsDragging(false);\n target.releasePointerCapture(event.pointerId);\n target.removeEventListener(\"pointermove\", onMove);\n target.removeEventListener(\"pointerup\", onUp);\n target.removeEventListener(\"pointercancel\", onUp);\n };\n target.addEventListener(\"pointermove\", onMove);\n target.addEventListener(\"pointerup\", onUp);\n target.addEventListener(\"pointercancel\", onUp);\n },\n [duration, seek],\n );\n\n return { fraction, displayFraction, isDragging, onPointerDown };\n}\n","import { useEffect } from \"react\";\nimport { useGingerPlayback } from \"../context/GingerSplitContexts\";\nimport { computeNextIndex } from \"../core/transitions\";\n\nexport type UseNextTrackPrefetchOptions = {\n /** When false, no prefetch runs. Default true. */\n enabled?: boolean;\n /**\n * Match `crossOrigin` on `Ginger.Player` when `fileUrl` is cross-origin so the browser\n * can reuse cached media consistently.\n */\n crossOrigin?: \"\" | \"anonymous\" | \"use-credentials\" | undefined;\n};\n\n/**\n * Warms the browser cache for the **logical** next track (same rules as the Next control:\n * `computeNextIndex` from queue, repeat, and playback mode) using a detached `HTMLAudioElement`\n * with `preload=\"auto\"`. Safe to call alongside `Ginger.Player`; it does not replace main playback.\n */\nexport function useNextTrackPrefetch(options: UseNextTrackPrefetchOptions = {}): void {\n const { enabled = true, crossOrigin } = options;\n const { tracks, currentIndex, repeatMode, playbackMode } = useGingerPlayback();\n\n useEffect(() => {\n if (!enabled || typeof document === \"undefined\") return;\n const nextIndex = computeNextIndex({ tracks, currentIndex, repeatMode, playbackMode });\n if (nextIndex === currentIndex) return;\n const nextUrl = tracks[nextIndex]?.fileUrl ?? \"\";\n if (!nextUrl) return;\n\n const audio = document.createElement(\"audio\");\n audio.preload = \"auto\";\n if (crossOrigin) audio.crossOrigin = crossOrigin;\n audio.src = nextUrl;\n audio.load();\n\n return () => {\n audio.removeAttribute(\"src\");\n audio.load();\n };\n }, [enabled, crossOrigin, tracks, currentIndex, repeatMode, playbackMode]);\n}\n"],"names":["useGinger","pb","useGingerPlayback","md","useGingerMedia","useMemo","state","gingerStateFromContextValues","getCurrentTrack","derivePlaybackUiState","effectiveDuration","effectiveRemaining","progressFraction","resolvedArtwork","resolvedAlbumLine","entries","clampFftSize","n","p","attachLiveAnalyser","element","options","entry","Context","context","source","analyser","isFirst","id","detachLiveAnalyser","consumer","isPlaybackSink","first","emptyFreq","emptyTime","useGingerLiveAnalyzer","enabled","fftSize","smoothingTimeConstant","minDecibels","maxDecibels","audioRef","opts","frame","setFrame","useState","error","setError","isSuspended","setIsSuspended","meta","setMeta","freqRef","useRef","timeRef","resume","useCallback","ctx","contextHolderRef","analyserHolderRef","useLayoutEffect","cancelled","consumerId","rafId","onStateChange","runLoop","a","fq","td","attach","el","fft","e","msg","retryRaf","maxAttempts","attempts","retryLoop","out","_a","useGingerKeyboardShortcuts","bindings","togglePlayPause","next","prev","toggleMute","muteBinding","useEffect","playPause","nextKey","prevKey","muteKey","onKeyDown","event","target","key","useGingerSleepTimer","durationMs","stopAfterTracks","respectPause","onFire","currentIndex","pause","isPaused","remainingTracksRef","prevIndexRef","useGingerDebugLog","useGingerState","prevRef","clamp01","value","useSeekDrag","duration","media","playback","seek","fraction","setFraction","isDragging","setIsDragging","liveFraction","displayFraction","onPointerDown","rect","update","clientX","ratio","onMove","moveEvent","onUp","upEvent","useNextTrackPrefetch","crossOrigin","tracks","repeatMode","playbackMode","nextIndex","computeNextIndex","nextUrl","audio"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lucaismyname/ginger",
3
- "version": "0.0.17",
3
+ "version": "0.0.21",
4
4
  "description": "A headless React audio player",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -44,6 +44,11 @@
44
44
  "test": "vitest run",
45
45
  "test:watch": "vitest",
46
46
  "test:coverage": "vitest run --coverage",
47
+ "typecheck": "tsc --noEmit -p tsconfig.json",
48
+ "lint": "biome check src/testing src/components/controls src/index.ts src/client.ts src/experimental-gapless",
49
+ "lint:fix": "biome check --write src/testing src/components/controls src/index.ts src/client.ts src/experimental-gapless",
50
+ "format": "biome format --write src/testing src/components/controls src/index.ts src/client.ts src/experimental-gapless",
51
+ "docs:api": "typedoc --options typedoc.json",
47
52
  "prepublishOnly": "npm run build"
48
53
  },
49
54
  "peerDependencies": {
@@ -56,7 +61,9 @@
56
61
  }
57
62
  },
58
63
  "devDependencies": {
64
+ "@biomejs/biome": "^1.9.4",
59
65
  "@testing-library/react": "^16.3.0",
66
+ "@testing-library/user-event": "^14.6.1",
60
67
  "@types/react": "^18.3.12",
61
68
  "@types/react-dom": "^18.3.1",
62
69
  "@vitejs/plugin-react": "^4.3.4",
@@ -64,6 +71,7 @@
64
71
  "jsdom": "^29.0.2",
65
72
  "react": "^18.3.1",
66
73
  "react-dom": "^18.3.1",
74
+ "typedoc": "^0.28.14",
67
75
  "typescript": "^5.6.3",
68
76
  "vite": "^5.4.11",
69
77
  "vite-plugin-dts": "^4.3.0",
@@ -1,2 +0,0 @@
1
- "use strict";const l=require("react/jsx-runtime"),f=require("react"),b=require("./GingerSplitContexts-C7puo0M7.cjs"),we=f.createContext(null);function _r(){const e=f.useContext(we);if(!e)throw new Error("Ginger components must be used within <Ginger.Provider>");return e}function Ne(e){const{buffered:r,duration:n}=e;return!(n>0)||r.length===0?0:Math.min(1,r.end(r.length-1)/n)}function Nr({className:e,style:r,preload:n="metadata",crossOrigin:a,respectReducedMotion:t=!1}){var I;const{audioRef:i,dispatch:u,state:s,notifyEnded:o}=_r(),d=((I=s.tracks[s.currentIndex])==null?void 0:I.fileUrl)??"",m=f.useRef({currentTime:-1,duration:-1,bufferedFraction:-1}),y=f.useRef(""),[h,k]=f.useState(!1);f.useEffect(()=>{if(!t||typeof window>"u")return;const p=window.matchMedia("(prefers-reduced-motion: reduce)"),E=()=>k(p.matches);return E(),p.addEventListener("change",E),()=>p.removeEventListener("change",E)},[t]);const v=(p,E=!1)=>{const P={currentTime:p.currentTime,duration:p.duration,bufferedFraction:Ne(p)},G=m.current,Me=h?.5:.25,O=Math.abs(P.currentTime-G.currentTime)>=Me||Math.abs(P.duration-G.duration)>=.01||Math.abs(P.bufferedFraction-G.bufferedFraction)>=.01;!E&&!O||(m.current=P,u({type:"MEDIA_TIME_UPDATE",payload:P}))};return f.useEffect(()=>{const p=i.current;p&&(p.volume=s.volume,p.muted=s.muted,p.playbackRate=s.playbackRate)},[i,s.volume,s.muted,s.playbackRate]),f.useEffect(()=>{const p=i.current;if(p){if(!d){p.removeAttribute("src"),m.current={currentTime:-1,duration:-1,bufferedFraction:-1},y.current!==""&&u({type:"MEDIA_SOURCE_CLEARED"}),y.current="";return}p.getAttribute("src")!==d&&(p.src=d,p.load(),m.current={currentTime:-1,duration:-1,bufferedFraction:-1}),y.current=d}},[i,u,s.currentIndex,s.tracks,d]),l.jsx("audio",{ref:i,className:e,style:r,preload:n,crossOrigin:a,controls:!1,playsInline:!0,onTimeUpdate:p=>{v(p.currentTarget)},onLoadedMetadata:p=>{const E=p.currentTarget;m.current={currentTime:-1,duration:-1,bufferedFraction:-1},u({type:"MEDIA_LOADED_METADATA",payload:{duration:E.duration,bufferedFraction:Ne(E)}})},onSeeking:p=>v(p.currentTarget,!0),onSeeked:p=>v(p.currentTarget,!0),onEnded:()=>o(),onPlay:()=>u({type:"MEDIA_PLAY"}),onPause:()=>u({type:"MEDIA_PAUSE"}),onWaiting:()=>u({type:"MEDIA_WAITING"}),onCanPlay:()=>u({type:"MEDIA_CANPLAY"}),onProgress:p=>v(p.currentTarget,!0),onVolumeChange:p=>{const E=p.currentTarget;u({type:"MEDIA_VOLUME_SYNC",payload:{volume:E.volume,muted:E.muted}})},onError:()=>{var G;const p=i.current,E=(G=p==null?void 0:p.error)==null?void 0:G.code;u({type:"MEDIA_ERROR",payload:{message:E===1?"MEDIA_ERR_ABORTED":E===2?"MEDIA_ERR_NETWORK":E===3?"MEDIA_ERR_DECODE":E===4?"MEDIA_ERR_SRC_NOT_SUPPORTED":"MEDIA_ERR_UNKNOWN"}})}})}function S(e,r){return r<=0?0:Math.max(0,Math.min(r-1,e))}function De(e,r){if(e.length<=1)return[...e];const n=e[r];if(!n)return[...e];const a=e.filter((t,i)=>i!==r);for(let t=a.length-1;t>0;t--){const i=Math.floor(Math.random()*(t+1));[a[t],a[i]]=[a[i],a[t]]}return[n,...a]}function K(e){return e?e.id!=null&&e.id!==""?`id:${e.id}`:`file:${e.fileUrl}`:""}function Lr(e,r){var u,s;if(!r)return 0;const n=e.findIndex(o=>o===r);if(n!==-1)return n;const a=K(r);if(!a)return 0;const t=[];for(let o=0;o<e.length;o+=1)K(e[o])===a&&t.push(o);if(t.length===0)return 0;if(t.length===1)return t[0];const i=typeof globalThis<"u"&&"process"in globalThis?(s=(u=globalThis.process)==null?void 0:u.env)==null?void 0:s.NODE_ENV:void 0;return i!=null&&i!=="production"&&console.warn("[@lucaismyname/ginger] Ambiguous track identity: multiple queue rows share the same fileUrl without a unique `id`. Resolving to the first match."),t[0]}function Fe(e,r,n){const a=[...e],t=Math.max(0,Math.min(a.length,n??a.length));return a.splice(t,0,r),a}function wr(e,r){if(r<0||r>=e.length)return[...e];const n=[...e];return n.splice(r,1),n}function Dr(e,r,n){if(r===n||r<0||r>=e.length||n<0||n>=e.length)return[...e];const a=[...e],[t]=a.splice(r,1);return t?(a.splice(n,0,t),a):[...e]}function Fr(e,r,n){return Fe(e,n,Math.max(0,Math.min(e.length,r+1)))}function Ur(e){const{tracks:r,currentIndex:n,repeatMode:a,playbackMode:t}=e,i=r.length;return i===0?{kind:"stop",nextIndex:0}:a==="one"?{kind:"replay_same"}:t==="single"?{kind:"stop",nextIndex:S(n,i)}:n<i-1?{kind:"advance",nextIndex:n+1}:a==="all"?{kind:"wrap",nextIndex:0}:{kind:"stop",nextIndex:S(n,i)}}function Ue(e){const{tracks:r,currentIndex:n,repeatMode:a,playbackMode:t}=e,i=r.length;return i===0?0:t==="single"?S(n,i):n<i-1?n+1:a==="all"?0:S(n,i)}function Vr(e){const{tracks:r,currentIndex:n,repeatMode:a,playbackMode:t}=e,i=r.length;return i===0?0:t==="single"?S(n,i):n>0?n-1:a==="all"?i-1:0}function $r(e){return e==="off"?"all":e==="all"?"one":"off"}function Ve(e,r){return(e==null?void 0:e.artworkUrl)??r??void 0}function Br(e,r){return(e==null?void 0:e.album)??r??void 0}function j(e){return e.tracks[e.currentIndex]??null}function Ie(e){return e.errorMessage?"error":e.tracks.length===0?"idle":e.isBuffering?"loading":e.isPaused?Number.isFinite(e.duration)&&e.duration>0&&e.currentTime>=e.duration-.05?"ended":"paused":"playing"}function B(e){var a;const r=e.duration;if(Number.isFinite(r)&&r>0)return r;const n=(a=e.tracks[e.currentIndex])==null?void 0:a.durationSeconds;return typeof n=="number"&&Number.isFinite(n)&&n>0?n:0}function $e(e){const n=B(e)-e.currentTime;return Number.isFinite(n)?Math.max(0,n):0}function Pe(e){const r=B(e);return r>0?Math.min(1,Math.max(0,e.currentTime/r)):0}function Be(e){var n;const r=j(e);return Ve(r,(n=e.playlistMeta)==null?void 0:n.artworkUrl)}function Oe(e){var n;const r=j(e);return Br(r,(n=e.playlistMeta)==null?void 0:n.subtitle)}function F(e,r){function n(a){const t=b.useGingerState(),u=(r(t)??"").trim(),{className:s,style:o,fallback:d,empty:m,children:y}=a;if(!u){const h=m??d??null;return h?l.jsx("span",{className:s,style:o,children:h}):null}return y?l.jsx("span",{className:s,style:o,children:y(u,t)}):l.jsx("span",{className:s,style:o,children:u})}return n.displayName=e,n}function U(e,r){return F(e,n=>r(j(n)))}const Or=U("Ginger.Current.Title",e=>e==null?void 0:e.title),Yr=U("Ginger.Current.Artist",e=>e==null?void 0:e.artist),Qr=F("Ginger.Current.Album",e=>Oe(e)),Hr=U("Ginger.Current.Description",e=>e==null?void 0:e.description),Kr=F("Ginger.Current.Copyright",e=>{var n;const r=j(e);return(r==null?void 0:r.copyright)??((n=e.playlistMeta)==null?void 0:n.copyright)}),Xr=U("Ginger.Current.Genre",e=>e==null?void 0:e.genre),qr=U("Ginger.Current.Label",e=>e==null?void 0:e.label),zr=U("Ginger.Current.Isrc",e=>e==null?void 0:e.isrc),Wr=U("Ginger.Current.TrackNumber",e=>(e==null?void 0:e.trackNumber)!=null?String(e.trackNumber):void 0);function Ye({className:e,style:r,fallback:n,empty:a,children:t,format:i}){var d;const u=b.useGingerState(),s=(d=j(u))==null?void 0:d.year;if(typeof s!="number"||!Number.isFinite(s)){const m=a??n??null;return m?l.jsx("span",{className:e,style:r,children:m}):null}const o=i?i(s):String(s);return t?l.jsx("span",{className:e,style:r,children:t(o,u)}):l.jsx("span",{className:e,style:r,children:o})}Ye.displayName="Ginger.Current.Year";function Qe({className:e,style:r,fallback:n,empty:a,children:t,preserveWhitespace:i=!0}){var m;const u=b.useGingerState(),s=((m=j(u))==null?void 0:m.lyrics)??"",o=i?s.replace(/^\s+|\s+$/g,""):s.trim();if(!o){const y=a??n??null;return y?l.jsx("span",{className:e,style:r,children:y}):null}const d=i?{whiteSpace:"pre-wrap"}:void 0;return t?l.jsx("span",{className:e,style:{...d,...r},children:t(o,u)}):l.jsx("span",{className:e,style:{...d,...r},children:o})}Qe.displayName="Ginger.Current.Lyrics";const Le=/\[(\d{1,2}):(\d{2})(?:\.(\d{1,3}))?\]/g;function He(e){const r=[];for(const n of e.split(/\r?\n/)){const a=[...n.matchAll(Le)];if(a.length===0)continue;const t=n.replace(Le,"").trim();for(const i of a){const u=Number(i[1]??0),s=Number(i[2]??0),o=Number((i[3]??"0").padEnd(3,"0")),d=u*60+s+o/1e3;Number.isFinite(d)&&d>=0&&r.push({time:d,text:t})}}return r.sort((n,a)=>n.time-a.time)}function Ke(){const{tracks:e,currentIndex:r}=b.useGingerPlayback(),{currentTime:n}=b.useGingerMedia(),a=e[r],t=f.useMemo(()=>a?Array.isArray(a.lyricsTimed)&&a.lyricsTimed.length>0?[...a.lyricsTimed].filter(u=>Number.isFinite(u.time)&&u.time>=0).sort((u,s)=>u.time-s.time):typeof a.lyrics=="string"?He(a.lyrics):[]:[],[a]),i=f.useMemo(()=>{for(let u=t.length-1;u>=0;u-=1)if(n>=t[u].time)return u;return-1},[n,t]);return{lines:t,activeIndex:i,activeLine:i>=0?t[i]??null:null}}function Ae({className:e,style:r,fallback:n,empty:a,unstyled:t=!1,activeClassName:i,lineClassName:u,children:s}){const o=b.useGingerState(),{lines:d,activeIndex:m}=Ke();if(d.length===0){const h=a??n??null;return h?l.jsx("span",{className:e,style:r,children:h}):null}const y=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 l.jsx("ul",{className:e,style:{...y,...r},"aria-label":"Synced lyrics",children:d.map((h,k)=>{const v=k===m;return l.jsx("li",{"aria-current":v?"true":void 0,"data-ginger-active":v||void 0,className:[u,v?i:void 0].filter(Boolean).join(" ")||void 0,style:t?void 0:{padding:"var(--ginger-playlist-row-padding, 4px 8px)",fontWeight:v?600:400,opacity:v?1:.75},children:s?s(h,k,v,o):h.text},`${h.time}-${k}`)})})}Ae.displayName="Ginger.Current.LyricsSynced";function Xe(){const{tracks:e,currentIndex:r}=b.useGingerPlayback(),{currentTime:n,seek:a}=b.useGingerMedia(),t=f.useMemo(()=>{var s;return[...((s=e[r])==null?void 0:s.chapters)??[]].filter(o=>o&&Number.isFinite(o.startSeconds)&&o.startSeconds>=0).sort((o,d)=>o.startSeconds-d.startSeconds)},[r,e]),i=f.useMemo(()=>{if(t.length===0)return-1;for(let u=t.length-1;u>=0;u-=1)if(n>=t[u].startSeconds)return u;return-1},[n,t]);return{list:t,activeIndex:i,active:i>=0?t[i]??null:null,seekTo:u=>{const s=t[u];s&&a(s.startSeconds)}}}function Q(e){if(!Number.isFinite(e)||e<0)return"0:00";const r=Math.floor(e%60);return`${Math.floor(e/60)}:${r.toString().padStart(2,"0")}`}function Re({className:e,style:r,fallback:n,empty:a,unstyled:t=!1,formatStart:i=Q,children:u}){const s=b.useGingerState(),{list:o,activeIndex:d,seekTo:m}=Xe();if(o.length===0){const h=a??n??null;return h?l.jsx("span",{className:e,style:r,children:h}):null}const y=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 l.jsx("ul",{className:e,style:{...y,...r},"aria-label":"Chapters",children:o.map((h,k)=>{const v=k===d;return l.jsx("li",{children:l.jsx("button",{type:"button","aria-current":v?"true":void 0,"data-ginger-active":v||void 0,onClick:()=>m(k),style:{width:t?void 0:"100%",textAlign:t?void 0:"left",border:t?void 0:"none",background:t?void 0:v?"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:u?u(h,k,v,s):l.jsxs("span",{children:[l.jsx("span",{style:{opacity:.75,marginRight:"0.35em"},children:i(h.startSeconds)}),h.title]})})},`${h.startSeconds}-${h.title}`)})})}Re.displayName="Ginger.Current.Chapters";function qe({visible:e=!1,className:r,style:n,fallback:a,empty:t,children:i}){var o;const u=b.useGingerState();if(!e)return null;const s=((o=j(u))==null?void 0:o.fileUrl)??"";if(!s){const d=t??a??null;return d?l.jsx("span",{className:r,style:n,children:d}):null}return i?l.jsx("span",{className:r,style:n,children:i(s,u)}):l.jsx("span",{className:r,style:n,children:s})}qe.displayName="Ginger.Current.FileUrl";function ze({className:e,style:r,fallback:n,empty:a,sizes:t,loading:i,onError:u,decoding:s,unstyled:o=!1,imgStyle:d}){const m=b.useGingerState(),y=j(m),h=Be(m);if(!h){const v=a??n??null;return v?l.jsx("span",{className:e,style:r,children:v}):null}const k=[y==null?void 0:y.title,y==null?void 0:y.artist].filter(Boolean).join(" — ")||"Artwork";return l.jsx("div",{className:e,style:o?{...r}:{background:"var(--ginger-artwork-bg, transparent)",borderRadius:"var(--ginger-artwork-radius, 0)",overflow:"hidden",...r},children:l.jsx("img",{src:h,alt:k,sizes:t,loading:i,decoding:s,onError:u,style:{display:o?void 0:"block",width:o?void 0:"100%",height:o?void 0:"100%",objectFit:o?void 0:"cover",...d}})})}ze.displayName="Ginger.Current.Artwork";function We({base:e=0,className:r,style:n,fallback:a,empty:t,children:i}){const u=b.useGingerState();if(u.tracks.length===0){const d=t??a??null;return d?l.jsx("span",{className:r,style:n,children:d}):null}const o=String(u.currentIndex+e);return i?l.jsx("span",{className:r,style:n,children:i(o,u)}):l.jsx("span",{className:r,style:n,children:o})}We.displayName="Ginger.Current.QueueIndex";function Je({className:e,style:r,fallback:n,empty:a,children:t}){const i=b.useGingerState(),u=String(i.tracks.length);if(i.tracks.length===0){const s=a??n??null;return s?l.jsx("span",{className:e,style:r,children:s}):null}return t?l.jsx("span",{className:e,style:r,children:t(u,i)}):l.jsx("span",{className:e,style:r,children:u})}Je.displayName="Ginger.Current.QueueLength";function Ze({base:e=0,separator:r=" / ",className:n,style:a,fallback:t,empty:i,children:u}){const s=b.useGingerState(),o=s.tracks.length;if(o===0){const h=i??t??null;return h?l.jsx("span",{className:n,style:a,children:h}):null}const d=String(s.currentIndex+e),m=String(o),y=`${d}${r}${m}`;return u?l.jsx("span",{className:n,style:a,children:u({index:d,length:m,label:y},s)}):l.jsx("span",{className:n,style:a,children:y})}Ze.displayName="Ginger.Current.QueuePosition";function Ge(e,r,n){const{className:a,style:t,fallback:i,empty:u,children:s,format:o=Q}=n;if(!(e>=0)||!Number.isFinite(e)){const m=u??i??null;return m?l.jsx("span",{className:a,style:t,children:m}):null}const d=o(e);return s?l.jsx("span",{className:a,style:t,children:s(d,r)}):l.jsx("span",{className:a,style:t,children:d})}function er(e){const r=b.useGingerState();return Ge(r.currentTime,r,e)}er.displayName="Ginger.Current.Elapsed";function rr(e){const r=b.useGingerState();return Ge(B(r),r,e)}rr.displayName="Ginger.Current.Duration";function nr(e){const r=b.useGingerState();return Ge($e(r),r,e)}nr.displayName="Ginger.Current.Remaining";function tr({className:e,style:r,fallback:n,empty:a,children:t}){const i=b.useGingerState(),u=B(i),s=Pe(i);if(!(u>0)){const o=a??n??null;return o?l.jsx("span",{className:e,style:r,children:o}):null}return t?l.jsx("span",{className:e,style:r,children:t({fraction:s,currentTime:i.currentTime,duration:u},i)}):l.jsx("span",{className:e,style:r,children:`${Math.round(s*100)}%`})}tr.displayName="Ginger.Current.Progress";function ar({className:e,style:r,height:n=4,showBuffered:a=!1,unstyled:t=!1}){const i=b.useGingerState(),u=`${Math.round(Pe(i)*100)}%`,s=`${Math.round(Math.min(1,Math.max(0,i.bufferedFraction))*100)}%`;return l.jsxs("div",{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?l.jsx("div",{style:{position:t?void 0:"absolute",left:t?void 0:0,top:t?void 0:0,height:t?void 0:"100%",width:s,background:t?void 0:"var(--ginger-buffer-color, rgba(107, 114, 128, 0.35))"}}):null,l.jsx("div",{style:{position:t?void 0:"relative",width:u,height:t?void 0:"100%",background:t?void 0:"var(--ginger-primary-color, #111827)"}})]})}ar.displayName="Ginger.Current.TimeRail";function ir({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 l.jsx("div",{className:e,style:a?{...r}:{width:"100%",height:n,background:"var(--ginger-muted-color, #e5e7eb)",borderRadius:999,overflow:"hidden",...r},"aria-hidden":!0,children:l.jsx("div",{style:{width:i,height:a?void 0:"100%",background:a?void 0:"var(--ginger-buffer-color, rgba(107, 114, 128, 0.35))"}})})}ir.displayName="Ginger.Current.BufferRail";function ur({className:e,style:r,fallback:n,empty:a,children:t}){const i=b.useGingerState(),u=Ie(i);return t?l.jsx("span",{className:e,style:r,children:t(u,i)}):l.jsx("span",{className:e,style:r,children:u})}ur.displayName="Ginger.Current.PlaybackState";function sr({className:e,style:r,fallback:n,empty:a,live:t="polite",children:i}){const u=b.useGingerState(),s=u.errorMessage??"";if(!s){const o=a??n??null;return o?l.jsx("span",{className:e,style:r,children:o}):null}return i?l.jsx("span",{className:e,style:r,"aria-live":t,children:i(s,u)}):l.jsx("span",{className:e,style:r,"aria-live":t,children:s})}sr.displayName="Ginger.Current.ErrorMessage";const H={seek:"Seek",volume:"Volume",playbackSpeed:"Playback speed",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 Jr(e){return e?{...H,...e,repeat:{...H.repeat,...e.repeat}}:H}const or=f.createContext(H);function Zr({locale:e,children:r}){const n=Jr(e);return l.jsx(or.Provider,{value:n,children:r})}function A(){return f.useContext(or)}function lr(){const e=b.useGingerPlayback(),r=b.useGingerMedia(),n=A(),a=f.useMemo(()=>b.gingerStateFromContextValues(e,r),[e,r]),t=B(a),i=t>0?a.currentTime:0,u=Number.isFinite(i)?i:0,s=t>0?`${Q(u)} of ${Q(t)}`:Q(u),o=d=>{r.seek(Number(d.currentTarget.value))};return{state:a,value:u,min:0,max:t>0?t:1,step:"any",ariaValueText:s,ariaLabel:n.seek,onSeekInput:o,onSeekChange:o}}function cr(){const e=b.useGingerPlayback(),r=b.useGingerMedia(),n=A(),a=f.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 dr(e){const r=b.useGingerPlayback(),n=A(),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 fr({playLabel:e="Play",pauseLabel:r="Pause",playAriaLabel:n,pauseAriaLabel:a,type:t="button",onClick:i,...u}){const s=A(),o=typeof e=="string"?e:s.play,d=typeof r=="string"?r:s.pause,m=dr({playAriaLabel:n??o,pauseAriaLabel:a??d});return l.jsx("button",{...u,type:t,"aria-label":m.ariaLabel,onClick:y=>{m.toggle(),i==null||i(y)},children:m.isPaused?e:r})}fr.displayName="Ginger.Control.PlayPause";function gr({type:e="button",onClick:r,...n}){const{repeatMode:a,cycleRepeat:t}=b.useGingerPlayback(),u=A().repeat[a];return l.jsx("button",{...n,type:e,"aria-label":u,onClick:s=>{t(),r==null||r(s)},children:u})}gr.displayName="Ginger.Control.Repeat";function pr({type:e="button",children:r="Next",onClick:n,...a}){const{next:t}=b.useGingerPlayback(),i=A();return l.jsx("button",{...a,type:e,"aria-label":i.nextTrack,onClick:u=>{t(),n==null||n(u)},children:r})}pr.displayName="Ginger.Control.Next";function mr({type:e="button",children:r="Previous",onClick:n,...a}){const{prev:t}=b.useGingerPlayback(),i=A();return l.jsx("button",{...a,type:e,"aria-label":i.previousTrack,onClick:u=>{t(),n==null||n(u)},children:r})}mr.displayName="Ginger.Control.Previous";function yr({type:e="button",children:r="Shuffle",onClick:n,...a}){const{isShuffled:t,toggleShuffle:i}=b.useGingerPlayback(),u=A();return l.jsx("button",{...a,type:e,"aria-pressed":t,"aria-label":u.shuffle,onClick:s=>{i(),n==null||n(s)},children:r})}yr.displayName="Ginger.Control.Shuffle";function br({inputStyle:e,style:r,unstyled:n=!1,...a}){const t=lr(),i=n?{...r,...e}:{width:"100%",...r,...e};return l.jsx("input",{...a,type:"range",min:t.min,max:t.max,step:t.step,value:t.value,"aria-label":t.ariaLabel,"aria-valuetext":t.ariaValueText,onInput:t.onSeekInput,onChange:t.onSeekChange,style:i})}br.displayName="Ginger.Control.SeekBar";function xr({inputStyle:e,style:r,unstyled:n=!1,...a}){const t=cr(),i=n?{...r,...e}:{width:"100%",...r,...e};return l.jsx("input",{...a,type:"range",min:t.min,max:t.max,step:t.step,value:t.value,"aria-label":t.ariaLabel,"aria-valuetext":t.ariaValueText,onInput:t.onVolumeInput,onChange:t.onVolumeChange,style:i})}xr.displayName="Ginger.Control.Volume";function hr({muteLabel:e,unmuteLabel:r,type:n="button",onClick:a,...t}){const{muted:i,toggleMute:u}=b.useGingerMedia(),s=A(),o=e??s.mute,d=r??s.unmute;return l.jsx("button",{...t,type:n,"aria-pressed":i,"aria-label":i?s.unmute:s.mute,onClick:m=>{u(),a==null||a(m)},children:i?d:o})}hr.displayName="Ginger.Control.Mute";const en=[.5,.75,1,1.25,1.5,2];function vr({rates:e=en,style:r,...n}){const{playbackRate:a,setPlaybackRate:t}=b.useGingerMedia(),i=A(),u=f.useMemo(()=>Array.from(new Set([...e,a])).sort((s,o)=>s-o),[e,a]);return l.jsx("select",{...n,"aria-label":i.playbackSpeed,value:String(a),style:r,onChange:s=>t(Number(s.currentTarget.value)),children:u.map(s=>l.jsx("option",{value:String(s),children:s===1?i.playbackRateNormal:i.playbackRateTimes(s)},s))})}vr.displayName="Ginger.Control.PlaybackRate";const Se=f.createContext(null);function rn(){const e=f.useContext(Se);if(!e)throw new Error("Ginger.Playlist.Track must be used inside <Ginger.Playlist>");return e}function kr({children:e,unstyled:r=!1,rowStyle:n,renderTrack:a,playOnSelect:t=!0,style:i,...u}){const{tracks:s,currentIndex:o,playTrackAt:d,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?l.jsx(Se.Provider,{value:{playOnSelect:t},children:l.jsx("ul",{style:y,...u,children:e})}):l.jsx(Se.Provider,{value:{playOnSelect:t},children:l.jsx("ul",{style:y,...u,children:s.map((k,v)=>{const I=v===o;return l.jsx("li",{children:l.jsx("button",{type:"button",onClick:()=>{t?d(v):m(v)},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(k,v,I):l.jsxs("span",{children:[k.title,k.artist?` — ${k.artist}`:""]})})},`${v}-${K(k)}`)})})})}kr.displayName="Ginger.Playlist";function Mr({index:e,unstyled:r=!1,className:n,style:a,children:t,liProps:i,onClick:u,...s}){const{playOnSelect:o}=rn(),{tracks:d,currentIndex:m,playTrackAt:y,selectTrackAt:h}=b.useGingerPlayback(),k=e===m,v=d[e],I=v!=null?l.jsxs("span",{children:[v.title,v.artist?` — ${v.artist}`:""]}):null;return l.jsx("li",{...i,children:l.jsx("button",{type:"button","aria-current":k?"true":void 0,"data-ginger-active":k||void 0,className:n,style:{width:r?void 0:"100%",textAlign:r?void 0:"left",border:r?void 0:"none",background:r?void 0:k?"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},...s,onClick:p=>{u==null||u(p),!p.defaultPrevented&&(o?y(e):h(e))},children:t??I})})}Mr.displayName="Ginger.Playlist.Track";const nn=Object.assign(kr,{Track:Mr}),tn=F("Ginger.Queue.Title",e=>{var r;return(r=e.playlistMeta)==null?void 0:r.title}),an=F("Ginger.Queue.Subtitle",e=>{var r;return(r=e.playlistMeta)==null?void 0:r.subtitle}),un=F("Ginger.Queue.Description",e=>{var r;return(r=e.playlistMeta)==null?void 0:r.description}),sn=F("Ginger.Queue.Copyright",e=>{var r;return(r=e.playlistMeta)==null?void 0:r.copyright});function Tr({className:e,style:r,fallback:n,empty:a,unstyled:t=!1,imgStyle:i}){var d,m;const u=b.useGingerState(),s=(d=u.playlistMeta)==null?void 0:d.artworkUrl;if(!s){const y=a??n??null;return y?l.jsx("span",{className:e,style:r,children:y}):null}const o=((m=u.playlistMeta)==null?void 0:m.title)??"Playlist artwork";return l.jsx("span",{className:e,style:t?{...r}:{display:"inline-block",background:"var(--ginger-artwork-bg, #f3f4f6)",borderRadius:"var(--ginger-artwork-radius, 6px)",overflow:"hidden",...r},children:l.jsx("img",{src:s,alt:o,style:{display:t?void 0:"block",width:t?void 0:"100%",height:t?void 0:"100%",objectFit:t?void 0:"cover",...i}})})}Tr.displayName="Ginger.Queue.Artwork";function X(e){return Number.isFinite(e)?Math.min(1,Math.max(0,e)):1}function ke(e){return Number.isFinite(e)?Math.min(4,Math.max(.25,e)):1}const w={currentTime:0,duration:0,bufferedFraction:0,isBuffering:!1,errorMessage:null},on={...w,volume:1,muted:!1,playbackRate:1};function Er(e){const r=[...e.tracks];let n=S(e.currentIndex??0,r.length),a=null,t=r;return e.isShuffled&&r.length>1&&(a=[...r],t=De(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,...on,volume:X(e.volume??1),muted:e.muted??!1,playbackRate:ke(e.playbackRate??1)}}function ln(e,r){switch(r.type){case"INIT":{const{tracks:n,currentIndex:a,playlistMeta:t,isPaused:i,isShuffled:u,repeatMode:s,playbackMode:o,volume:d,muted:m,playbackRate:y}=r.payload;return Er({tracks:n,currentIndex:a,playlistMeta:t??null,isPaused:i??!0,isShuffled:u??!1,repeatMode:s??"off",playbackMode:o??"playlist",volume:d,muted:m,playbackRate:y})}case"SET_QUEUE":{const{tracks:n,currentIndex:a}=r.payload,t=[...n],i=S(a??e.currentIndex,t.length);return{...e,tracks:t,currentIndex:i,isShuffled:!1,originalTracks:null,...w}}case"INSERT_TRACK":{const n=r.payload.index??e.tracks.length,a=Fe(e.tracks,r.payload.track,n);if(r.payload.autoPlay){const i=S(n,a.length);return{...e,tracks:a,currentIndex:i,isShuffled:!1,originalTracks:null,isPaused:!1,...w}}const t=n<=e.currentIndex?e.currentIndex+1:e.currentIndex;return{...e,tracks:a,isShuffled:!1,originalTracks:null,currentIndex:S(t,a.length)}}case"REMOVE_TRACK":{const n=r.payload.index,a=wr(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;return{...e,tracks:a,isShuffled:!1,originalTracks:null,currentIndex:S(t,a.length),...n===e.currentIndex?w:{}}}case"MOVE_TRACK":{const{fromIndex:n,toIndex:a}=r.payload,t=Dr(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(i,t.length)}}case"ADD_NEXT":{const n=Fr(e.tracks,e.currentIndex,r.payload.track);return{...e,tracks:n,isShuffled:!1,originalTracks:null}}case"SET_INDEX":{const n=S(r.payload.index,e.tracks.length),a=r.payload.autoPlay,t=a===!0?!1:a===!1?!0:e.isPaused;return{...e,currentIndex:n,...w,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:$r(e.repeatMode)};case"TOGGLE_SHUFFLE":{if(e.tracks.length<=1)return{...e,isShuffled:!1,originalTracks:null};if(!e.isShuffled){const i=[...e.tracks],u=De(i,e.currentIndex);return{...e,isShuffled:!0,originalTracks:i,tracks:u,currentIndex:0}}const n=e.originalTracks?[...e.originalTracks]:[...e.tracks],a=e.tracks[e.currentIndex],t=Lr(n,a);return{...e,isShuffled:!1,originalTracks:null,tracks:n,currentIndex:S(t,n.length)}}case"NEXT":{const n=Ue(e),a=n===e.currentIndex;return{...e,currentIndex:n,...a?{}:w,isPaused:a?e.isPaused:!1}}case"PREV":{const n=Vr(e),a=n===e.currentIndex;return{...e,currentIndex:n,...a?{}:w,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,...w};case"SET_VOLUME":return{...e,volume:X(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:ke(r.payload)};case"MEDIA_VOLUME_SYNC":{const{volume:n,muted:a}=r.payload,t=X(n);return t===e.volume&&a===e.muted?e:{...e,volume:t,muted:a}}default:return e}}function Ee(){return typeof navigator>"u"||!("mediaSession"in navigator)?null:navigator.mediaSession}function cn(e,r,n){var o;const a=r.tracks[r.currentIndex],t=a==null?void 0:a.title,i=a==null?void 0:a.artist,u=a==null?void 0:a.album,s=Ve(a,(o=r.playlistMeta)==null?void 0:o.artworkUrl);f.useEffect(()=>{const d=Ee();!e||!d||(d.metadata=new MediaMetadata({title:t??"Unknown track",artist:i,album:u,artwork:s?[{src:s}]:void 0}))},[e,t,i,u,s]),f.useEffect(()=>{const d=Ee();!e||!d||(d.playbackState=r.isPaused?"paused":"playing")},[e,r.isPaused]),f.useEffect(()=>{const d=Ee();if(!(!e||!d)){try{d.setActionHandler("play",n.play),d.setActionHandler("pause",n.pause),d.setActionHandler("nexttrack",n.next),d.setActionHandler("previoustrack",n.prev),d.setActionHandler("seekto",m=>{typeof m.seekTime=="number"&&Number.isFinite(m.seekTime)&&n.seek(m.seekTime)})}catch{}return()=>{try{d.setActionHandler("play",null),d.setActionHandler("pause",null),d.setActionHandler("nexttrack",null),d.setActionHandler("previoustrack",null),d.setActionHandler("seekto",null)}catch{}}}},[e,n])}const dn={"--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 fn({children:e,initialTracks:r=[],initialIndex:n=0,initialPlaylistMeta:a=null,initialShuffle:t=!1,initialRepeatMode:i="off",initialPlaybackMode:u="playlist",initialPaused:s=!0,initialVolume:o=1,initialMuted:d=!1,initialPlaybackRate:m=1,initialStateKey:y,locale:h,mediaSession:k=!1,beforePlay:v,onPlayBlocked:I,persistence:p,hydrateOnMount:E=!1,resumeOnTrackChange:P=!1,unstyled:G=!1,className:Me,style:O,onTrackChange:q,onPlay:z,onPause:W,onQueueEnd:J,onError:Z}){var _e;const D=f.useRef(null),[c,x]=f.useReducer(ln,void 0,()=>Er({tracks:r,currentIndex:n,playlistMeta:a,isPaused:s,isShuffled:t,repeatMode:i,playbackMode:u,volume:o,muted:d,playbackRate:m})),Ce=f.useRef(c),Te=f.useRef({tracks:r,currentIndex:n,playlistMeta:a,isPaused:s,isShuffled:t,repeatMode:i,playbackMode:u,volume:o,muted:d,playbackRate:m});Te.current={tracks:r,currentIndex:n,playlistMeta:a,isPaused:s,isShuffled:t,repeatMode:i,playbackMode:u,volume:o,muted:d,playbackRate:m};const Y=f.useRef(void 0);f.useEffect(()=>{if(y===void 0){Y.current=void 0;return}if(Y.current===void 0){Y.current=y;return}if(Y.current===y)return;Y.current=y;const g=Te.current;x({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,x]),f.useEffect(()=>{Ce.current=c},[c]);const je=c.tracks[c.currentIndex]??null;f.useEffect(()=>{q==null||q(je,c.currentIndex)},[je,c.currentIndex,q]),f.useEffect(()=>{c.errorMessage&&(Z==null||Z(c.errorMessage))},[c.errorMessage,Z]);const ee=f.useRef(void 0);f.useEffect(()=>{if(ee.current===void 0){ee.current=c.isPaused;return}ee.current!==c.isPaused&&(c.isPaused?W==null||W():z==null||z()),ee.current=c.isPaused},[c.isPaused,W,z]);const _=f.useCallback(()=>{x({type:"PLAY"})},[]),N=f.useCallback(()=>{var g;x({type:"PAUSE"}),(g=D.current)==null||g.pause()},[]),re=f.useCallback(()=>{c.isPaused?_():N()},[N,_,c.isPaused]),L=f.useCallback(g=>{const M=D.current;M&&Number.isFinite(g)&&(M.currentTime=Math.max(0,g))},[]),ne=f.useCallback(g=>{x({type:"SET_VOLUME",payload:X(g)})},[]),te=f.useCallback(g=>{x({type:"SET_MUTED",payload:g})},[]),ae=f.useCallback(()=>{x({type:"TOGGLE_MUTE"})},[]),ie=f.useCallback(g=>{x({type:"SET_PLAYBACK_RATE",payload:ke(g)})},[]),V=f.useCallback(()=>{x({type:"NEXT"})},[]),$=f.useCallback(()=>{x({type:"PREV"})},[]),ue=f.useCallback(g=>{x({type:"SET_REPEAT",payload:g})},[]),se=f.useCallback(()=>{x({type:"CYCLE_REPEAT"})},[]),oe=f.useCallback(()=>{x({type:"TOGGLE_SHUFFLE"})},[]),le=f.useCallback((g,M)=>{x({type:"SET_QUEUE",payload:{tracks:g,currentIndex:M}})},[]),ce=f.useCallback((g,M,T)=>{x({type:"INSERT_TRACK",payload:{track:g,index:M,autoPlay:T}})},[]),de=f.useCallback(g=>{x({type:"REMOVE_TRACK",payload:{index:g}})},[]),fe=f.useCallback((g,M)=>{x({type:"MOVE_TRACK",payload:{fromIndex:g,toIndex:M}})},[]),ge=f.useCallback(g=>{x({type:"ADD_NEXT",payload:{track:g}})},[]),pe=f.useCallback(g=>{x({type:"SET_INDEX",payload:{index:g,autoPlay:!0}})},[]),me=f.useCallback(g=>{x({type:"SET_INDEX",payload:{index:g,autoPlay:!1}})},[]),ye=f.useCallback(g=>{x({type:"SET_PLAYLIST_META",payload:g})},[]),be=f.useCallback(g=>{x({type:"SET_PLAYBACK_MODE",payload:g})},[]),xe=f.useCallback(g=>{x({type:"INIT",payload:g})},[]);f.useEffect(()=>{if(!p||!E)return;const g=p.get("ginger:volume"),M=p.get("ginger:muted"),T=p.get("ginger:playbackRate"),C=p.get("ginger:repeatMode"),ve=p.get("ginger:currentIndex"),R=Te.current;x({type:"INIT",payload:{tracks:R.tracks,playlistMeta:R.playlistMeta,isPaused:R.isPaused,isShuffled:R.isShuffled,playbackMode:R.playbackMode,currentIndex:typeof ve=="number"?ve:R.currentIndex,repeatMode:C==="off"||C==="all"||C==="one"?C:R.repeatMode,volume:typeof g=="number"?g:R.volume,muted:typeof M=="boolean"?M:R.muted,playbackRate:typeof T=="number"?T:R.playbackRate}})},[E,p]),f.useEffect(()=>{p&&(p.set("ginger:volume",c.volume),p.set("ginger:muted",c.muted),p.set("ginger:playbackRate",c.playbackRate),p.set("ginger:repeatMode",c.repeatMode),p.set("ginger:currentIndex",c.currentIndex))},[p,c.volume,c.muted,c.playbackRate,c.repeatMode,c.currentIndex]),f.useEffect(()=>{if(!p||!P)return;const g=c.tracks[c.currentIndex];if(!g)return;const M=`ginger:resume:${K(g)}`,T=p.get(M);typeof T=="number"&&Number.isFinite(T)&&L(T)},[p,P,c.currentIndex,c.tracks,L]),f.useEffect(()=>{if(!p||!P)return;const g=c.tracks[c.currentIndex];if(!g||!(c.currentTime>=0))return;const M=`ginger:resume:${K(g)}`,T=setTimeout(()=>p.set(M,c.currentTime),250);return()=>clearTimeout(T)},[p,P,c.currentIndex,c.tracks,c.currentTime]);const Sr=(_e=c.tracks[c.currentIndex])==null?void 0:_e.fileUrl;f.useEffect(()=>{const g=D.current;if(!g)return;if(c.isPaused){g.pause();return}let M=!1;return(async()=>{if(v){let T=!1;try{T=await v()}catch(C){const ve=C instanceof Error?C.message:"beforePlay rejected";x({type:"MEDIA_ERROR",payload:{message:ve}});return}if(!T){M||(x({type:"PAUSE"}),I==null||I());return}}M||g.play().catch(T=>{const C=T instanceof Error?T.message:typeof T=="string"?T:"Playback failed (e.g. autoplay blocked or unavailable source)";x({type:"MEDIA_ERROR",payload:{message:C}})})})(),()=>{M=!0}},[v,Sr,I,c.isPaused]);const he=f.useCallback(()=>{const g=Ur(Ce.current);if(g.kind==="replay_same"){const T=D.current;T&&(T.currentTime=0),x({type:"PLAY"});return}if(g.kind==="stop"){x({type:"PAUSE"}),J==null||J();return}const M=g.nextIndex;x({type:"SET_INDEX",payload:{index:M,autoPlay:!0}})},[J]),Ir=f.useMemo(()=>({play:_,pause:N,next:V,prev:$,seek:L}),[_,N,V,$,L]);cn(!!k,c,Ir);const Pr=h!=null&&h.seek&&/[\u0590-\u08FF]/.test(h.seek)?"rtl":"ltr",Ar=f.useMemo(()=>({state:c,dispatch:x,audioRef:D,notifyEnded:he,init:xe,play:_,pause:N,togglePlayPause:re,seek:L,setVolume:ne,setMuted:te,toggleMute:ae,setPlaybackRate:ie,next:V,prev:$,setRepeatMode:ue,cycleRepeat:se,toggleShuffle:oe,setQueue:le,insertTrackAt:ce,removeTrackAt:de,moveTrack:fe,enqueueNext:ge,playTrackAt:pe,selectTrackAt:me,setPlaylistMeta:ye,setPlaybackMode:be}),[se,x,xe,V,he,N,_,pe,ce,de,fe,ge,me,$,L,te,ie,le,ue,ye,be,ne,c,ae,re,oe]),Rr=f.useMemo(()=>({tracks:c.tracks,currentIndex:c.currentIndex,isPaused:c.isPaused,isShuffled:c.isShuffled,repeatMode:c.repeatMode,originalTracks:c.originalTracks,playlistMeta:c.playlistMeta,init:xe,play:_,pause:N,togglePlayPause:re,next:V,prev:$,setRepeatMode:ue,cycleRepeat:se,toggleShuffle:oe,playbackMode:c.playbackMode,setQueue:le,insertTrackAt:ce,removeTrackAt:de,moveTrack:fe,enqueueNext:ge,playTrackAt:pe,selectTrackAt:me,setPlaylistMeta:ye,setPlaybackMode:be,dispatch:x}),[c.tracks,c.currentIndex,c.isPaused,c.isShuffled,c.repeatMode,c.playbackMode,c.originalTracks,c.playlistMeta,xe,_,N,re,V,$,ue,se,oe,le,ce,de,fe,ge,pe,me,ye,be,x]),Gr=f.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:L,setVolume:ne,setMuted:te,toggleMute:ae,setPlaybackRate:ie,audioRef:D,notifyEnded:he,dispatch:x}),[c.currentTime,c.duration,c.bufferedFraction,c.isBuffering,c.errorMessage,c.volume,c.muted,c.playbackRate,L,ne,te,ae,ie,D,he,x]),Cr=Ie(c),jr=f.useMemo(()=>G?O:{...dn,...O},[O,G]);return l.jsx(Zr,{locale:h,children:l.jsx(b.GingerPlaybackContext.Provider,{value:Rr,children:l.jsx(b.GingerMediaContext.Provider,{value:Gr,children:l.jsx(we.Provider,{value:Ar,children:l.jsx("div",{className:Me,style:jr,"data-ginger-playback":Cr,dir:Pr,children:e})})})})})}const gn={Provider:fn,Player:Nr,Current:{Title:Or,Artist:Yr,Album:Qr,Description:Hr,Copyright:Kr,Genre:Xr,Label:qr,Isrc:zr,TrackNumber:Wr,Year:Ye,Lyrics:Qe,LyricsSynced:Ae,Chapters:Re,FileUrl:qe,Artwork:ze,QueueIndex:We,QueueLength:Je,QueuePosition:Ze,Elapsed:er,Duration:rr,Remaining:nr,Progress:tr,TimeRail:ar,BufferRail:ir,PlaybackState:ur,ErrorMessage:sr},Queue:{Title:tn,Subtitle:an,Description:un,Copyright:sn,Artwork:Tr},Control:{PlayPause:fr,Repeat:gr,Next:pr,Previous:mr,Shuffle:yr,SeekBar:br,Volume:xr,Mute:hr,PlaybackRate:vr},Playlist:nn};exports.Chapters=Re;exports.Ginger=gn;exports.LyricsSynced=Ae;exports.clampPlaybackRate=ke;exports.clampVolume=X;exports.computeNextIndex=Ue;exports.defaultGingerLocale=H;exports.derivePlaybackUiState=Ie;exports.effectiveDuration=B;exports.effectiveRemaining=$e;exports.getCurrentTrack=j;exports.parseLrc=He;exports.progressFraction=Pe;exports.resolvedAlbumLine=Oe;exports.resolvedArtwork=Be;exports.useGingerChapters=Xe;exports.useGingerLocale=A;exports.useGingerLyricsSync=Ke;exports.usePlayPauseBinding=dr;exports.useSeekBarBinding=lr;exports.useVolumeSlider=cr;
2
- //# sourceMappingURL=ginger-DWOyCog6.cjs.map