@lucaismyname/ginger 0.0.59 → 0.0.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -3
- package/dist/cast/castTypes.d.ts +130 -0
- package/dist/cast/castTypes.d.ts.map +1 -0
- package/dist/cast/index.cjs +2 -0
- package/dist/cast/index.cjs.map +1 -0
- package/dist/cast/index.d.ts +7 -0
- package/dist/cast/index.d.ts.map +1 -0
- package/dist/cast/index.js +254 -0
- package/dist/cast/index.js.map +1 -0
- package/dist/cast/loadCastFramework.d.ts +8 -0
- package/dist/cast/loadCastFramework.d.ts.map +1 -0
- package/dist/cast/loadCastFramework.test.d.ts +2 -0
- package/dist/cast/loadCastFramework.test.d.ts.map +1 -0
- package/dist/cast/trackToMediaInfo.d.ts +14 -0
- package/dist/cast/trackToMediaInfo.d.ts.map +1 -0
- package/dist/cast/trackToMediaInfo.test.d.ts +2 -0
- package/dist/cast/trackToMediaInfo.test.d.ts.map +1 -0
- package/dist/cast/useGingerCast.d.ts +57 -0
- package/dist/cast/useGingerCast.d.ts.map +1 -0
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -64,7 +64,7 @@ For docs beyond this README, use the repository links below:
|
|
|
64
64
|
- Streaming adapters: [`docs/guides/streaming-adapters.md`](https://github.com/lucaismyname/ginger/blob/main/packages/ginger/docs/guides/streaming-adapters.md)
|
|
65
65
|
- Components reference: [`docs/reference/components.md`](https://github.com/lucaismyname/ginger/blob/main/packages/ginger/docs/reference/components.md)
|
|
66
66
|
- Hooks reference: [`docs/reference/hooks.md`](https://github.com/lucaismyname/ginger/blob/main/packages/ginger/docs/reference/hooks.md)
|
|
67
|
-
- Subpath exports (waveform, EQ, spatial, transcript, remote, crossfade, …): [`docs/reference/subpaths.md`](https://github.com/lucaismyname/ginger/blob/main/packages/ginger/docs/reference/subpaths.md)
|
|
67
|
+
- Subpath exports (waveform, EQ, spatial, transcript, remote, cast, crossfade, …): [`docs/reference/subpaths.md`](https://github.com/lucaismyname/ginger/blob/main/packages/ginger/docs/reference/subpaths.md)
|
|
68
68
|
- Generated API docs: [`docs/api/index.html`](https://github.com/lucaismyname/ginger/blob/main/packages/ginger/docs/api/index.html)
|
|
69
69
|
|
|
70
70
|
## Subpath Exports
|
|
@@ -76,6 +76,7 @@ For docs beyond this README, use the repository links below:
|
|
|
76
76
|
- `@lucaismyname/ginger/spatial`
|
|
77
77
|
- `@lucaismyname/ginger/transcript`
|
|
78
78
|
- `@lucaismyname/ginger/remote`
|
|
79
|
+
- `@lucaismyname/ginger/cast`
|
|
79
80
|
- `@lucaismyname/ginger/crossfade`
|
|
80
81
|
- `@lucaismyname/ginger/experimental-gapless`
|
|
81
82
|
- `@lucaismyname/ginger/devtools`
|
|
@@ -210,6 +211,31 @@ Snapshots send the current queue order with **`isShuffled: false`** so followers
|
|
|
210
211
|
|
|
211
212
|
`@lucaismyname/ginger/remote` also exports **`DEFAULT_REMOTE_CHANNEL_NAME`** and the **`RemoteMessage`** type if you need to share protocol constants or type your own channel helpers.
|
|
212
213
|
|
|
214
|
+
### Chromecast (`@lucaismyname/ginger/cast`)
|
|
215
|
+
|
|
216
|
+
Loads the **Google Cast Web Sender** (CAF), exposes **`useGingerCast`** for session + **`loadMedia`** sync, and helpers **`loadCastFramework`**, **`trackToMediaInfo`**, **`guessContentTypeFromUrl`**. The default receiver is the **Default Media Receiver**; override with **`receiverApplicationId`**.
|
|
217
|
+
|
|
218
|
+
**Platform:** Cast requires **HTTPS** in production (localhost is allowed for development). **`Track.fileUrl`** must be fetchable by the **Cast device** with correct **CORS**; avoid **mixed content**.
|
|
219
|
+
|
|
220
|
+
**Avoid double playback:** render **`{!isCasting && <Ginger.Player />}`** so the browser does not decode the same URLs as the TV. Optional **`syncLocalAudio: "pause-mute"`** mutes the local `<audio>` while connected.
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
import { Ginger } from "@lucaismyname/ginger";
|
|
224
|
+
import { useGingerCast } from "@lucaismyname/ginger/cast";
|
|
225
|
+
|
|
226
|
+
function WithCast() {
|
|
227
|
+
const { isCasting, requestSession, endSession, error } = useGingerCast();
|
|
228
|
+
return (
|
|
229
|
+
<>
|
|
230
|
+
{error && <p role="alert">{error}</p>}
|
|
231
|
+
<button type="button" onClick={() => void requestSession()}>Cast</button>
|
|
232
|
+
<button type="button" onClick={endSession}>Stop</button>
|
|
233
|
+
{!isCasting && <Ginger.Player />}
|
|
234
|
+
</>
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
213
239
|
### Crossfade (`@lucaismyname/ginger/crossfade`)
|
|
214
240
|
|
|
215
241
|
Adds a Web Audio crossfade graph for **overlap-based** transitions between outgoing and incoming media. This is distinct from the longer-term **gapless** work: crossfade overlaps two sources on purpose, while gapless aims for seamless adjacent track boundaries on a single playback path.
|
|
@@ -1313,11 +1339,12 @@ Additional entrypoints:
|
|
|
1313
1339
|
- `@lucaismyname/ginger/spatial`
|
|
1314
1340
|
- `@lucaismyname/ginger/transcript`
|
|
1315
1341
|
- `@lucaismyname/ginger/remote`
|
|
1342
|
+
- `@lucaismyname/ginger/cast`
|
|
1316
1343
|
- `@lucaismyname/ginger/crossfade`
|
|
1317
1344
|
- `@lucaismyname/ginger/experimental-gapless`
|
|
1318
1345
|
- `@lucaismyname/ginger/devtools`
|
|
1319
1346
|
|
|
1320
|
-
See [Subpath Exports](#subpath-exports) for **`spatial`**, **`transcript`**, **`remote`**, and **`devtools`** usage. `experimental-gapless` is explicitly non-production and does not alter core playback.
|
|
1347
|
+
See [Subpath Exports](#subpath-exports) for **`spatial`**, **`transcript`**, **`remote`**, **`cast`**, and **`devtools`** usage. `experimental-gapless` is explicitly non-production and does not alter core playback.
|
|
1321
1348
|
|
|
1322
1349
|
## Notes
|
|
1323
1350
|
|
|
@@ -1339,7 +1366,7 @@ These priorities guide new work in the library; they are not a guarantee of ship
|
|
|
1339
1366
|
|
|
1340
1367
|
1. **Music libraries and continuous listening** — Features that make track-to-track playback feel better come first: **next-track prefetch** (`useNextTrackPrefetch`), shipped **crossfade** (`@lucaismyname/ginger/crossfade`), ongoing **gapless** capability work (`@lucaismyname/ginger/experimental-gapless`), and first-class **chapter** / **synced lyrics** UI (`Ginger.Current.Chapters`, `Ginger.Current.LyricsSynced`).
|
|
1341
1368
|
2. **Podcasts and live-style streams** — **HLS / DASH** integration is emphasized when a concrete app needs it; the core package stays on native `<audio>` with optional adapters or documentation rather than hard dependencies. **SRT / WebVTT** transcripts are supported via `@lucaismyname/ginger/transcript`.
|
|
1342
|
-
3. **Embedded or internal players** — **Accessibility**, persistence, and **testing** helpers are favored over heavier ecosystem integrations
|
|
1369
|
+
3. **Embedded or internal players** — **Accessibility**, persistence, and **testing** helpers are favored over heavier ecosystem integrations unless there is a dedicated use case. For **Chromecast**, opt‑in to `@lucaismyname/ginger/cast`. **Multi-tab** web apps can use `@lucaismyname/ginger/remote` (BroadcastChannel) before reaching for OS-level remote playback.
|
|
1343
1370
|
|
|
1344
1371
|
## Monorepo Development
|
|
1345
1372
|
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal typings for the subset of the Google Cast Web Sender API used by Ginger.
|
|
3
|
+
* Runtime objects come from the loaded Cast Framework script (`loadCastFramework`).
|
|
4
|
+
*/
|
|
5
|
+
/** Default Media Receiver application ID (audio/video). */
|
|
6
|
+
export declare const DEFAULT_MEDIA_RECEIVER_APP_ID = "CC1AD845";
|
|
7
|
+
export type CastImage = {
|
|
8
|
+
url: string;
|
|
9
|
+
width?: number;
|
|
10
|
+
height?: number;
|
|
11
|
+
};
|
|
12
|
+
/** Instance shape from `new chrome.cast.media.MusicTrackMediaMetadata()`. */
|
|
13
|
+
export type CastMusicTrackMetadata = {
|
|
14
|
+
metadataType: number;
|
|
15
|
+
title?: string;
|
|
16
|
+
artist?: string;
|
|
17
|
+
albumName?: string;
|
|
18
|
+
images?: CastImage[];
|
|
19
|
+
};
|
|
20
|
+
export type CastMediaInfoLike = {
|
|
21
|
+
contentId: string;
|
|
22
|
+
contentType: string;
|
|
23
|
+
streamType?: string;
|
|
24
|
+
metadata?: CastMusicTrackMetadata;
|
|
25
|
+
};
|
|
26
|
+
export type CastLoadRequestLike = {
|
|
27
|
+
media: CastMediaInfoLike;
|
|
28
|
+
autoplay?: boolean;
|
|
29
|
+
currentTime?: number;
|
|
30
|
+
};
|
|
31
|
+
export type CastMediaInfoConstructor = new (contentId: string, contentType: string) => CastMediaInfoLike;
|
|
32
|
+
export type CastMusicTrackMetadataConstructor = new () => CastMusicTrackMetadata;
|
|
33
|
+
export type CastLoadRequestConstructor = new (mediaInfo: CastMediaInfoLike) => CastLoadRequestLike;
|
|
34
|
+
/** Subset of `chrome.cast.Session` methods we call (duck-typed). */
|
|
35
|
+
export type CastSessionLike = {
|
|
36
|
+
loadMedia: (request: CastLoadRequestLike, successCallback: (media?: unknown) => void, errorCallback: (error: CastErrorLike) => void) => void;
|
|
37
|
+
endSession: (successCallback: () => void, errorCallback: (error: CastErrorLike) => void) => void;
|
|
38
|
+
};
|
|
39
|
+
export type CastErrorLike = {
|
|
40
|
+
code?: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
};
|
|
43
|
+
export type CastContextOptionsLike = {
|
|
44
|
+
receiverApplicationId: string;
|
|
45
|
+
autoJoinPolicy?: number;
|
|
46
|
+
resumeSavedSession?: boolean;
|
|
47
|
+
};
|
|
48
|
+
/** `cast.framework.CastContext` duck type. */
|
|
49
|
+
export type CastFrameworkContextLike = {
|
|
50
|
+
setOptions: (options: CastContextOptionsLike) => void;
|
|
51
|
+
getCastState: () => number;
|
|
52
|
+
getCurrentSession: () => CastSessionLike | null;
|
|
53
|
+
addEventListener: (type: string, listener: (event: {
|
|
54
|
+
sessionState?: string;
|
|
55
|
+
}) => void) => void;
|
|
56
|
+
removeEventListener: (type: string, listener: (event: {
|
|
57
|
+
sessionState?: string;
|
|
58
|
+
}) => void) => void;
|
|
59
|
+
requestSession: () => Promise<void>;
|
|
60
|
+
};
|
|
61
|
+
export type CastFrameworkNamespace = {
|
|
62
|
+
CastContext: {
|
|
63
|
+
getInstance: () => CastFrameworkContextLike;
|
|
64
|
+
};
|
|
65
|
+
CastContextEventType: {
|
|
66
|
+
CAST_STATE_CHANGED: string;
|
|
67
|
+
SESSION_STATE_CHANGED: string;
|
|
68
|
+
};
|
|
69
|
+
CastState: {
|
|
70
|
+
NO_DEVICES: number;
|
|
71
|
+
NOT_CONNECTED: number;
|
|
72
|
+
CONNECTING: number;
|
|
73
|
+
CONNECTED: number;
|
|
74
|
+
};
|
|
75
|
+
SessionState: {
|
|
76
|
+
SESSION_STARTED: string;
|
|
77
|
+
SESSION_ENDED: string;
|
|
78
|
+
SESSION_RESUMED: string;
|
|
79
|
+
};
|
|
80
|
+
RemotePlayer: new () => CastRemotePlayerLike;
|
|
81
|
+
RemotePlayerController: new (player: CastRemotePlayerLike) => CastRemotePlayerControllerLike;
|
|
82
|
+
RemotePlayerEventType: {
|
|
83
|
+
IS_PAUSED_CHANGED: string;
|
|
84
|
+
CURRENT_TIME_CHANGED: string;
|
|
85
|
+
DURATION_CHANGED: string;
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
export type CastRemotePlayerLike = {
|
|
89
|
+
isPaused: boolean;
|
|
90
|
+
currentTime: number;
|
|
91
|
+
duration: number;
|
|
92
|
+
controller?: CastRemotePlayerControllerLike;
|
|
93
|
+
addEventListener: (type: string, handler: () => void) => void;
|
|
94
|
+
removeEventListener: (type: string, handler: () => void) => void;
|
|
95
|
+
};
|
|
96
|
+
export type CastRemotePlayerControllerLike = {
|
|
97
|
+
playOrPause: () => void;
|
|
98
|
+
seek: (options: {
|
|
99
|
+
value: number;
|
|
100
|
+
}) => void;
|
|
101
|
+
stop: () => void;
|
|
102
|
+
};
|
|
103
|
+
export type ChromeCastNamespace = {
|
|
104
|
+
media: {
|
|
105
|
+
DEFAULT_MEDIA_RECEIVER_APP_ID: string;
|
|
106
|
+
MediaInfo: CastMediaInfoConstructor;
|
|
107
|
+
MusicTrackMediaMetadata: CastMusicTrackMetadataConstructor;
|
|
108
|
+
LoadRequest: CastLoadRequestConstructor;
|
|
109
|
+
MetadataType: {
|
|
110
|
+
MUSIC_TRACK: number;
|
|
111
|
+
};
|
|
112
|
+
StreamType: {
|
|
113
|
+
BUFFERED: string;
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
isAvailable: boolean;
|
|
117
|
+
AutoJoinPolicy: {
|
|
118
|
+
ORIGIN_SCOPED: number;
|
|
119
|
+
TAB_AND_ORIGIN_SCOPED: number;
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
export type WindowWithCast = Window & {
|
|
123
|
+
cast?: {
|
|
124
|
+
framework?: CastFrameworkNamespace;
|
|
125
|
+
};
|
|
126
|
+
chrome?: {
|
|
127
|
+
cast?: ChromeCastNamespace;
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
//# sourceMappingURL=castTypes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"castTypes.d.ts","sourceRoot":"","sources":["../../src/cast/castTypes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("react"),oe=require("../useGinger-BMRLzjmr.cjs"),ue=require("../selectors-CEGlYoFu.cjs"),J="CC1AD845",q="https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1";let b=null;function ae(){if(!(typeof window>"u"))return window}function M(){var e,i;const u=ae();return u?(i=(e=u.cast)==null?void 0:e.framework)!=null&&i.CastContext?Promise.resolve():b||(b=new Promise((f,c)=>{const l=document.querySelector(`script[src="${q}"]`);if(l){const T=()=>{var p,a;(a=(p=u.cast)==null?void 0:p.framework)!=null&&a.CastContext?f():c(new Error("Cast script loaded but cast.framework is missing."))};if(l.dataset.gingerCastLoaded==="true"){T();return}l.addEventListener("load",T,{once:!0}),l.addEventListener("error",()=>c(new Error("Cast script failed to load.")),{once:!0});return}const m=document.createElement("script");m.src=q,m.async=!0,m.addEventListener("load",()=>{var T,p;m.dataset.gingerCastLoaded="true",(p=(T=u.cast)==null?void 0:T.framework)!=null&&p.CastContext?f():c(new Error("Cast script loaded but cast.framework is missing."))}),m.addEventListener("error",()=>{c(new Error("Cast script failed to load."))}),document.head.appendChild(m)}),b):Promise.reject(new Error("Cast is only available in a browser environment."))}const ie={".mp3":"audio/mpeg",".aac":"audio/aac",".m4a":"audio/mp4",".ogg":"audio/ogg",".opus":"audio/opus",".wav":"audio/wav",".flac":"audio/flac",".webm":"audio/webm"};function K(u){try{const e=new URL(u,"https://example.com").pathname.toLowerCase(),i=e.lastIndexOf(".");if(i===-1)return"audio/mpeg";const f=e.slice(i);return ie[f]??"audio/mpeg"}catch{return"audio/mpeg"}}function $(u,e,i){var m;const f=((m=i==null?void 0:i.contentTypeResolver)==null?void 0:m.call(i,e))??K(e.fileUrl),c=new u.media.MediaInfo(e.fileUrl,f);c.streamType=u.media.StreamType.BUFFERED;const l=new u.media.MusicTrackMediaMetadata;return l.metadataType=u.media.MetadataType.MUSIC_TRACK,l.title=e.title,e.artist&&(l.artist=e.artist),e.album&&(l.albumName=e.album),e.artworkUrl&&(l.images=[{url:e.artworkUrl}]),c.metadata=l,new u.media.LoadRequest(c)}const ce=2;function A(u){var f,c;const e=(f=u.cast)==null?void 0:f.framework,i=(c=u.chrome)==null?void 0:c.cast;return!(e!=null&&e.CastContext)||!(i!=null&&i.media)?null:{context:e.CastContext.getInstance(),framework:e,chromeCast:i}}function le(u){if(u instanceof Error)return u.message;if(u&&typeof u=="object"&&"description"in u){const e=u.description;if(typeof e=="string"&&e.length>0)return e}return"Unknown Cast error"}function de(u={}){const{enabled:e=!0,receiverApplicationId:i=J,autoJoinPolicy:f,resumeSavedSession:c=!0,syncLocalAudio:l="pause-mute",endSessionOnUnmount:m=!1,contentTypeResolver:T,onError:p}=u,{state:a,audioRef:R}=oe.useGinger(),y=ue.getCurrentTrack(a),[w,j]=n.useState(!1),[B,V]=n.useState(!1),[E,W]=n.useState(!1),[X,z]=n.useState(null),[Q,L]=n.useState(null),[Y,Z]=n.useState(0),[ee,te]=n.useState(0),[ne,re]=n.useState(!0),U=n.useRef(null),_=n.useRef(null),g=n.useRef(null),P=n.useRef(0),N=n.useRef(-1),S=n.useRef(null),G=n.useRef(0),O=n.useRef(a.currentTime);O.current=a.currentTime;const D=n.useRef(p);D.current=p;const v=n.useCallback(s=>{var r;const t=le(s);L(t);try{(r=D.current)==null||r.call(D,(s instanceof Error,s))}catch{}},[]);n.useEffect(()=>{if(!e)return;let s=!1;return M().then(()=>{var r,o;if(s)return;j(!0),V(!!((o=(r=window.chrome)==null?void 0:r.cast)!=null&&o.isAvailable))}).catch(t=>{s||v(t instanceof Error?t:new Error(String(t)))}),()=>{s=!0}},[e,v]),n.useEffect(()=>{var H;if(!e||!w)return;const t=A(window);if(!t)return;const{context:r,framework:o,chromeCast:I}=t,d=f??((H=I.AutoJoinPolicy)==null?void 0:H.ORIGIN_SCOPED)??1;r.setOptions({receiverApplicationId:i,autoJoinPolicy:d,resumeSavedSession:c});const C=()=>{const x=r.getCastState()===o.CastState.CONNECTED;W(x)},h=()=>C(),F=x=>{x.sessionState&&z(x.sessionState),C()};return r.addEventListener(o.CastContextEventType.CAST_STATE_CHANGED,h),r.addEventListener(o.CastContextEventType.SESSION_STATE_CHANGED,F),C(),()=>{r.removeEventListener(o.CastContextEventType.CAST_STATE_CHANGED,h),r.removeEventListener(o.CastContextEventType.SESSION_STATE_CHANGED,F)}},[e,w,i,f,c]),n.useEffect(()=>{if(!e||!w)return;const t=A(window);if(!t)return;const{framework:r}=t,o=new r.RemotePlayer,I=new r.RemotePlayerController(o);U.current=o,_.current=I;const d=()=>{Z(o.currentTime),te(o.duration),re(o.isPaused)},{RemotePlayerEventType:C}=r;return o.addEventListener(C.IS_PAUSED_CHANGED,d),o.addEventListener(C.CURRENT_TIME_CHANGED,d),o.addEventListener(C.DURATION_CHANGED,d),d(),()=>{o.removeEventListener(C.IS_PAUSED_CHANGED,d),o.removeEventListener(C.CURRENT_TIME_CHANGED,d),o.removeEventListener(C.DURATION_CHANGED,d),U.current=null,_.current=null}},[e,w]),n.useEffect(()=>{if(!e||l==="none")return;const s=R.current;if(!E||!s){S.current&&s&&(s.muted=S.current.muted,s.volume=S.current.volume,S.current=null);return}return S.current||(S.current={volume:s.volume,muted:s.muted}),s.muted=!0,s.volume=0,()=>{S.current&&R.current&&(R.current.muted=S.current.muted,R.current.volume=S.current.volume,S.current=null)}},[e,l,E,R]),n.useEffect(()=>{if(!e||!w||!E)return;const t=A(window);if(!t)return;const r=t.context.getCurrentSession();if(!r||!y)return;const o=`${a.currentIndex}:${y.fileUrl}`,I=t.chromeCast;if(g.current===o)return;g.current=o,G.current=Date.now()+500;const d=$(I,y,{contentTypeResolver:T});d.autoplay=!a.isPaused,d.currentTime=0,r.loadMedia(d,()=>{N.current=a.currentIndex,P.current=O.current},C=>v(C))},[e,w,E,a.currentIndex,a.isPaused,y,T,v]),n.useEffect(()=>{E||(g.current=null)},[E]),n.useEffect(()=>{if(!e||!w||!E||!y)return;const s=`${a.currentIndex}:${y.fileUrl}`;if(g.current!==s||Date.now()<G.current)return;const t=U.current,r=_.current;!t||!r||t.isPaused!==a.isPaused&&r.playOrPause()},[e,w,E,a.isPaused,a.currentIndex,y]),n.useEffect(()=>{var t;if(!e||!E)return;if(N.current!==a.currentIndex){N.current=a.currentIndex,P.current=a.currentTime;return}Math.abs(a.currentTime-P.current)>ce&&((t=_.current)==null||t.seek({value:a.currentTime})),P.current=a.currentTime},[e,E,a.currentTime,a.currentIndex]);const se=n.useCallback(async()=>{w||await M();const t=A(window);if(!t){v(new Error("Cast framework is not available."));return}try{await t.context.requestSession(),L(null)}catch(r){v(r)}},[v,w]),k=n.useCallback(()=>{const t=A(window),r=t==null?void 0:t.context.getCurrentSession();r&&r.endSession(()=>{g.current=null,L(null)},o=>v(o))},[v]);return n.useEffect(()=>{if(m)return()=>{k()}},[m,k]),{isAvailable:B,isConnected:E,isCasting:E,sessionState:X,error:Q,requestSession:se,endSession:k,receiverCurrentTime:Y,receiverDuration:ee,receiverIsPaused:ne}}exports.DEFAULT_MEDIA_RECEIVER_APP_ID=J;exports.guessContentTypeFromUrl=K;exports.loadCastFramework=M;exports.trackToMediaInfo=$;exports.useGingerCast=de;
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../../src/cast/castTypes.ts","../../src/cast/loadCastFramework.ts","../../src/cast/trackToMediaInfo.ts","../../src/cast/useGingerCast.ts"],"sourcesContent":["/**\n * Minimal typings for the subset of the Google Cast Web Sender API used by Ginger.\n * Runtime objects come from the loaded Cast Framework script (`loadCastFramework`).\n */\n\n/** Default Media Receiver application ID (audio/video). */\nexport const DEFAULT_MEDIA_RECEIVER_APP_ID = \"CC1AD845\";\n\nexport type CastImage = {\n url: string;\n width?: number;\n height?: number;\n};\n\n/** Instance shape from `new chrome.cast.media.MusicTrackMediaMetadata()`. */\nexport type CastMusicTrackMetadata = {\n metadataType: number;\n title?: string;\n artist?: string;\n albumName?: string;\n images?: CastImage[];\n};\n\nexport type CastMediaInfoLike = {\n contentId: string;\n contentType: string;\n streamType?: string;\n metadata?: CastMusicTrackMetadata;\n};\n\nexport type CastLoadRequestLike = {\n media: CastMediaInfoLike;\n autoplay?: boolean;\n currentTime?: number;\n};\n\nexport type CastMediaInfoConstructor = new (\n contentId: string,\n contentType: string,\n) => CastMediaInfoLike;\nexport type CastMusicTrackMetadataConstructor = new () => CastMusicTrackMetadata;\nexport type CastLoadRequestConstructor = new (mediaInfo: CastMediaInfoLike) => CastLoadRequestLike;\n\n/** Subset of `chrome.cast.Session` methods we call (duck-typed). */\nexport type CastSessionLike = {\n loadMedia: (\n request: CastLoadRequestLike,\n successCallback: (media?: unknown) => void,\n errorCallback: (error: CastErrorLike) => void,\n ) => void;\n endSession: (successCallback: () => void, errorCallback: (error: CastErrorLike) => void) => void;\n};\n\nexport type CastErrorLike = {\n code?: string;\n description?: string;\n};\n\nexport type CastContextOptionsLike = {\n receiverApplicationId: string;\n autoJoinPolicy?: number;\n resumeSavedSession?: boolean;\n};\n\n/** `cast.framework.CastContext` duck type. */\nexport type CastFrameworkContextLike = {\n setOptions: (options: CastContextOptionsLike) => void;\n getCastState: () => number;\n getCurrentSession: () => CastSessionLike | null;\n addEventListener: (type: string, listener: (event: { sessionState?: string }) => void) => void;\n removeEventListener: (type: string, listener: (event: { sessionState?: string }) => void) => void;\n requestSession: () => Promise<void>;\n};\n\nexport type CastFrameworkNamespace = {\n CastContext: {\n getInstance: () => CastFrameworkContextLike;\n };\n CastContextEventType: {\n CAST_STATE_CHANGED: string;\n SESSION_STATE_CHANGED: string;\n };\n CastState: {\n NO_DEVICES: number;\n NOT_CONNECTED: number;\n CONNECTING: number;\n CONNECTED: number;\n };\n SessionState: {\n SESSION_STARTED: string;\n SESSION_ENDED: string;\n SESSION_RESUMED: string;\n };\n RemotePlayer: new () => CastRemotePlayerLike;\n RemotePlayerController: new (player: CastRemotePlayerLike) => CastRemotePlayerControllerLike;\n RemotePlayerEventType: {\n IS_PAUSED_CHANGED: string;\n CURRENT_TIME_CHANGED: string;\n DURATION_CHANGED: string;\n };\n};\n\nexport type CastRemotePlayerLike = {\n isPaused: boolean;\n currentTime: number;\n duration: number;\n controller?: CastRemotePlayerControllerLike;\n addEventListener: (type: string, handler: () => void) => void;\n removeEventListener: (type: string, handler: () => void) => void;\n};\n\nexport type CastRemotePlayerControllerLike = {\n playOrPause: () => void;\n seek: (options: { value: number }) => void;\n stop: () => void;\n};\n\nexport type ChromeCastNamespace = {\n media: {\n DEFAULT_MEDIA_RECEIVER_APP_ID: string;\n MediaInfo: CastMediaInfoConstructor;\n MusicTrackMediaMetadata: CastMusicTrackMetadataConstructor;\n LoadRequest: CastLoadRequestConstructor;\n MetadataType: {\n MUSIC_TRACK: number;\n };\n StreamType: {\n BUFFERED: string;\n };\n };\n isAvailable: boolean;\n AutoJoinPolicy: {\n ORIGIN_SCOPED: number;\n TAB_AND_ORIGIN_SCOPED: number;\n };\n};\n\nexport type WindowWithCast = Window & {\n cast?: { framework?: CastFrameworkNamespace };\n chrome?: { cast?: ChromeCastNamespace };\n};\n","import type { WindowWithCast } from \"./castTypes\";\n\nconst CAST_SCRIPT_SRC =\n \"https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1\";\n\nlet loadPromise: Promise<void> | null = null;\n\nfunction getWindow(): WindowWithCast | undefined {\n if (typeof window === \"undefined\") return undefined;\n return window as WindowWithCast;\n}\n\n/**\n * Loads the Google Cast Web Sender script (Cast Application Framework) once.\n * Resolves when `window.cast.framework` is available. Safe to call multiple times.\n *\n * In SSR environments (no `window`), rejects with an error.\n */\nexport function loadCastFramework(): Promise<void> {\n const win = getWindow();\n if (!win) {\n return Promise.reject(new Error(\"Cast is only available in a browser environment.\"));\n }\n\n if (win.cast?.framework?.CastContext) {\n return Promise.resolve();\n }\n\n if (loadPromise) {\n return loadPromise;\n }\n\n loadPromise = new Promise((resolve, reject) => {\n const existing = document.querySelector(`script[src=\"${CAST_SCRIPT_SRC}\"]`);\n if (existing) {\n const onLoad = () => {\n if (win.cast?.framework?.CastContext) {\n resolve();\n } else {\n reject(new Error(\"Cast script loaded but cast.framework is missing.\"));\n }\n };\n if ((existing as HTMLScriptElement).dataset.gingerCastLoaded === \"true\") {\n onLoad();\n return;\n }\n existing.addEventListener(\"load\", onLoad, { once: true });\n existing.addEventListener(\"error\", () => reject(new Error(\"Cast script failed to load.\")), {\n once: true,\n });\n return;\n }\n\n const script = document.createElement(\"script\");\n script.src = CAST_SCRIPT_SRC;\n script.async = true;\n script.addEventListener(\"load\", () => {\n script.dataset.gingerCastLoaded = \"true\";\n if (win.cast?.framework?.CastContext) {\n resolve();\n } else {\n reject(new Error(\"Cast script loaded but cast.framework is missing.\"));\n }\n });\n script.addEventListener(\"error\", () => {\n reject(new Error(\"Cast script failed to load.\"));\n });\n document.head.appendChild(script);\n });\n\n return loadPromise;\n}\n","import type { Track } from \"../types\";\nimport type { CastLoadRequestLike, ChromeCastNamespace } from \"./castTypes\";\n\nconst EXT_TO_MIME: Record<string, string> = {\n \".mp3\": \"audio/mpeg\",\n \".aac\": \"audio/aac\",\n \".m4a\": \"audio/mp4\",\n \".ogg\": \"audio/ogg\",\n \".opus\": \"audio/opus\",\n \".wav\": \"audio/wav\",\n \".flac\": \"audio/flac\",\n \".webm\": \"audio/webm\",\n};\n\n/**\n * Guess a MIME type from a file URL path. Falls back to `audio/mpeg`.\n */\nexport function guessContentTypeFromUrl(fileUrl: string): string {\n try {\n const path = new URL(fileUrl, \"https://example.com\").pathname.toLowerCase();\n const dot = path.lastIndexOf(\".\");\n if (dot === -1) return \"audio/mpeg\";\n const ext = path.slice(dot);\n return EXT_TO_MIME[ext] ?? \"audio/mpeg\";\n } catch {\n return \"audio/mpeg\";\n }\n}\n\n/**\n * Builds a Cast `LoadRequest` for the given track using the runtime `chrome.cast.media` constructors.\n * Call only after `loadCastFramework()` and when `chrome.cast` is defined.\n */\nexport function trackToMediaInfo(\n chromeCast: ChromeCastNamespace,\n track: Track,\n options?: {\n contentTypeResolver?: (t: Track) => string;\n },\n): CastLoadRequestLike {\n const contentType =\n options?.contentTypeResolver?.(track) ?? guessContentTypeFromUrl(track.fileUrl);\n\n const media = new chromeCast.media.MediaInfo(track.fileUrl, contentType);\n media.streamType = chromeCast.media.StreamType.BUFFERED;\n\n const meta = new chromeCast.media.MusicTrackMediaMetadata();\n meta.metadataType = chromeCast.media.MetadataType.MUSIC_TRACK;\n meta.title = track.title;\n if (track.artist) {\n meta.artist = track.artist;\n }\n if (track.album) {\n meta.albumName = track.album;\n }\n if (track.artworkUrl) {\n meta.images = [{ url: track.artworkUrl }];\n }\n\n media.metadata = meta;\n\n return new chromeCast.media.LoadRequest(media);\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useGinger } from \"../hooks/useGinger\";\nimport { getCurrentTrack } from \"../internal/selectors\";\nimport type { Track } from \"../types\";\nimport type { CastErrorLike, WindowWithCast } from \"./castTypes\";\nimport { DEFAULT_MEDIA_RECEIVER_APP_ID } from \"./castTypes\";\nimport { loadCastFramework } from \"./loadCastFramework\";\nimport { trackToMediaInfo } from \"./trackToMediaInfo\";\n\nconst SEEK_JUMP_SECONDS = 2;\n\nfunction getCastGlobals(win: WindowWithCast): {\n context: import(\"./castTypes\").CastFrameworkContextLike;\n framework: import(\"./castTypes\").CastFrameworkNamespace;\n chromeCast: import(\"./castTypes\").ChromeCastNamespace;\n} | null {\n const framework = win.cast?.framework;\n const chromeCast = win.chrome?.cast;\n if (!framework?.CastContext || !chromeCast?.media) return null;\n return {\n context: framework.CastContext.getInstance(),\n framework,\n chromeCast,\n };\n}\n\nexport type UseGingerCastOptions = {\n /** When false, the hook does not touch Cast APIs. Default: true. */\n enabled?: boolean;\n /** Receiver app ID. Default: Default Media Receiver (`CC1AD845`). */\n receiverApplicationId?: string;\n /** Passed to `CastContext.setOptions`. Default: `chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED`. */\n autoJoinPolicy?: number;\n /** Passed to `CastContext.setOptions`. Default: true. */\n resumeSavedSession?: boolean;\n /**\n * When connected, silences the local `<audio>` (`muted` + `volume = 0`) so it does not compete\n * with the TV. Restored on disconnect. Does **not** change Ginger playback state.\n * Default: `\"pause-mute\"` (silence local element).\n */\n syncLocalAudio?: \"pause-mute\" | \"none\";\n /** When true, ends the Cast session when the hook unmounts. Default: false. */\n endSessionOnUnmount?: boolean;\n contentTypeResolver?: (track: Track) => string;\n onError?: (error: CastErrorLike | Error) => void;\n};\n\nexport type UseGingerCastResult = {\n /** Cast APIs loaded and `chrome.cast` is present (may still be `false` on unsupported browsers). */\n isAvailable: boolean;\n /** Cast sender is connected to a receiver. */\n isConnected: boolean;\n /** Same as `isConnected`; use to gate `Ginger.Player` (`{!isCasting && <Ginger.Player />}`). */\n isCasting: boolean;\n /** Last `SESSION_STATE_CHANGED` value from CAF, if any. */\n sessionState: string | null;\n error: string | null;\n /** Starts a Cast session (shows the Cast device picker when needed). */\n requestSession: () => Promise<void>;\n /** Ends the current Cast session, if any. */\n endSession: () => void;\n /** Position reported by `RemotePlayer` on the sender (useful if local audio is unmounted). */\n receiverCurrentTime: number;\n /** Duration reported by `RemotePlayer`. */\n receiverDuration: number;\n /** Pause state reported by `RemotePlayer`. */\n receiverIsPaused: boolean;\n};\n\nfunction formatCastError(err: CastErrorLike | Error | unknown): string {\n if (err instanceof Error) return err.message;\n if (err && typeof err === \"object\" && \"description\" in err) {\n const d = (err as CastErrorLike).description;\n if (typeof d === \"string\" && d.length > 0) return d;\n }\n return \"Unknown Cast error\";\n}\n\n/**\n * Chromecast Web Sender (CAF) bridge: loads the current Ginger track on a Cast receiver and keeps\n * transport roughly in sync while Ginger remains the queue source of truth.\n *\n * Prefer **`{!isCasting && <Ginger.Player />}`** so local `<audio>` does not decode the same URLs\n * as the receiver. When you must keep `Ginger.Player` mounted, use `syncLocalAudio: \"pause-mute\"`\n * to mute the local element.\n *\n * ```ts\n * import { useGingerCast } from \"@lucaismyname/ginger/cast\";\n * ```\n */\nexport function useGingerCast(options: UseGingerCastOptions = {}): UseGingerCastResult {\n const {\n enabled = true,\n receiverApplicationId = DEFAULT_MEDIA_RECEIVER_APP_ID,\n autoJoinPolicy: autoJoinPolicyOpt,\n resumeSavedSession = true,\n syncLocalAudio = \"pause-mute\",\n endSessionOnUnmount = false,\n contentTypeResolver,\n onError,\n } = options;\n\n const { state, audioRef } = useGinger();\n const currentTrack = getCurrentTrack(state);\n\n const [frameworkReady, setFrameworkReady] = useState(false);\n const [isAvailable, setIsAvailable] = useState(false);\n const [isConnected, setIsConnected] = useState(false);\n const [sessionState, setSessionState] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [receiverCurrentTime, setReceiverCurrentTime] = useState(0);\n const [receiverDuration, setReceiverDuration] = useState(0);\n const [receiverIsPaused, setReceiverIsPaused] = useState(true);\n\n const remotePlayerRef = useRef<import(\"./castTypes\").CastRemotePlayerLike | null>(null);\n const remoteControllerRef = useRef<import(\"./castTypes\").CastRemotePlayerControllerLike | null>(\n null,\n );\n const lastMediaKeyRef = useRef<string | null>(null);\n const prevSeekTimeRef = useRef(0);\n const prevSeekIndexRef = useRef<number>(-1);\n const localsRef = useRef<{ volume: number; muted: boolean } | null>(null);\n const skipPlayPauseUntilRef = useRef(0);\n const stateCurrentTimeRef = useRef(state.currentTime);\n stateCurrentTimeRef.current = state.currentTime;\n\n const onErrorRef = useRef(onError);\n onErrorRef.current = onError;\n\n const emitError = useCallback((err: CastErrorLike | Error | unknown) => {\n const msg = formatCastError(err);\n setError(msg);\n try {\n onErrorRef.current?.(err instanceof Error ? err : (err as CastErrorLike));\n } catch {\n /* ignore user handler errors */\n }\n }, []);\n\n // Load CAF once when enabled.\n useEffect(() => {\n if (!enabled) return;\n let cancelled = false;\n void loadCastFramework()\n .then(() => {\n if (cancelled) return;\n setFrameworkReady(true);\n const win = window as WindowWithCast;\n setIsAvailable(Boolean(win.chrome?.cast?.isAvailable));\n })\n .catch((e: unknown) => {\n if (cancelled) return;\n emitError(e instanceof Error ? e : new Error(String(e)));\n });\n return () => {\n cancelled = true;\n };\n }, [enabled, emitError]);\n\n // CastContext options + connection listeners.\n useEffect(() => {\n if (!enabled || !frameworkReady) return;\n const win = window as WindowWithCast;\n const globals = getCastGlobals(win);\n if (!globals) return;\n\n const { context, framework, chromeCast } = globals;\n const autoJoinPolicy = autoJoinPolicyOpt ?? chromeCast.AutoJoinPolicy?.ORIGIN_SCOPED ?? 1;\n\n context.setOptions({\n receiverApplicationId,\n autoJoinPolicy,\n resumeSavedSession,\n });\n\n const syncConnected = () => {\n const connected = context.getCastState() === framework.CastState.CONNECTED;\n setIsConnected(connected);\n };\n\n const onCastState = () => syncConnected();\n const onSessionState = (e: { sessionState?: string }) => {\n if (e.sessionState) setSessionState(e.sessionState);\n syncConnected();\n };\n\n context.addEventListener(framework.CastContextEventType.CAST_STATE_CHANGED, onCastState);\n context.addEventListener(framework.CastContextEventType.SESSION_STATE_CHANGED, onSessionState);\n syncConnected();\n\n return () => {\n context.removeEventListener(framework.CastContextEventType.CAST_STATE_CHANGED, onCastState);\n context.removeEventListener(\n framework.CastContextEventType.SESSION_STATE_CHANGED,\n onSessionState,\n );\n };\n }, [enabled, frameworkReady, receiverApplicationId, autoJoinPolicyOpt, resumeSavedSession]);\n\n // RemotePlayer (for seek / play/pause / progress mirrors).\n useEffect(() => {\n if (!enabled || !frameworkReady) return;\n const win = window as WindowWithCast;\n const globals = getCastGlobals(win);\n if (!globals) return;\n\n const { framework } = globals;\n const player = new framework.RemotePlayer();\n const controller = new framework.RemotePlayerController(player);\n remotePlayerRef.current = player;\n remoteControllerRef.current = controller;\n\n const bump = () => {\n setReceiverCurrentTime(player.currentTime);\n setReceiverDuration(player.duration);\n setReceiverIsPaused(player.isPaused);\n };\n\n const { RemotePlayerEventType } = framework;\n player.addEventListener(RemotePlayerEventType.IS_PAUSED_CHANGED, bump);\n player.addEventListener(RemotePlayerEventType.CURRENT_TIME_CHANGED, bump);\n player.addEventListener(RemotePlayerEventType.DURATION_CHANGED, bump);\n bump();\n\n return () => {\n player.removeEventListener(RemotePlayerEventType.IS_PAUSED_CHANGED, bump);\n player.removeEventListener(RemotePlayerEventType.CURRENT_TIME_CHANGED, bump);\n player.removeEventListener(RemotePlayerEventType.DURATION_CHANGED, bump);\n remotePlayerRef.current = null;\n remoteControllerRef.current = null;\n };\n }, [enabled, frameworkReady]);\n\n // Silence local <audio> while casting (optional).\n useEffect(() => {\n if (!enabled || syncLocalAudio === \"none\") return;\n const el = audioRef.current;\n if (!isConnected || !el) {\n if (localsRef.current && el) {\n el.muted = localsRef.current.muted;\n el.volume = localsRef.current.volume;\n localsRef.current = null;\n }\n return;\n }\n if (!localsRef.current) {\n localsRef.current = { volume: el.volume, muted: el.muted };\n }\n el.muted = true;\n el.volume = 0;\n return () => {\n if (localsRef.current && audioRef.current) {\n audioRef.current.muted = localsRef.current.muted;\n audioRef.current.volume = localsRef.current.volume;\n localsRef.current = null;\n }\n };\n }, [enabled, syncLocalAudio, isConnected, audioRef]);\n\n // Load media when the active track changes.\n useEffect(() => {\n if (!enabled || !frameworkReady || !isConnected) return;\n const win = window as WindowWithCast;\n const globals = getCastGlobals(win);\n if (!globals) return;\n const session = globals.context.getCurrentSession();\n if (!session || !currentTrack) return;\n\n const mediaKey = `${state.currentIndex}:${currentTrack.fileUrl}`;\n const chromeCast = globals.chromeCast;\n\n if (lastMediaKeyRef.current === mediaKey) return;\n\n lastMediaKeyRef.current = mediaKey;\n skipPlayPauseUntilRef.current = Date.now() + 500;\n const loadRequest = trackToMediaInfo(chromeCast, currentTrack, { contentTypeResolver });\n loadRequest.autoplay = !state.isPaused;\n loadRequest.currentTime = 0;\n\n session.loadMedia(\n loadRequest,\n () => {\n prevSeekIndexRef.current = state.currentIndex;\n prevSeekTimeRef.current = stateCurrentTimeRef.current;\n },\n (err) => emitError(err),\n );\n }, [\n enabled,\n frameworkReady,\n isConnected,\n state.currentIndex,\n state.isPaused,\n currentTrack,\n contentTypeResolver,\n emitError,\n ]);\n\n useEffect(() => {\n if (!isConnected) {\n lastMediaKeyRef.current = null;\n }\n }, [isConnected]);\n\n // Play/pause on the receiver when Ginger pause state changes (same track only).\n useEffect(() => {\n if (!enabled || !frameworkReady || !isConnected) return;\n if (!currentTrack) return;\n const mediaKey = `${state.currentIndex}:${currentTrack.fileUrl}`;\n if (lastMediaKeyRef.current !== mediaKey) return;\n if (Date.now() < skipPlayPauseUntilRef.current) return;\n\n const player = remotePlayerRef.current;\n const controller = remoteControllerRef.current;\n if (!player || !controller) return;\n if (player.isPaused !== state.isPaused) {\n controller.playOrPause();\n }\n }, [enabled, frameworkReady, isConnected, state.isPaused, state.currentIndex, currentTrack]);\n\n // Map large local time jumps to receiver seek (e.g. scrubbing).\n useEffect(() => {\n if (!enabled || !isConnected) return;\n if (prevSeekIndexRef.current !== state.currentIndex) {\n prevSeekIndexRef.current = state.currentIndex;\n prevSeekTimeRef.current = state.currentTime;\n return;\n }\n const jump = Math.abs(state.currentTime - prevSeekTimeRef.current);\n if (jump > SEEK_JUMP_SECONDS) {\n remoteControllerRef.current?.seek({ value: state.currentTime });\n }\n prevSeekTimeRef.current = state.currentTime;\n }, [enabled, isConnected, state.currentTime, state.currentIndex]);\n\n const requestSession = useCallback(async () => {\n if (!frameworkReady) {\n await loadCastFramework();\n }\n const win = window as WindowWithCast;\n const globals = getCastGlobals(win);\n if (!globals) {\n emitError(new Error(\"Cast framework is not available.\"));\n return;\n }\n try {\n await globals.context.requestSession();\n setError(null);\n } catch (e) {\n emitError(e);\n }\n }, [emitError, frameworkReady]);\n\n const endSession = useCallback(() => {\n const win = window as WindowWithCast;\n const globals = getCastGlobals(win);\n const session = globals?.context.getCurrentSession() as\n | import(\"./castTypes\").CastSessionLike\n | null;\n if (!session) return;\n session.endSession(\n () => {\n lastMediaKeyRef.current = null;\n setError(null);\n },\n (err) => emitError(err),\n );\n }, [emitError]);\n\n useEffect(() => {\n if (!endSessionOnUnmount) return;\n return () => {\n endSession();\n };\n }, [endSessionOnUnmount, endSession]);\n\n return {\n isAvailable,\n isConnected,\n isCasting: isConnected,\n sessionState,\n error,\n requestSession,\n endSession,\n receiverCurrentTime,\n receiverDuration,\n receiverIsPaused,\n };\n}\n"],"names":["DEFAULT_MEDIA_RECEIVER_APP_ID","CAST_SCRIPT_SRC","loadPromise","getWindow","loadCastFramework","win","_b","_a","resolve","reject","existing","onLoad","script","EXT_TO_MIME","guessContentTypeFromUrl","fileUrl","path","dot","ext","trackToMediaInfo","chromeCast","track","options","contentType","media","meta","SEEK_JUMP_SECONDS","getCastGlobals","framework","formatCastError","err","d","useGingerCast","enabled","receiverApplicationId","autoJoinPolicyOpt","resumeSavedSession","syncLocalAudio","endSessionOnUnmount","contentTypeResolver","onError","state","audioRef","useGinger","currentTrack","getCurrentTrack","frameworkReady","setFrameworkReady","useState","isAvailable","setIsAvailable","isConnected","setIsConnected","sessionState","setSessionState","error","setError","receiverCurrentTime","setReceiverCurrentTime","receiverDuration","setReceiverDuration","receiverIsPaused","setReceiverIsPaused","remotePlayerRef","useRef","remoteControllerRef","lastMediaKeyRef","prevSeekTimeRef","prevSeekIndexRef","localsRef","skipPlayPauseUntilRef","stateCurrentTimeRef","onErrorRef","emitError","useCallback","msg","useEffect","cancelled","e","globals","context","autoJoinPolicy","syncConnected","connected","onCastState","onSessionState","player","controller","bump","RemotePlayerEventType","el","session","mediaKey","loadRequest","requestSession","endSession"],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type { WindowWithCast } from './castTypes';
|
|
2
|
+
export { DEFAULT_MEDIA_RECEIVER_APP_ID } from './castTypes';
|
|
3
|
+
export { loadCastFramework } from './loadCastFramework';
|
|
4
|
+
export { guessContentTypeFromUrl, trackToMediaInfo } from './trackToMediaInfo';
|
|
5
|
+
export { useGingerCast } from './useGingerCast';
|
|
6
|
+
export type { UseGingerCastOptions, UseGingerCastResult } from './useGingerCast';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cast/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,6BAA6B,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { useState as g, useRef as T, useCallback as O, useEffect as p } from "react";
|
|
2
|
+
import { u as se } from "../useGinger-DKrHZ4NU.js";
|
|
3
|
+
import { g as ie } from "../selectors-BT3WSsKN.js";
|
|
4
|
+
const ae = "CC1AD845", K = "https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1";
|
|
5
|
+
let N = null;
|
|
6
|
+
function ue() {
|
|
7
|
+
if (!(typeof window > "u"))
|
|
8
|
+
return window;
|
|
9
|
+
}
|
|
10
|
+
function $() {
|
|
11
|
+
var e, a;
|
|
12
|
+
const s = ue();
|
|
13
|
+
return s ? (a = (e = s.cast) == null ? void 0 : e.framework) != null && a.CastContext ? Promise.resolve() : N || (N = new Promise((d, u) => {
|
|
14
|
+
const c = document.querySelector(`script[src="${K}"]`);
|
|
15
|
+
if (c) {
|
|
16
|
+
const S = () => {
|
|
17
|
+
var y, i;
|
|
18
|
+
(i = (y = s.cast) == null ? void 0 : y.framework) != null && i.CastContext ? d() : u(new Error("Cast script loaded but cast.framework is missing."));
|
|
19
|
+
};
|
|
20
|
+
if (c.dataset.gingerCastLoaded === "true") {
|
|
21
|
+
S();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
c.addEventListener("load", S, { once: !0 }), c.addEventListener("error", () => u(new Error("Cast script failed to load.")), {
|
|
25
|
+
once: !0
|
|
26
|
+
});
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const m = document.createElement("script");
|
|
30
|
+
m.src = K, m.async = !0, m.addEventListener("load", () => {
|
|
31
|
+
var S, y;
|
|
32
|
+
m.dataset.gingerCastLoaded = "true", (y = (S = s.cast) == null ? void 0 : S.framework) != null && y.CastContext ? d() : u(new Error("Cast script loaded but cast.framework is missing."));
|
|
33
|
+
}), m.addEventListener("error", () => {
|
|
34
|
+
u(new Error("Cast script failed to load."));
|
|
35
|
+
}), document.head.appendChild(m);
|
|
36
|
+
}), N) : Promise.reject(new Error("Cast is only available in a browser environment."));
|
|
37
|
+
}
|
|
38
|
+
const ce = {
|
|
39
|
+
".mp3": "audio/mpeg",
|
|
40
|
+
".aac": "audio/aac",
|
|
41
|
+
".m4a": "audio/mp4",
|
|
42
|
+
".ogg": "audio/ogg",
|
|
43
|
+
".opus": "audio/opus",
|
|
44
|
+
".wav": "audio/wav",
|
|
45
|
+
".flac": "audio/flac",
|
|
46
|
+
".webm": "audio/webm"
|
|
47
|
+
};
|
|
48
|
+
function le(s) {
|
|
49
|
+
try {
|
|
50
|
+
const e = new URL(s, "https://example.com").pathname.toLowerCase(), a = e.lastIndexOf(".");
|
|
51
|
+
if (a === -1) return "audio/mpeg";
|
|
52
|
+
const d = e.slice(a);
|
|
53
|
+
return ce[d] ?? "audio/mpeg";
|
|
54
|
+
} catch {
|
|
55
|
+
return "audio/mpeg";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function de(s, e, a) {
|
|
59
|
+
var m;
|
|
60
|
+
const d = ((m = a == null ? void 0 : a.contentTypeResolver) == null ? void 0 : m.call(a, e)) ?? le(e.fileUrl), u = new s.media.MediaInfo(e.fileUrl, d);
|
|
61
|
+
u.streamType = s.media.StreamType.BUFFERED;
|
|
62
|
+
const c = new s.media.MusicTrackMediaMetadata();
|
|
63
|
+
return c.metadataType = s.media.MetadataType.MUSIC_TRACK, c.title = e.title, e.artist && (c.artist = e.artist), e.album && (c.albumName = e.album), e.artworkUrl && (c.images = [{ url: e.artworkUrl }]), u.metadata = c, new s.media.LoadRequest(u);
|
|
64
|
+
}
|
|
65
|
+
const me = 2;
|
|
66
|
+
function P(s) {
|
|
67
|
+
var d, u;
|
|
68
|
+
const e = (d = s.cast) == null ? void 0 : d.framework, a = (u = s.chrome) == null ? void 0 : u.cast;
|
|
69
|
+
return !(e != null && e.CastContext) || !(a != null && a.media) ? null : {
|
|
70
|
+
context: e.CastContext.getInstance(),
|
|
71
|
+
framework: e,
|
|
72
|
+
chromeCast: a
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function fe(s) {
|
|
76
|
+
if (s instanceof Error) return s.message;
|
|
77
|
+
if (s && typeof s == "object" && "description" in s) {
|
|
78
|
+
const e = s.description;
|
|
79
|
+
if (typeof e == "string" && e.length > 0) return e;
|
|
80
|
+
}
|
|
81
|
+
return "Unknown Cast error";
|
|
82
|
+
}
|
|
83
|
+
function ve(s = {}) {
|
|
84
|
+
const {
|
|
85
|
+
enabled: e = !0,
|
|
86
|
+
receiverApplicationId: a = ae,
|
|
87
|
+
autoJoinPolicy: d,
|
|
88
|
+
resumeSavedSession: u = !0,
|
|
89
|
+
syncLocalAudio: c = "pause-mute",
|
|
90
|
+
endSessionOnUnmount: m = !1,
|
|
91
|
+
contentTypeResolver: S,
|
|
92
|
+
onError: y
|
|
93
|
+
} = s, { state: i, audioRef: I } = se(), R = ie(i), [w, j] = g(!1), [B, V] = g(!1), [f, W] = g(!1), [X, z] = g(null), [Q, U] = g(null), [Y, Z] = g(0), [ee, te] = g(0), [ne, re] = g(!0), M = T(null), x = T(
|
|
94
|
+
null
|
|
95
|
+
), A = T(null), D = T(0), k = T(-1), E = T(null), h = T(0), H = T(i.currentTime);
|
|
96
|
+
H.current = i.currentTime;
|
|
97
|
+
const b = T(y);
|
|
98
|
+
b.current = y;
|
|
99
|
+
const v = O((r) => {
|
|
100
|
+
var n;
|
|
101
|
+
const t = fe(r);
|
|
102
|
+
U(t);
|
|
103
|
+
try {
|
|
104
|
+
(n = b.current) == null || n.call(b, (r instanceof Error, r));
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
107
|
+
}, []);
|
|
108
|
+
p(() => {
|
|
109
|
+
if (!e) return;
|
|
110
|
+
let r = !1;
|
|
111
|
+
return $().then(() => {
|
|
112
|
+
var n, o;
|
|
113
|
+
if (r) return;
|
|
114
|
+
j(!0), V(!!((o = (n = window.chrome) == null ? void 0 : n.cast) != null && o.isAvailable));
|
|
115
|
+
}).catch((t) => {
|
|
116
|
+
r || v(t instanceof Error ? t : new Error(String(t)));
|
|
117
|
+
}), () => {
|
|
118
|
+
r = !0;
|
|
119
|
+
};
|
|
120
|
+
}, [e, v]), p(() => {
|
|
121
|
+
var J;
|
|
122
|
+
if (!e || !w) return;
|
|
123
|
+
const t = P(window);
|
|
124
|
+
if (!t) return;
|
|
125
|
+
const { context: n, framework: o, chromeCast: _ } = t, l = d ?? ((J = _.AutoJoinPolicy) == null ? void 0 : J.ORIGIN_SCOPED) ?? 1;
|
|
126
|
+
n.setOptions({
|
|
127
|
+
receiverApplicationId: a,
|
|
128
|
+
autoJoinPolicy: l,
|
|
129
|
+
resumeSavedSession: u
|
|
130
|
+
});
|
|
131
|
+
const C = () => {
|
|
132
|
+
const L = n.getCastState() === o.CastState.CONNECTED;
|
|
133
|
+
W(L);
|
|
134
|
+
}, F = () => C(), q = (L) => {
|
|
135
|
+
L.sessionState && z(L.sessionState), C();
|
|
136
|
+
};
|
|
137
|
+
return n.addEventListener(o.CastContextEventType.CAST_STATE_CHANGED, F), n.addEventListener(o.CastContextEventType.SESSION_STATE_CHANGED, q), C(), () => {
|
|
138
|
+
n.removeEventListener(o.CastContextEventType.CAST_STATE_CHANGED, F), n.removeEventListener(
|
|
139
|
+
o.CastContextEventType.SESSION_STATE_CHANGED,
|
|
140
|
+
q
|
|
141
|
+
);
|
|
142
|
+
};
|
|
143
|
+
}, [e, w, a, d, u]), p(() => {
|
|
144
|
+
if (!e || !w) return;
|
|
145
|
+
const t = P(window);
|
|
146
|
+
if (!t) return;
|
|
147
|
+
const { framework: n } = t, o = new n.RemotePlayer(), _ = new n.RemotePlayerController(o);
|
|
148
|
+
M.current = o, x.current = _;
|
|
149
|
+
const l = () => {
|
|
150
|
+
Z(o.currentTime), te(o.duration), re(o.isPaused);
|
|
151
|
+
}, { RemotePlayerEventType: C } = n;
|
|
152
|
+
return o.addEventListener(C.IS_PAUSED_CHANGED, l), o.addEventListener(C.CURRENT_TIME_CHANGED, l), o.addEventListener(C.DURATION_CHANGED, l), l(), () => {
|
|
153
|
+
o.removeEventListener(C.IS_PAUSED_CHANGED, l), o.removeEventListener(C.CURRENT_TIME_CHANGED, l), o.removeEventListener(C.DURATION_CHANGED, l), M.current = null, x.current = null;
|
|
154
|
+
};
|
|
155
|
+
}, [e, w]), p(() => {
|
|
156
|
+
if (!e || c === "none") return;
|
|
157
|
+
const r = I.current;
|
|
158
|
+
if (!f || !r) {
|
|
159
|
+
E.current && r && (r.muted = E.current.muted, r.volume = E.current.volume, E.current = null);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
return E.current || (E.current = { volume: r.volume, muted: r.muted }), r.muted = !0, r.volume = 0, () => {
|
|
163
|
+
E.current && I.current && (I.current.muted = E.current.muted, I.current.volume = E.current.volume, E.current = null);
|
|
164
|
+
};
|
|
165
|
+
}, [e, c, f, I]), p(() => {
|
|
166
|
+
if (!e || !w || !f) return;
|
|
167
|
+
const t = P(window);
|
|
168
|
+
if (!t) return;
|
|
169
|
+
const n = t.context.getCurrentSession();
|
|
170
|
+
if (!n || !R) return;
|
|
171
|
+
const o = `${i.currentIndex}:${R.fileUrl}`, _ = t.chromeCast;
|
|
172
|
+
if (A.current === o) return;
|
|
173
|
+
A.current = o, h.current = Date.now() + 500;
|
|
174
|
+
const l = de(_, R, { contentTypeResolver: S });
|
|
175
|
+
l.autoplay = !i.isPaused, l.currentTime = 0, n.loadMedia(
|
|
176
|
+
l,
|
|
177
|
+
() => {
|
|
178
|
+
k.current = i.currentIndex, D.current = H.current;
|
|
179
|
+
},
|
|
180
|
+
(C) => v(C)
|
|
181
|
+
);
|
|
182
|
+
}, [
|
|
183
|
+
e,
|
|
184
|
+
w,
|
|
185
|
+
f,
|
|
186
|
+
i.currentIndex,
|
|
187
|
+
i.isPaused,
|
|
188
|
+
R,
|
|
189
|
+
S,
|
|
190
|
+
v
|
|
191
|
+
]), p(() => {
|
|
192
|
+
f || (A.current = null);
|
|
193
|
+
}, [f]), p(() => {
|
|
194
|
+
if (!e || !w || !f || !R) return;
|
|
195
|
+
const r = `${i.currentIndex}:${R.fileUrl}`;
|
|
196
|
+
if (A.current !== r || Date.now() < h.current) return;
|
|
197
|
+
const t = M.current, n = x.current;
|
|
198
|
+
!t || !n || t.isPaused !== i.isPaused && n.playOrPause();
|
|
199
|
+
}, [e, w, f, i.isPaused, i.currentIndex, R]), p(() => {
|
|
200
|
+
var t;
|
|
201
|
+
if (!e || !f) return;
|
|
202
|
+
if (k.current !== i.currentIndex) {
|
|
203
|
+
k.current = i.currentIndex, D.current = i.currentTime;
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
Math.abs(i.currentTime - D.current) > me && ((t = x.current) == null || t.seek({ value: i.currentTime })), D.current = i.currentTime;
|
|
207
|
+
}, [e, f, i.currentTime, i.currentIndex]);
|
|
208
|
+
const oe = O(async () => {
|
|
209
|
+
w || await $();
|
|
210
|
+
const t = P(window);
|
|
211
|
+
if (!t) {
|
|
212
|
+
v(new Error("Cast framework is not available."));
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
await t.context.requestSession(), U(null);
|
|
217
|
+
} catch (n) {
|
|
218
|
+
v(n);
|
|
219
|
+
}
|
|
220
|
+
}, [v, w]), G = O(() => {
|
|
221
|
+
const t = P(window), n = t == null ? void 0 : t.context.getCurrentSession();
|
|
222
|
+
n && n.endSession(
|
|
223
|
+
() => {
|
|
224
|
+
A.current = null, U(null);
|
|
225
|
+
},
|
|
226
|
+
(o) => v(o)
|
|
227
|
+
);
|
|
228
|
+
}, [v]);
|
|
229
|
+
return p(() => {
|
|
230
|
+
if (m)
|
|
231
|
+
return () => {
|
|
232
|
+
G();
|
|
233
|
+
};
|
|
234
|
+
}, [m, G]), {
|
|
235
|
+
isAvailable: B,
|
|
236
|
+
isConnected: f,
|
|
237
|
+
isCasting: f,
|
|
238
|
+
sessionState: X,
|
|
239
|
+
error: Q,
|
|
240
|
+
requestSession: oe,
|
|
241
|
+
endSession: G,
|
|
242
|
+
receiverCurrentTime: Y,
|
|
243
|
+
receiverDuration: ee,
|
|
244
|
+
receiverIsPaused: ne
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
export {
|
|
248
|
+
ae as DEFAULT_MEDIA_RECEIVER_APP_ID,
|
|
249
|
+
le as guessContentTypeFromUrl,
|
|
250
|
+
$ as loadCastFramework,
|
|
251
|
+
de as trackToMediaInfo,
|
|
252
|
+
ve as useGingerCast
|
|
253
|
+
};
|
|
254
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../src/cast/castTypes.ts","../../src/cast/loadCastFramework.ts","../../src/cast/trackToMediaInfo.ts","../../src/cast/useGingerCast.ts"],"sourcesContent":["/**\n * Minimal typings for the subset of the Google Cast Web Sender API used by Ginger.\n * Runtime objects come from the loaded Cast Framework script (`loadCastFramework`).\n */\n\n/** Default Media Receiver application ID (audio/video). */\nexport const DEFAULT_MEDIA_RECEIVER_APP_ID = \"CC1AD845\";\n\nexport type CastImage = {\n url: string;\n width?: number;\n height?: number;\n};\n\n/** Instance shape from `new chrome.cast.media.MusicTrackMediaMetadata()`. */\nexport type CastMusicTrackMetadata = {\n metadataType: number;\n title?: string;\n artist?: string;\n albumName?: string;\n images?: CastImage[];\n};\n\nexport type CastMediaInfoLike = {\n contentId: string;\n contentType: string;\n streamType?: string;\n metadata?: CastMusicTrackMetadata;\n};\n\nexport type CastLoadRequestLike = {\n media: CastMediaInfoLike;\n autoplay?: boolean;\n currentTime?: number;\n};\n\nexport type CastMediaInfoConstructor = new (\n contentId: string,\n contentType: string,\n) => CastMediaInfoLike;\nexport type CastMusicTrackMetadataConstructor = new () => CastMusicTrackMetadata;\nexport type CastLoadRequestConstructor = new (mediaInfo: CastMediaInfoLike) => CastLoadRequestLike;\n\n/** Subset of `chrome.cast.Session` methods we call (duck-typed). */\nexport type CastSessionLike = {\n loadMedia: (\n request: CastLoadRequestLike,\n successCallback: (media?: unknown) => void,\n errorCallback: (error: CastErrorLike) => void,\n ) => void;\n endSession: (successCallback: () => void, errorCallback: (error: CastErrorLike) => void) => void;\n};\n\nexport type CastErrorLike = {\n code?: string;\n description?: string;\n};\n\nexport type CastContextOptionsLike = {\n receiverApplicationId: string;\n autoJoinPolicy?: number;\n resumeSavedSession?: boolean;\n};\n\n/** `cast.framework.CastContext` duck type. */\nexport type CastFrameworkContextLike = {\n setOptions: (options: CastContextOptionsLike) => void;\n getCastState: () => number;\n getCurrentSession: () => CastSessionLike | null;\n addEventListener: (type: string, listener: (event: { sessionState?: string }) => void) => void;\n removeEventListener: (type: string, listener: (event: { sessionState?: string }) => void) => void;\n requestSession: () => Promise<void>;\n};\n\nexport type CastFrameworkNamespace = {\n CastContext: {\n getInstance: () => CastFrameworkContextLike;\n };\n CastContextEventType: {\n CAST_STATE_CHANGED: string;\n SESSION_STATE_CHANGED: string;\n };\n CastState: {\n NO_DEVICES: number;\n NOT_CONNECTED: number;\n CONNECTING: number;\n CONNECTED: number;\n };\n SessionState: {\n SESSION_STARTED: string;\n SESSION_ENDED: string;\n SESSION_RESUMED: string;\n };\n RemotePlayer: new () => CastRemotePlayerLike;\n RemotePlayerController: new (player: CastRemotePlayerLike) => CastRemotePlayerControllerLike;\n RemotePlayerEventType: {\n IS_PAUSED_CHANGED: string;\n CURRENT_TIME_CHANGED: string;\n DURATION_CHANGED: string;\n };\n};\n\nexport type CastRemotePlayerLike = {\n isPaused: boolean;\n currentTime: number;\n duration: number;\n controller?: CastRemotePlayerControllerLike;\n addEventListener: (type: string, handler: () => void) => void;\n removeEventListener: (type: string, handler: () => void) => void;\n};\n\nexport type CastRemotePlayerControllerLike = {\n playOrPause: () => void;\n seek: (options: { value: number }) => void;\n stop: () => void;\n};\n\nexport type ChromeCastNamespace = {\n media: {\n DEFAULT_MEDIA_RECEIVER_APP_ID: string;\n MediaInfo: CastMediaInfoConstructor;\n MusicTrackMediaMetadata: CastMusicTrackMetadataConstructor;\n LoadRequest: CastLoadRequestConstructor;\n MetadataType: {\n MUSIC_TRACK: number;\n };\n StreamType: {\n BUFFERED: string;\n };\n };\n isAvailable: boolean;\n AutoJoinPolicy: {\n ORIGIN_SCOPED: number;\n TAB_AND_ORIGIN_SCOPED: number;\n };\n};\n\nexport type WindowWithCast = Window & {\n cast?: { framework?: CastFrameworkNamespace };\n chrome?: { cast?: ChromeCastNamespace };\n};\n","import type { WindowWithCast } from \"./castTypes\";\n\nconst CAST_SCRIPT_SRC =\n \"https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1\";\n\nlet loadPromise: Promise<void> | null = null;\n\nfunction getWindow(): WindowWithCast | undefined {\n if (typeof window === \"undefined\") return undefined;\n return window as WindowWithCast;\n}\n\n/**\n * Loads the Google Cast Web Sender script (Cast Application Framework) once.\n * Resolves when `window.cast.framework` is available. Safe to call multiple times.\n *\n * In SSR environments (no `window`), rejects with an error.\n */\nexport function loadCastFramework(): Promise<void> {\n const win = getWindow();\n if (!win) {\n return Promise.reject(new Error(\"Cast is only available in a browser environment.\"));\n }\n\n if (win.cast?.framework?.CastContext) {\n return Promise.resolve();\n }\n\n if (loadPromise) {\n return loadPromise;\n }\n\n loadPromise = new Promise((resolve, reject) => {\n const existing = document.querySelector(`script[src=\"${CAST_SCRIPT_SRC}\"]`);\n if (existing) {\n const onLoad = () => {\n if (win.cast?.framework?.CastContext) {\n resolve();\n } else {\n reject(new Error(\"Cast script loaded but cast.framework is missing.\"));\n }\n };\n if ((existing as HTMLScriptElement).dataset.gingerCastLoaded === \"true\") {\n onLoad();\n return;\n }\n existing.addEventListener(\"load\", onLoad, { once: true });\n existing.addEventListener(\"error\", () => reject(new Error(\"Cast script failed to load.\")), {\n once: true,\n });\n return;\n }\n\n const script = document.createElement(\"script\");\n script.src = CAST_SCRIPT_SRC;\n script.async = true;\n script.addEventListener(\"load\", () => {\n script.dataset.gingerCastLoaded = \"true\";\n if (win.cast?.framework?.CastContext) {\n resolve();\n } else {\n reject(new Error(\"Cast script loaded but cast.framework is missing.\"));\n }\n });\n script.addEventListener(\"error\", () => {\n reject(new Error(\"Cast script failed to load.\"));\n });\n document.head.appendChild(script);\n });\n\n return loadPromise;\n}\n","import type { Track } from \"../types\";\nimport type { CastLoadRequestLike, ChromeCastNamespace } from \"./castTypes\";\n\nconst EXT_TO_MIME: Record<string, string> = {\n \".mp3\": \"audio/mpeg\",\n \".aac\": \"audio/aac\",\n \".m4a\": \"audio/mp4\",\n \".ogg\": \"audio/ogg\",\n \".opus\": \"audio/opus\",\n \".wav\": \"audio/wav\",\n \".flac\": \"audio/flac\",\n \".webm\": \"audio/webm\",\n};\n\n/**\n * Guess a MIME type from a file URL path. Falls back to `audio/mpeg`.\n */\nexport function guessContentTypeFromUrl(fileUrl: string): string {\n try {\n const path = new URL(fileUrl, \"https://example.com\").pathname.toLowerCase();\n const dot = path.lastIndexOf(\".\");\n if (dot === -1) return \"audio/mpeg\";\n const ext = path.slice(dot);\n return EXT_TO_MIME[ext] ?? \"audio/mpeg\";\n } catch {\n return \"audio/mpeg\";\n }\n}\n\n/**\n * Builds a Cast `LoadRequest` for the given track using the runtime `chrome.cast.media` constructors.\n * Call only after `loadCastFramework()` and when `chrome.cast` is defined.\n */\nexport function trackToMediaInfo(\n chromeCast: ChromeCastNamespace,\n track: Track,\n options?: {\n contentTypeResolver?: (t: Track) => string;\n },\n): CastLoadRequestLike {\n const contentType =\n options?.contentTypeResolver?.(track) ?? guessContentTypeFromUrl(track.fileUrl);\n\n const media = new chromeCast.media.MediaInfo(track.fileUrl, contentType);\n media.streamType = chromeCast.media.StreamType.BUFFERED;\n\n const meta = new chromeCast.media.MusicTrackMediaMetadata();\n meta.metadataType = chromeCast.media.MetadataType.MUSIC_TRACK;\n meta.title = track.title;\n if (track.artist) {\n meta.artist = track.artist;\n }\n if (track.album) {\n meta.albumName = track.album;\n }\n if (track.artworkUrl) {\n meta.images = [{ url: track.artworkUrl }];\n }\n\n media.metadata = meta;\n\n return new chromeCast.media.LoadRequest(media);\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useGinger } from \"../hooks/useGinger\";\nimport { getCurrentTrack } from \"../internal/selectors\";\nimport type { Track } from \"../types\";\nimport type { CastErrorLike, WindowWithCast } from \"./castTypes\";\nimport { DEFAULT_MEDIA_RECEIVER_APP_ID } from \"./castTypes\";\nimport { loadCastFramework } from \"./loadCastFramework\";\nimport { trackToMediaInfo } from \"./trackToMediaInfo\";\n\nconst SEEK_JUMP_SECONDS = 2;\n\nfunction getCastGlobals(win: WindowWithCast): {\n context: import(\"./castTypes\").CastFrameworkContextLike;\n framework: import(\"./castTypes\").CastFrameworkNamespace;\n chromeCast: import(\"./castTypes\").ChromeCastNamespace;\n} | null {\n const framework = win.cast?.framework;\n const chromeCast = win.chrome?.cast;\n if (!framework?.CastContext || !chromeCast?.media) return null;\n return {\n context: framework.CastContext.getInstance(),\n framework,\n chromeCast,\n };\n}\n\nexport type UseGingerCastOptions = {\n /** When false, the hook does not touch Cast APIs. Default: true. */\n enabled?: boolean;\n /** Receiver app ID. Default: Default Media Receiver (`CC1AD845`). */\n receiverApplicationId?: string;\n /** Passed to `CastContext.setOptions`. Default: `chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED`. */\n autoJoinPolicy?: number;\n /** Passed to `CastContext.setOptions`. Default: true. */\n resumeSavedSession?: boolean;\n /**\n * When connected, silences the local `<audio>` (`muted` + `volume = 0`) so it does not compete\n * with the TV. Restored on disconnect. Does **not** change Ginger playback state.\n * Default: `\"pause-mute\"` (silence local element).\n */\n syncLocalAudio?: \"pause-mute\" | \"none\";\n /** When true, ends the Cast session when the hook unmounts. Default: false. */\n endSessionOnUnmount?: boolean;\n contentTypeResolver?: (track: Track) => string;\n onError?: (error: CastErrorLike | Error) => void;\n};\n\nexport type UseGingerCastResult = {\n /** Cast APIs loaded and `chrome.cast` is present (may still be `false` on unsupported browsers). */\n isAvailable: boolean;\n /** Cast sender is connected to a receiver. */\n isConnected: boolean;\n /** Same as `isConnected`; use to gate `Ginger.Player` (`{!isCasting && <Ginger.Player />}`). */\n isCasting: boolean;\n /** Last `SESSION_STATE_CHANGED` value from CAF, if any. */\n sessionState: string | null;\n error: string | null;\n /** Starts a Cast session (shows the Cast device picker when needed). */\n requestSession: () => Promise<void>;\n /** Ends the current Cast session, if any. */\n endSession: () => void;\n /** Position reported by `RemotePlayer` on the sender (useful if local audio is unmounted). */\n receiverCurrentTime: number;\n /** Duration reported by `RemotePlayer`. */\n receiverDuration: number;\n /** Pause state reported by `RemotePlayer`. */\n receiverIsPaused: boolean;\n};\n\nfunction formatCastError(err: CastErrorLike | Error | unknown): string {\n if (err instanceof Error) return err.message;\n if (err && typeof err === \"object\" && \"description\" in err) {\n const d = (err as CastErrorLike).description;\n if (typeof d === \"string\" && d.length > 0) return d;\n }\n return \"Unknown Cast error\";\n}\n\n/**\n * Chromecast Web Sender (CAF) bridge: loads the current Ginger track on a Cast receiver and keeps\n * transport roughly in sync while Ginger remains the queue source of truth.\n *\n * Prefer **`{!isCasting && <Ginger.Player />}`** so local `<audio>` does not decode the same URLs\n * as the receiver. When you must keep `Ginger.Player` mounted, use `syncLocalAudio: \"pause-mute\"`\n * to mute the local element.\n *\n * ```ts\n * import { useGingerCast } from \"@lucaismyname/ginger/cast\";\n * ```\n */\nexport function useGingerCast(options: UseGingerCastOptions = {}): UseGingerCastResult {\n const {\n enabled = true,\n receiverApplicationId = DEFAULT_MEDIA_RECEIVER_APP_ID,\n autoJoinPolicy: autoJoinPolicyOpt,\n resumeSavedSession = true,\n syncLocalAudio = \"pause-mute\",\n endSessionOnUnmount = false,\n contentTypeResolver,\n onError,\n } = options;\n\n const { state, audioRef } = useGinger();\n const currentTrack = getCurrentTrack(state);\n\n const [frameworkReady, setFrameworkReady] = useState(false);\n const [isAvailable, setIsAvailable] = useState(false);\n const [isConnected, setIsConnected] = useState(false);\n const [sessionState, setSessionState] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [receiverCurrentTime, setReceiverCurrentTime] = useState(0);\n const [receiverDuration, setReceiverDuration] = useState(0);\n const [receiverIsPaused, setReceiverIsPaused] = useState(true);\n\n const remotePlayerRef = useRef<import(\"./castTypes\").CastRemotePlayerLike | null>(null);\n const remoteControllerRef = useRef<import(\"./castTypes\").CastRemotePlayerControllerLike | null>(\n null,\n );\n const lastMediaKeyRef = useRef<string | null>(null);\n const prevSeekTimeRef = useRef(0);\n const prevSeekIndexRef = useRef<number>(-1);\n const localsRef = useRef<{ volume: number; muted: boolean } | null>(null);\n const skipPlayPauseUntilRef = useRef(0);\n const stateCurrentTimeRef = useRef(state.currentTime);\n stateCurrentTimeRef.current = state.currentTime;\n\n const onErrorRef = useRef(onError);\n onErrorRef.current = onError;\n\n const emitError = useCallback((err: CastErrorLike | Error | unknown) => {\n const msg = formatCastError(err);\n setError(msg);\n try {\n onErrorRef.current?.(err instanceof Error ? err : (err as CastErrorLike));\n } catch {\n /* ignore user handler errors */\n }\n }, []);\n\n // Load CAF once when enabled.\n useEffect(() => {\n if (!enabled) return;\n let cancelled = false;\n void loadCastFramework()\n .then(() => {\n if (cancelled) return;\n setFrameworkReady(true);\n const win = window as WindowWithCast;\n setIsAvailable(Boolean(win.chrome?.cast?.isAvailable));\n })\n .catch((e: unknown) => {\n if (cancelled) return;\n emitError(e instanceof Error ? e : new Error(String(e)));\n });\n return () => {\n cancelled = true;\n };\n }, [enabled, emitError]);\n\n // CastContext options + connection listeners.\n useEffect(() => {\n if (!enabled || !frameworkReady) return;\n const win = window as WindowWithCast;\n const globals = getCastGlobals(win);\n if (!globals) return;\n\n const { context, framework, chromeCast } = globals;\n const autoJoinPolicy = autoJoinPolicyOpt ?? chromeCast.AutoJoinPolicy?.ORIGIN_SCOPED ?? 1;\n\n context.setOptions({\n receiverApplicationId,\n autoJoinPolicy,\n resumeSavedSession,\n });\n\n const syncConnected = () => {\n const connected = context.getCastState() === framework.CastState.CONNECTED;\n setIsConnected(connected);\n };\n\n const onCastState = () => syncConnected();\n const onSessionState = (e: { sessionState?: string }) => {\n if (e.sessionState) setSessionState(e.sessionState);\n syncConnected();\n };\n\n context.addEventListener(framework.CastContextEventType.CAST_STATE_CHANGED, onCastState);\n context.addEventListener(framework.CastContextEventType.SESSION_STATE_CHANGED, onSessionState);\n syncConnected();\n\n return () => {\n context.removeEventListener(framework.CastContextEventType.CAST_STATE_CHANGED, onCastState);\n context.removeEventListener(\n framework.CastContextEventType.SESSION_STATE_CHANGED,\n onSessionState,\n );\n };\n }, [enabled, frameworkReady, receiverApplicationId, autoJoinPolicyOpt, resumeSavedSession]);\n\n // RemotePlayer (for seek / play/pause / progress mirrors).\n useEffect(() => {\n if (!enabled || !frameworkReady) return;\n const win = window as WindowWithCast;\n const globals = getCastGlobals(win);\n if (!globals) return;\n\n const { framework } = globals;\n const player = new framework.RemotePlayer();\n const controller = new framework.RemotePlayerController(player);\n remotePlayerRef.current = player;\n remoteControllerRef.current = controller;\n\n const bump = () => {\n setReceiverCurrentTime(player.currentTime);\n setReceiverDuration(player.duration);\n setReceiverIsPaused(player.isPaused);\n };\n\n const { RemotePlayerEventType } = framework;\n player.addEventListener(RemotePlayerEventType.IS_PAUSED_CHANGED, bump);\n player.addEventListener(RemotePlayerEventType.CURRENT_TIME_CHANGED, bump);\n player.addEventListener(RemotePlayerEventType.DURATION_CHANGED, bump);\n bump();\n\n return () => {\n player.removeEventListener(RemotePlayerEventType.IS_PAUSED_CHANGED, bump);\n player.removeEventListener(RemotePlayerEventType.CURRENT_TIME_CHANGED, bump);\n player.removeEventListener(RemotePlayerEventType.DURATION_CHANGED, bump);\n remotePlayerRef.current = null;\n remoteControllerRef.current = null;\n };\n }, [enabled, frameworkReady]);\n\n // Silence local <audio> while casting (optional).\n useEffect(() => {\n if (!enabled || syncLocalAudio === \"none\") return;\n const el = audioRef.current;\n if (!isConnected || !el) {\n if (localsRef.current && el) {\n el.muted = localsRef.current.muted;\n el.volume = localsRef.current.volume;\n localsRef.current = null;\n }\n return;\n }\n if (!localsRef.current) {\n localsRef.current = { volume: el.volume, muted: el.muted };\n }\n el.muted = true;\n el.volume = 0;\n return () => {\n if (localsRef.current && audioRef.current) {\n audioRef.current.muted = localsRef.current.muted;\n audioRef.current.volume = localsRef.current.volume;\n localsRef.current = null;\n }\n };\n }, [enabled, syncLocalAudio, isConnected, audioRef]);\n\n // Load media when the active track changes.\n useEffect(() => {\n if (!enabled || !frameworkReady || !isConnected) return;\n const win = window as WindowWithCast;\n const globals = getCastGlobals(win);\n if (!globals) return;\n const session = globals.context.getCurrentSession();\n if (!session || !currentTrack) return;\n\n const mediaKey = `${state.currentIndex}:${currentTrack.fileUrl}`;\n const chromeCast = globals.chromeCast;\n\n if (lastMediaKeyRef.current === mediaKey) return;\n\n lastMediaKeyRef.current = mediaKey;\n skipPlayPauseUntilRef.current = Date.now() + 500;\n const loadRequest = trackToMediaInfo(chromeCast, currentTrack, { contentTypeResolver });\n loadRequest.autoplay = !state.isPaused;\n loadRequest.currentTime = 0;\n\n session.loadMedia(\n loadRequest,\n () => {\n prevSeekIndexRef.current = state.currentIndex;\n prevSeekTimeRef.current = stateCurrentTimeRef.current;\n },\n (err) => emitError(err),\n );\n }, [\n enabled,\n frameworkReady,\n isConnected,\n state.currentIndex,\n state.isPaused,\n currentTrack,\n contentTypeResolver,\n emitError,\n ]);\n\n useEffect(() => {\n if (!isConnected) {\n lastMediaKeyRef.current = null;\n }\n }, [isConnected]);\n\n // Play/pause on the receiver when Ginger pause state changes (same track only).\n useEffect(() => {\n if (!enabled || !frameworkReady || !isConnected) return;\n if (!currentTrack) return;\n const mediaKey = `${state.currentIndex}:${currentTrack.fileUrl}`;\n if (lastMediaKeyRef.current !== mediaKey) return;\n if (Date.now() < skipPlayPauseUntilRef.current) return;\n\n const player = remotePlayerRef.current;\n const controller = remoteControllerRef.current;\n if (!player || !controller) return;\n if (player.isPaused !== state.isPaused) {\n controller.playOrPause();\n }\n }, [enabled, frameworkReady, isConnected, state.isPaused, state.currentIndex, currentTrack]);\n\n // Map large local time jumps to receiver seek (e.g. scrubbing).\n useEffect(() => {\n if (!enabled || !isConnected) return;\n if (prevSeekIndexRef.current !== state.currentIndex) {\n prevSeekIndexRef.current = state.currentIndex;\n prevSeekTimeRef.current = state.currentTime;\n return;\n }\n const jump = Math.abs(state.currentTime - prevSeekTimeRef.current);\n if (jump > SEEK_JUMP_SECONDS) {\n remoteControllerRef.current?.seek({ value: state.currentTime });\n }\n prevSeekTimeRef.current = state.currentTime;\n }, [enabled, isConnected, state.currentTime, state.currentIndex]);\n\n const requestSession = useCallback(async () => {\n if (!frameworkReady) {\n await loadCastFramework();\n }\n const win = window as WindowWithCast;\n const globals = getCastGlobals(win);\n if (!globals) {\n emitError(new Error(\"Cast framework is not available.\"));\n return;\n }\n try {\n await globals.context.requestSession();\n setError(null);\n } catch (e) {\n emitError(e);\n }\n }, [emitError, frameworkReady]);\n\n const endSession = useCallback(() => {\n const win = window as WindowWithCast;\n const globals = getCastGlobals(win);\n const session = globals?.context.getCurrentSession() as\n | import(\"./castTypes\").CastSessionLike\n | null;\n if (!session) return;\n session.endSession(\n () => {\n lastMediaKeyRef.current = null;\n setError(null);\n },\n (err) => emitError(err),\n );\n }, [emitError]);\n\n useEffect(() => {\n if (!endSessionOnUnmount) return;\n return () => {\n endSession();\n };\n }, [endSessionOnUnmount, endSession]);\n\n return {\n isAvailable,\n isConnected,\n isCasting: isConnected,\n sessionState,\n error,\n requestSession,\n endSession,\n receiverCurrentTime,\n receiverDuration,\n receiverIsPaused,\n };\n}\n"],"names":["DEFAULT_MEDIA_RECEIVER_APP_ID","CAST_SCRIPT_SRC","loadPromise","getWindow","loadCastFramework","win","_b","_a","resolve","reject","existing","onLoad","script","EXT_TO_MIME","guessContentTypeFromUrl","fileUrl","path","dot","ext","trackToMediaInfo","chromeCast","track","options","contentType","media","meta","SEEK_JUMP_SECONDS","getCastGlobals","framework","formatCastError","err","d","useGingerCast","enabled","receiverApplicationId","autoJoinPolicyOpt","resumeSavedSession","syncLocalAudio","endSessionOnUnmount","contentTypeResolver","onError","state","audioRef","useGinger","currentTrack","getCurrentTrack","frameworkReady","setFrameworkReady","useState","isAvailable","setIsAvailable","isConnected","setIsConnected","sessionState","setSessionState","error","setError","receiverCurrentTime","setReceiverCurrentTime","receiverDuration","setReceiverDuration","receiverIsPaused","setReceiverIsPaused","remotePlayerRef","useRef","remoteControllerRef","lastMediaKeyRef","prevSeekTimeRef","prevSeekIndexRef","localsRef","skipPlayPauseUntilRef","stateCurrentTimeRef","onErrorRef","emitError","useCallback","msg","useEffect","cancelled","e","globals","context","autoJoinPolicy","syncConnected","connected","onCastState","onSessionState","player","controller","bump","RemotePlayerEventType","el","session","mediaKey","loadRequest","requestSession","endSession"],"mappings":";;;AAMO,MAAMA,KAAgC,YCJvCC,IACJ;AAEF,IAAIC,IAAoC;AAExC,SAASC,KAAwC;AAC/C,MAAI,SAAO,SAAW;AACtB,WAAO;AACT;AAQO,SAASC,IAAmC;;AACjD,QAAMC,IAAMF,GAAA;AACZ,SAAKE,KAIDC,KAAAC,IAAAF,EAAI,SAAJ,gBAAAE,EAAU,cAAV,QAAAD,EAAqB,cAChB,QAAQ,QAAA,IAGbJ,MAIJA,IAAc,IAAI,QAAQ,CAACM,GAASC,MAAW;AAC7C,UAAMC,IAAW,SAAS,cAAc,eAAeT,CAAe,IAAI;AAC1E,QAAIS,GAAU;AACZ,YAAMC,IAAS,MAAM;;AACnB,SAAIL,KAAAC,IAAAF,EAAI,SAAJ,gBAAAE,EAAU,cAAV,QAAAD,EAAqB,cACvBE,EAAA,IAEAC,EAAO,IAAI,MAAM,mDAAmD,CAAC;AAAA,MAEzE;AACA,UAAKC,EAA+B,QAAQ,qBAAqB,QAAQ;AACvE,QAAAC,EAAA;AACA;AAAA,MACF;AACA,MAAAD,EAAS,iBAAiB,QAAQC,GAAQ,EAAE,MAAM,IAAM,GACxDD,EAAS,iBAAiB,SAAS,MAAMD,EAAO,IAAI,MAAM,6BAA6B,CAAC,GAAG;AAAA,QACzF,MAAM;AAAA,MAAA,CACP;AACD;AAAA,IACF;AAEA,UAAMG,IAAS,SAAS,cAAc,QAAQ;AAC9C,IAAAA,EAAO,MAAMX,GACbW,EAAO,QAAQ,IACfA,EAAO,iBAAiB,QAAQ,MAAM;;AACpC,MAAAA,EAAO,QAAQ,mBAAmB,SAC9BN,KAAAC,IAAAF,EAAI,SAAJ,gBAAAE,EAAU,cAAV,QAAAD,EAAqB,cACvBE,EAAA,IAEAC,EAAO,IAAI,MAAM,mDAAmD,CAAC;AAAA,IAEzE,CAAC,GACDG,EAAO,iBAAiB,SAAS,MAAM;AACrC,MAAAH,EAAO,IAAI,MAAM,6BAA6B,CAAC;AAAA,IACjD,CAAC,GACD,SAAS,KAAK,YAAYG,CAAM;AAAA,EAClC,CAAC,GAEMV,KAjDE,QAAQ,OAAO,IAAI,MAAM,kDAAkD,CAAC;AAkDvF;ACpEA,MAAMW,KAAsC;AAAA,EAC1C,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AACX;AAKO,SAASC,GAAwBC,GAAyB;AAC/D,MAAI;AACF,UAAMC,IAAO,IAAI,IAAID,GAAS,qBAAqB,EAAE,SAAS,YAAA,GACxDE,IAAMD,EAAK,YAAY,GAAG;AAChC,QAAIC,MAAQ,GAAI,QAAO;AACvB,UAAMC,IAAMF,EAAK,MAAMC,CAAG;AAC1B,WAAOJ,GAAYK,CAAG,KAAK;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMO,SAASC,GACdC,GACAC,GACAC,GAGqB;;AACrB,QAAMC,MACJhB,IAAAe,KAAA,gBAAAA,EAAS,wBAAT,gBAAAf,EAAA,KAAAe,GAA+BD,OAAUP,GAAwBO,EAAM,OAAO,GAE1EG,IAAQ,IAAIJ,EAAW,MAAM,UAAUC,EAAM,SAASE,CAAW;AACvE,EAAAC,EAAM,aAAaJ,EAAW,MAAM,WAAW;AAE/C,QAAMK,IAAO,IAAIL,EAAW,MAAM,wBAAA;AAClC,SAAAK,EAAK,eAAeL,EAAW,MAAM,aAAa,aAClDK,EAAK,QAAQJ,EAAM,OACfA,EAAM,WACRI,EAAK,SAASJ,EAAM,SAElBA,EAAM,UACRI,EAAK,YAAYJ,EAAM,QAErBA,EAAM,eACRI,EAAK,SAAS,CAAC,EAAE,KAAKJ,EAAM,YAAY,IAG1CG,EAAM,WAAWC,GAEV,IAAIL,EAAW,MAAM,YAAYI,CAAK;AAC/C;ACrDA,MAAME,KAAoB;AAE1B,SAASC,EAAetB,GAIf;;AACP,QAAMuB,KAAYrB,IAAAF,EAAI,SAAJ,gBAAAE,EAAU,WACtBa,KAAad,IAAAD,EAAI,WAAJ,gBAAAC,EAAY;AAC/B,SAAI,EAACsB,KAAA,QAAAA,EAAW,gBAAe,EAACR,KAAA,QAAAA,EAAY,SAAc,OACnD;AAAA,IACL,SAASQ,EAAU,YAAY,YAAA;AAAA,IAC/B,WAAAA;AAAA,IACA,YAAAR;AAAA,EAAA;AAEJ;AA6CA,SAASS,GAAgBC,GAA8C;AACrE,MAAIA,aAAe,MAAO,QAAOA,EAAI;AACrC,MAAIA,KAAO,OAAOA,KAAQ,YAAY,iBAAiBA,GAAK;AAC1D,UAAMC,IAAKD,EAAsB;AACjC,QAAI,OAAOC,KAAM,YAAYA,EAAE,SAAS,EAAG,QAAOA;AAAA,EACpD;AACA,SAAO;AACT;AAcO,SAASC,GAAcV,IAAgC,IAAyB;AACrF,QAAM;AAAA,IACJ,SAAAW,IAAU;AAAA,IACV,uBAAAC,IAAwBlC;AAAA,IACxB,gBAAgBmC;AAAA,IAChB,oBAAAC,IAAqB;AAAA,IACrB,gBAAAC,IAAiB;AAAA,IACjB,qBAAAC,IAAsB;AAAA,IACtB,qBAAAC;AAAA,IACA,SAAAC;AAAA,EAAA,IACElB,GAEE,EAAE,OAAAmB,GAAO,UAAAC,EAAA,IAAaC,GAAA,GACtBC,IAAeC,GAAgBJ,CAAK,GAEpC,CAACK,GAAgBC,CAAiB,IAAIC,EAAS,EAAK,GACpD,CAACC,GAAaC,CAAc,IAAIF,EAAS,EAAK,GAC9C,CAACG,GAAaC,CAAc,IAAIJ,EAAS,EAAK,GAC9C,CAACK,GAAcC,CAAe,IAAIN,EAAwB,IAAI,GAC9D,CAACO,GAAOC,CAAQ,IAAIR,EAAwB,IAAI,GAChD,CAACS,GAAqBC,CAAsB,IAAIV,EAAS,CAAC,GAC1D,CAACW,IAAkBC,EAAmB,IAAIZ,EAAS,CAAC,GACpD,CAACa,IAAkBC,EAAmB,IAAId,EAAS,EAAI,GAEvDe,IAAkBC,EAA0D,IAAI,GAChFC,IAAsBD;AAAA,IAC1B;AAAA,EAAA,GAEIE,IAAkBF,EAAsB,IAAI,GAC5CG,IAAkBH,EAAO,CAAC,GAC1BI,IAAmBJ,EAAe,EAAE,GACpCK,IAAYL,EAAkD,IAAI,GAClEM,IAAwBN,EAAO,CAAC,GAChCO,IAAsBP,EAAOvB,EAAM,WAAW;AACpD,EAAA8B,EAAoB,UAAU9B,EAAM;AAEpC,QAAM+B,IAAaR,EAAOxB,CAAO;AACjC,EAAAgC,EAAW,UAAUhC;AAErB,QAAMiC,IAAYC,EAAY,CAAC5C,MAAyC;;AACtE,UAAM6C,IAAM9C,GAAgBC,CAAG;AAC/B,IAAA0B,EAASmB,CAAG;AACZ,QAAI;AACF,OAAApE,IAAAiE,EAAW,YAAX,QAAAjE,EAAA,KAAAiE,IAAqB1C,aAAe,OAAQA;AAAA,IAC9C,QAAQ;AAAA,IAER;AAAA,EACF,GAAG,CAAA,CAAE;AAGL,EAAA8C,EAAU,MAAM;AACd,QAAI,CAAC3C,EAAS;AACd,QAAI4C,IAAY;AAChB,WAAKzE,EAAA,EACF,KAAK,MAAM;;AACV,UAAIyE,EAAW;AACf,MAAA9B,EAAkB,EAAI,GAEtBG,EAAe,IAAQ5C,KAAAC,IADX,OACe,WAAJ,gBAAAA,EAAY,SAAZ,QAAAD,EAAkB,YAAY;AAAA,IACvD,CAAC,EACA,MAAM,CAACwE,MAAe;AACrB,MAAID,KACJJ,EAAUK,aAAa,QAAQA,IAAI,IAAI,MAAM,OAAOA,CAAC,CAAC,CAAC;AAAA,IACzD,CAAC,GACI,MAAM;AACX,MAAAD,IAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC5C,GAASwC,CAAS,CAAC,GAGvBG,EAAU,MAAM;;AACd,QAAI,CAAC3C,KAAW,CAACa,EAAgB;AAEjC,UAAMiC,IAAUpD,EADJ,MACsB;AAClC,QAAI,CAACoD,EAAS;AAEd,UAAM,EAAE,SAAAC,GAAS,WAAApD,GAAW,YAAAR,EAAA,IAAe2D,GACrCE,IAAiB9C,OAAqB5B,IAAAa,EAAW,mBAAX,gBAAAb,EAA2B,kBAAiB;AAExF,IAAAyE,EAAQ,WAAW;AAAA,MACjB,uBAAA9C;AAAA,MACA,gBAAA+C;AAAA,MACA,oBAAA7C;AAAA,IAAA,CACD;AAED,UAAM8C,IAAgB,MAAM;AAC1B,YAAMC,IAAYH,EAAQ,aAAA,MAAmBpD,EAAU,UAAU;AACjE,MAAAwB,EAAe+B,CAAS;AAAA,IAC1B,GAEMC,IAAc,MAAMF,EAAA,GACpBG,IAAiB,CAACP,MAAiC;AACvD,MAAIA,EAAE,gBAAcxB,EAAgBwB,EAAE,YAAY,GAClDI,EAAA;AAAA,IACF;AAEA,WAAAF,EAAQ,iBAAiBpD,EAAU,qBAAqB,oBAAoBwD,CAAW,GACvFJ,EAAQ,iBAAiBpD,EAAU,qBAAqB,uBAAuByD,CAAc,GAC7FH,EAAA,GAEO,MAAM;AACX,MAAAF,EAAQ,oBAAoBpD,EAAU,qBAAqB,oBAAoBwD,CAAW,GAC1FJ,EAAQ;AAAA,QACNpD,EAAU,qBAAqB;AAAA,QAC/ByD;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF,GAAG,CAACpD,GAASa,GAAgBZ,GAAuBC,GAAmBC,CAAkB,CAAC,GAG1FwC,EAAU,MAAM;AACd,QAAI,CAAC3C,KAAW,CAACa,EAAgB;AAEjC,UAAMiC,IAAUpD,EADJ,MACsB;AAClC,QAAI,CAACoD,EAAS;AAEd,UAAM,EAAE,WAAAnD,MAAcmD,GAChBO,IAAS,IAAI1D,EAAU,aAAA,GACvB2D,IAAa,IAAI3D,EAAU,uBAAuB0D,CAAM;AAC9D,IAAAvB,EAAgB,UAAUuB,GAC1BrB,EAAoB,UAAUsB;AAE9B,UAAMC,IAAO,MAAM;AACjB,MAAA9B,EAAuB4B,EAAO,WAAW,GACzC1B,GAAoB0B,EAAO,QAAQ,GACnCxB,GAAoBwB,EAAO,QAAQ;AAAA,IACrC,GAEM,EAAE,uBAAAG,MAA0B7D;AAClC,WAAA0D,EAAO,iBAAiBG,EAAsB,mBAAmBD,CAAI,GACrEF,EAAO,iBAAiBG,EAAsB,sBAAsBD,CAAI,GACxEF,EAAO,iBAAiBG,EAAsB,kBAAkBD,CAAI,GACpEA,EAAA,GAEO,MAAM;AACX,MAAAF,EAAO,oBAAoBG,EAAsB,mBAAmBD,CAAI,GACxEF,EAAO,oBAAoBG,EAAsB,sBAAsBD,CAAI,GAC3EF,EAAO,oBAAoBG,EAAsB,kBAAkBD,CAAI,GACvEzB,EAAgB,UAAU,MAC1BE,EAAoB,UAAU;AAAA,IAChC;AAAA,EACF,GAAG,CAAChC,GAASa,CAAc,CAAC,GAG5B8B,EAAU,MAAM;AACd,QAAI,CAAC3C,KAAWI,MAAmB,OAAQ;AAC3C,UAAMqD,IAAKhD,EAAS;AACpB,QAAI,CAACS,KAAe,CAACuC,GAAI;AACvB,MAAIrB,EAAU,WAAWqB,MACvBA,EAAG,QAAQrB,EAAU,QAAQ,OAC7BqB,EAAG,SAASrB,EAAU,QAAQ,QAC9BA,EAAU,UAAU;AAEtB;AAAA,IACF;AACA,WAAKA,EAAU,YACbA,EAAU,UAAU,EAAE,QAAQqB,EAAG,QAAQ,OAAOA,EAAG,MAAA,IAErDA,EAAG,QAAQ,IACXA,EAAG,SAAS,GACL,MAAM;AACX,MAAIrB,EAAU,WAAW3B,EAAS,YAChCA,EAAS,QAAQ,QAAQ2B,EAAU,QAAQ,OAC3C3B,EAAS,QAAQ,SAAS2B,EAAU,QAAQ,QAC5CA,EAAU,UAAU;AAAA,IAExB;AAAA,EACF,GAAG,CAACpC,GAASI,GAAgBc,GAAaT,CAAQ,CAAC,GAGnDkC,EAAU,MAAM;AACd,QAAI,CAAC3C,KAAW,CAACa,KAAkB,CAACK,EAAa;AAEjD,UAAM4B,IAAUpD,EADJ,MACsB;AAClC,QAAI,CAACoD,EAAS;AACd,UAAMY,IAAUZ,EAAQ,QAAQ,kBAAA;AAChC,QAAI,CAACY,KAAW,CAAC/C,EAAc;AAE/B,UAAMgD,IAAW,GAAGnD,EAAM,YAAY,IAAIG,EAAa,OAAO,IACxDxB,IAAa2D,EAAQ;AAE3B,QAAIb,EAAgB,YAAY0B,EAAU;AAE1C,IAAA1B,EAAgB,UAAU0B,GAC1BtB,EAAsB,UAAU,KAAK,IAAA,IAAQ;AAC7C,UAAMuB,IAAc1E,GAAiBC,GAAYwB,GAAc,EAAE,qBAAAL,GAAqB;AACtF,IAAAsD,EAAY,WAAW,CAACpD,EAAM,UAC9BoD,EAAY,cAAc,GAE1BF,EAAQ;AAAA,MACNE;AAAA,MACA,MAAM;AACJ,QAAAzB,EAAiB,UAAU3B,EAAM,cACjC0B,EAAgB,UAAUI,EAAoB;AAAA,MAChD;AAAA,MACA,CAACzC,MAAQ2C,EAAU3C,CAAG;AAAA,IAAA;AAAA,EAE1B,GAAG;AAAA,IACDG;AAAA,IACAa;AAAA,IACAK;AAAA,IACAV,EAAM;AAAA,IACNA,EAAM;AAAA,IACNG;AAAA,IACAL;AAAA,IACAkC;AAAA,EAAA,CACD,GAEDG,EAAU,MAAM;AACd,IAAKzB,MACHe,EAAgB,UAAU;AAAA,EAE9B,GAAG,CAACf,CAAW,CAAC,GAGhByB,EAAU,MAAM;AAEd,QADI,CAAC3C,KAAW,CAACa,KAAkB,CAACK,KAChC,CAACP,EAAc;AACnB,UAAMgD,IAAW,GAAGnD,EAAM,YAAY,IAAIG,EAAa,OAAO;AAE9D,QADIsB,EAAgB,YAAY0B,KAC5B,KAAK,QAAQtB,EAAsB,QAAS;AAEhD,UAAMgB,IAASvB,EAAgB,SACzBwB,IAAatB,EAAoB;AACvC,IAAI,CAACqB,KAAU,CAACC,KACZD,EAAO,aAAa7C,EAAM,YAC5B8C,EAAW,YAAA;AAAA,EAEf,GAAG,CAACtD,GAASa,GAAgBK,GAAaV,EAAM,UAAUA,EAAM,cAAcG,CAAY,CAAC,GAG3FgC,EAAU,MAAM;;AACd,QAAI,CAAC3C,KAAW,CAACkB,EAAa;AAC9B,QAAIiB,EAAiB,YAAY3B,EAAM,cAAc;AACnD,MAAA2B,EAAiB,UAAU3B,EAAM,cACjC0B,EAAgB,UAAU1B,EAAM;AAChC;AAAA,IACF;AAEA,IADa,KAAK,IAAIA,EAAM,cAAc0B,EAAgB,OAAO,IACtDzC,QACTnB,IAAA0D,EAAoB,YAApB,QAAA1D,EAA6B,KAAK,EAAE,OAAOkC,EAAM,iBAEnD0B,EAAgB,UAAU1B,EAAM;AAAA,EAClC,GAAG,CAACR,GAASkB,GAAaV,EAAM,aAAaA,EAAM,YAAY,CAAC;AAEhE,QAAMqD,KAAiBpB,EAAY,YAAY;AAC7C,IAAK5B,KACH,MAAM1C,EAAA;AAGR,UAAM2E,IAAUpD,EADJ,MACsB;AAClC,QAAI,CAACoD,GAAS;AACZ,MAAAN,EAAU,IAAI,MAAM,kCAAkC,CAAC;AACvD;AAAA,IACF;AACA,QAAI;AACF,YAAMM,EAAQ,QAAQ,eAAA,GACtBvB,EAAS,IAAI;AAAA,IACf,SAASsB,GAAG;AACV,MAAAL,EAAUK,CAAC;AAAA,IACb;AAAA,EACF,GAAG,CAACL,GAAW3B,CAAc,CAAC,GAExBiD,IAAarB,EAAY,MAAM;AAEnC,UAAMK,IAAUpD,EADJ,MACsB,GAC5BgE,IAAUZ,KAAA,gBAAAA,EAAS,QAAQ;AAGjC,IAAKY,KACLA,EAAQ;AAAA,MACN,MAAM;AACJ,QAAAzB,EAAgB,UAAU,MAC1BV,EAAS,IAAI;AAAA,MACf;AAAA,MACA,CAAC1B,MAAQ2C,EAAU3C,CAAG;AAAA,IAAA;AAAA,EAE1B,GAAG,CAAC2C,CAAS,CAAC;AAEd,SAAAG,EAAU,MAAM;AACd,QAAKtC;AACL,aAAO,MAAM;AACX,QAAAyD,EAAA;AAAA,MACF;AAAA,EACF,GAAG,CAACzD,GAAqByD,CAAU,CAAC,GAE7B;AAAA,IACL,aAAA9C;AAAA,IACA,aAAAE;AAAA,IACA,WAAWA;AAAA,IACX,cAAAE;AAAA,IACA,OAAAE;AAAA,IACA,gBAAAuC;AAAA,IACA,YAAAC;AAAA,IACA,qBAAAtC;AAAA,IACA,kBAAAE;AAAA,IACA,kBAAAE;AAAA,EAAA;AAEJ;"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads the Google Cast Web Sender script (Cast Application Framework) once.
|
|
3
|
+
* Resolves when `window.cast.framework` is available. Safe to call multiple times.
|
|
4
|
+
*
|
|
5
|
+
* In SSR environments (no `window`), rejects with an error.
|
|
6
|
+
*/
|
|
7
|
+
export declare function loadCastFramework(): Promise<void>;
|
|
8
|
+
//# sourceMappingURL=loadCastFramework.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loadCastFramework.d.ts","sourceRoot":"","sources":["../../src/cast/loadCastFramework.ts"],"names":[],"mappings":"AAYA;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAqDjD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loadCastFramework.test.d.ts","sourceRoot":"","sources":["../../src/cast/loadCastFramework.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Track } from '../types';
|
|
2
|
+
import { CastLoadRequestLike, ChromeCastNamespace } from './castTypes';
|
|
3
|
+
/**
|
|
4
|
+
* Guess a MIME type from a file URL path. Falls back to `audio/mpeg`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function guessContentTypeFromUrl(fileUrl: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Builds a Cast `LoadRequest` for the given track using the runtime `chrome.cast.media` constructors.
|
|
9
|
+
* Call only after `loadCastFramework()` and when `chrome.cast` is defined.
|
|
10
|
+
*/
|
|
11
|
+
export declare function trackToMediaInfo(chromeCast: ChromeCastNamespace, track: Track, options?: {
|
|
12
|
+
contentTypeResolver?: (t: Track) => string;
|
|
13
|
+
}): CastLoadRequestLike;
|
|
14
|
+
//# sourceMappingURL=trackToMediaInfo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trackToMediaInfo.d.ts","sourceRoot":"","sources":["../../src/cast/trackToMediaInfo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAa5E;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAU/D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,mBAAmB,EAC/B,KAAK,EAAE,KAAK,EACZ,OAAO,CAAC,EAAE;IACR,mBAAmB,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,MAAM,CAAC;CAC5C,GACA,mBAAmB,CAuBrB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trackToMediaInfo.test.d.ts","sourceRoot":"","sources":["../../src/cast/trackToMediaInfo.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Track } from '../types';
|
|
2
|
+
import { CastErrorLike } from './castTypes';
|
|
3
|
+
export type UseGingerCastOptions = {
|
|
4
|
+
/** When false, the hook does not touch Cast APIs. Default: true. */
|
|
5
|
+
enabled?: boolean;
|
|
6
|
+
/** Receiver app ID. Default: Default Media Receiver (`CC1AD845`). */
|
|
7
|
+
receiverApplicationId?: string;
|
|
8
|
+
/** Passed to `CastContext.setOptions`. Default: `chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED`. */
|
|
9
|
+
autoJoinPolicy?: number;
|
|
10
|
+
/** Passed to `CastContext.setOptions`. Default: true. */
|
|
11
|
+
resumeSavedSession?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* When connected, silences the local `<audio>` (`muted` + `volume = 0`) so it does not compete
|
|
14
|
+
* with the TV. Restored on disconnect. Does **not** change Ginger playback state.
|
|
15
|
+
* Default: `"pause-mute"` (silence local element).
|
|
16
|
+
*/
|
|
17
|
+
syncLocalAudio?: "pause-mute" | "none";
|
|
18
|
+
/** When true, ends the Cast session when the hook unmounts. Default: false. */
|
|
19
|
+
endSessionOnUnmount?: boolean;
|
|
20
|
+
contentTypeResolver?: (track: Track) => string;
|
|
21
|
+
onError?: (error: CastErrorLike | Error) => void;
|
|
22
|
+
};
|
|
23
|
+
export type UseGingerCastResult = {
|
|
24
|
+
/** Cast APIs loaded and `chrome.cast` is present (may still be `false` on unsupported browsers). */
|
|
25
|
+
isAvailable: boolean;
|
|
26
|
+
/** Cast sender is connected to a receiver. */
|
|
27
|
+
isConnected: boolean;
|
|
28
|
+
/** Same as `isConnected`; use to gate `Ginger.Player` (`{!isCasting && <Ginger.Player />}`). */
|
|
29
|
+
isCasting: boolean;
|
|
30
|
+
/** Last `SESSION_STATE_CHANGED` value from CAF, if any. */
|
|
31
|
+
sessionState: string | null;
|
|
32
|
+
error: string | null;
|
|
33
|
+
/** Starts a Cast session (shows the Cast device picker when needed). */
|
|
34
|
+
requestSession: () => Promise<void>;
|
|
35
|
+
/** Ends the current Cast session, if any. */
|
|
36
|
+
endSession: () => void;
|
|
37
|
+
/** Position reported by `RemotePlayer` on the sender (useful if local audio is unmounted). */
|
|
38
|
+
receiverCurrentTime: number;
|
|
39
|
+
/** Duration reported by `RemotePlayer`. */
|
|
40
|
+
receiverDuration: number;
|
|
41
|
+
/** Pause state reported by `RemotePlayer`. */
|
|
42
|
+
receiverIsPaused: boolean;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Chromecast Web Sender (CAF) bridge: loads the current Ginger track on a Cast receiver and keeps
|
|
46
|
+
* transport roughly in sync while Ginger remains the queue source of truth.
|
|
47
|
+
*
|
|
48
|
+
* Prefer **`{!isCasting && <Ginger.Player />}`** so local `<audio>` does not decode the same URLs
|
|
49
|
+
* as the receiver. When you must keep `Ginger.Player` mounted, use `syncLocalAudio: "pause-mute"`
|
|
50
|
+
* to mute the local element.
|
|
51
|
+
*
|
|
52
|
+
* ```ts
|
|
53
|
+
* import { useGingerCast } from "@lucaismyname/ginger/cast";
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export declare function useGingerCast(options?: UseGingerCastOptions): UseGingerCastResult;
|
|
57
|
+
//# sourceMappingURL=useGingerCast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useGingerCast.d.ts","sourceRoot":"","sources":["../../src/cast/useGingerCast.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,KAAK,EAAE,aAAa,EAAkB,MAAM,aAAa,CAAC;AAsBjE,MAAM,MAAM,oBAAoB,GAAG;IACjC,oEAAoE;IACpE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qEAAqE;IACrE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,+FAA+F;IAC/F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yDAAyD;IACzD,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;OAIG;IACH,cAAc,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC;IACvC,+EAA+E;IAC/E,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC;IAC/C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,KAAK,IAAI,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,oGAAoG;IACpG,WAAW,EAAE,OAAO,CAAC;IACrB,8CAA8C;IAC9C,WAAW,EAAE,OAAO,CAAC;IACrB,gGAAgG;IAChG,SAAS,EAAE,OAAO,CAAC;IACnB,2DAA2D;IAC3D,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,wEAAwE;IACxE,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,6CAA6C;IAC7C,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,8FAA8F;IAC9F,mBAAmB,EAAE,MAAM,CAAC;IAC5B,2CAA2C;IAC3C,gBAAgB,EAAE,MAAM,CAAC;IACzB,8CAA8C;IAC9C,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAAC;AAWF;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,mBAAmB,CA0SrF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lucaismyname/ginger",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.60",
|
|
4
4
|
"description": "A headless react audio-player component primitive",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -59,6 +59,11 @@
|
|
|
59
59
|
"import": "./dist/remote/index.js",
|
|
60
60
|
"require": "./dist/remote/index.cjs"
|
|
61
61
|
},
|
|
62
|
+
"./cast": {
|
|
63
|
+
"types": "./dist/cast/index.d.ts",
|
|
64
|
+
"import": "./dist/cast/index.js",
|
|
65
|
+
"require": "./dist/cast/index.cjs"
|
|
66
|
+
},
|
|
62
67
|
"./crossfade": {
|
|
63
68
|
"types": "./dist/crossfade/index.d.ts",
|
|
64
69
|
"import": "./dist/crossfade/index.js",
|