@moviie/player-expo 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +230 -0
  3. package/app.plugin.cjs +3 -0
  4. package/dist/cast.cjs +100 -0
  5. package/dist/cast.cjs.map +1 -0
  6. package/dist/cast.d.cts +15 -0
  7. package/dist/cast.d.ts +15 -0
  8. package/dist/cast.mjs +92 -0
  9. package/dist/cast.mjs.map +1 -0
  10. package/dist/chunk-67DJ7NOB.mjs +294 -0
  11. package/dist/chunk-67DJ7NOB.mjs.map +1 -0
  12. package/dist/chunk-7U2LKIGU.mjs +12 -0
  13. package/dist/chunk-7U2LKIGU.mjs.map +1 -0
  14. package/dist/chunk-BJTO5JO5.mjs +10 -0
  15. package/dist/chunk-BJTO5JO5.mjs.map +1 -0
  16. package/dist/index.cjs +3934 -0
  17. package/dist/index.cjs.map +1 -0
  18. package/dist/index.d.cts +450 -0
  19. package/dist/index.d.ts +450 -0
  20. package/dist/index.mjs +3571 -0
  21. package/dist/index.mjs.map +1 -0
  22. package/dist/layout.cjs +217 -0
  23. package/dist/layout.cjs.map +1 -0
  24. package/dist/layout.d.cts +20 -0
  25. package/dist/layout.d.ts +20 -0
  26. package/dist/layout.mjs +4 -0
  27. package/dist/layout.mjs.map +1 -0
  28. package/dist/moviie-cast-adapter-DmSU2u3j.d.cts +53 -0
  29. package/dist/moviie-cast-adapter-DmSU2u3j.d.ts +53 -0
  30. package/dist/plugin/with-moviie.cjs +88 -0
  31. package/dist/plugin/with-moviie.cjs.map +1 -0
  32. package/dist/plugin/with-moviie.d.cts +52 -0
  33. package/dist/plugin/with-moviie.d.ts +52 -0
  34. package/dist/plugin/with-moviie.mjs +76 -0
  35. package/dist/plugin/with-moviie.mjs.map +1 -0
  36. package/package.json +134 -0
  37. package/plugin/validate-options.ts +31 -0
  38. package/plugin/with-moviie-types.ts +21 -0
  39. package/plugin/with-moviie.ts +84 -0
  40. package/src/apply-expo-moviie-endpoints.ts +47 -0
  41. package/src/cast/google-cast-adapter.ts +111 -0
  42. package/src/cast/index.ts +12 -0
  43. package/src/components/controls/moviie-bottom-timeline.tsx +477 -0
  44. package/src/components/controls/moviie-cast-buttons.tsx +96 -0
  45. package/src/components/controls/moviie-chrome-edge-gradients.tsx +162 -0
  46. package/src/components/controls/moviie-controls.tsx +585 -0
  47. package/src/components/controls/moviie-skin-chrome-context.tsx +374 -0
  48. package/src/components/controls/moviie-skin-smart-progress.tsx +157 -0
  49. package/src/components/icons/embed-media-icons.tsx +282 -0
  50. package/src/components/icons/moviie-embed-brand-mark.tsx +58 -0
  51. package/src/components/moviie-error-boundary.tsx +80 -0
  52. package/src/components/moviie-player-error-shell.tsx +143 -0
  53. package/src/components/moviie-player-loading-shell.tsx +59 -0
  54. package/src/components/moviie-skin-custom-fullscreen-modal.tsx +232 -0
  55. package/src/components/moviie-video-props.ts +134 -0
  56. package/src/components/moviie-video.tsx +568 -0
  57. package/src/components/moviie-video.web.tsx +167 -0
  58. package/src/components/overlays/moviie-watermark.tsx +53 -0
  59. package/src/constants.ts +374 -0
  60. package/src/hooks/use-moviie-event.ts +103 -0
  61. package/src/hooks/use-moviie-playback-ended.ts +14 -0
  62. package/src/hooks/use-moviie-playback-resume-persistence.ts +76 -0
  63. package/src/hooks/use-moviie-playback.ts +99 -0
  64. package/src/hooks/use-moviie-player-types.ts +55 -0
  65. package/src/hooks/use-moviie-player.ts +236 -0
  66. package/src/hooks/use-moviie-player.web.ts +57 -0
  67. package/src/hooks/use-moviie-telemetry.ts +133 -0
  68. package/src/index.ts +90 -0
  69. package/src/layout.ts +4 -0
  70. package/src/lib/add-moviie-playback-ended-listener.ts +16 -0
  71. package/src/lib/build-moviie-branding-marketing-url.ts +14 -0
  72. package/src/lib/build-moviie-embed-brand-home-url.ts +16 -0
  73. package/src/lib/build-moviie-skin-layout-metrics.ts +166 -0
  74. package/src/lib/build-moviie-video-presentation.ts +42 -0
  75. package/src/lib/build-moviie-watch-embed-url.ts +30 -0
  76. package/src/lib/cast-adapter-registry.ts +13 -0
  77. package/src/lib/clamp-unit-interval.ts +3 -0
  78. package/src/lib/compute-custom-fullscreen-stage-dimensions.ts +39 -0
  79. package/src/lib/compute-moviie-playback-ended.ts +31 -0
  80. package/src/lib/compute-playback-buffering.ts +41 -0
  81. package/src/lib/compute-playback-progress-ratio.ts +33 -0
  82. package/src/lib/compute-timeline-scrub-commit-time.ts +40 -0
  83. package/src/lib/custom-fullscreen-native-orientation.ts +88 -0
  84. package/src/lib/format-playback-clock.ts +27 -0
  85. package/src/lib/jsx-native-bridge.ts +45 -0
  86. package/src/lib/map-smart-progress-display-ratio.ts +43 -0
  87. package/src/lib/moviie-cast-adapter.ts +53 -0
  88. package/src/lib/moviie-error-display.ts +149 -0
  89. package/src/lib/moviie-shell-tokens.ts +27 -0
  90. package/src/lib/moviie-telemetry-callbacks.ts +76 -0
  91. package/src/lib/optional-status-bar.ts +48 -0
  92. package/src/lib/partition-moviie-video-host-style.ts +54 -0
  93. package/src/lib/remember-playback-position-storage.ts +98 -0
  94. package/src/lib/resolve-moviie-skin-layout-scale.ts +17 -0
  95. package/src/lib/resolve-orientation-lock-for-rotate-z-deg.ts +21 -0
  96. package/src/lib/resolve-skin-accent-color.ts +10 -0
  97. package/src/lib/resolve-web-embed-iframe-src.ts +17 -0
  98. package/src/lib/warn-once.ts +23 -0
  99. package/src/platform/native-application-id.ts +10 -0
  100. package/src/platform/platform-client-info.ts +31 -0
  101. package/src/playback/fetch-playback-fresh.ts +27 -0
  102. package/src/playback/playback-memory-cache.ts +52 -0
  103. package/src/provider/moviie-provider.tsx +99 -0
  104. package/src/secure-store-viewer-token-store.ts +80 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Moviie
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,230 @@
1
+ # @moviie/player-expo
2
+
3
+ React Native / Expo bindings for the Moviie video platform. Re-exports [`@moviie/player-sdk`](https://www.npmjs.com/package/@moviie/player-sdk) and ships `expo-video` integrations (`MoviieVideo`, hooks, telemetry wiring, optional Chromecast adapter).
4
+
5
+ ## Quickstart (3 steps)
6
+
7
+ ```bash
8
+ # 1. Install (5 mandatory deps)
9
+ pnpm add @moviie/player-expo \
10
+ expo expo-video expo-application expo-screen-orientation \
11
+ react-native-svg react-native-reanimated react-native-gesture-handler \
12
+ react-native-safe-area-context
13
+
14
+ # Optional — gain extra features:
15
+ # pnpm add expo-secure-store # persistent resume position + viewer token
16
+ # pnpm add expo-status-bar # hide status bar in custom fullscreen
17
+ # pnpm add react-native-google-cast # Chromecast support (subpath /cast)
18
+ ```
19
+
20
+ ```json
21
+ // 2. app.json — add config plugin
22
+ {
23
+ "expo": {
24
+ "plugins": [
25
+ ["@moviie/player-expo", { "backgroundPlayback": true, "pictureInPicture": true }],
26
+ ["expo-video", { "supportsBackgroundPlayback": true, "supportsPictureInPicture": true }]
27
+ ]
28
+ }
29
+ }
30
+ ```
31
+
32
+ ```tsx
33
+ // 3. App.tsx
34
+ import { MoviieProvider, MoviieVideo, useMoviiePlayer } from "@moviie/player-expo"
35
+
36
+ function Player({ embedId }: { embedId: string }) {
37
+ const moviie = useMoviiePlayer({ embedId })
38
+ return <MoviieVideo {...moviie} aspectRatio={16 / 9} />
39
+ }
40
+
41
+ export default function App() {
42
+ return (
43
+ <MoviieProvider publishableKey={process.env.EXPO_PUBLIC_MOVIIE_PUBLISHABLE_KEY}>
44
+ <Player embedId="YOUR-EMBED-UUID" />
45
+ </MoviieProvider>
46
+ )
47
+ }
48
+ ```
49
+
50
+ Run `pnpm expo prebuild && pnpm expo run:ios` (or `run:android`) to compile the
51
+ native module — `expo-video` doesn't ship with Expo Go.
52
+
53
+ ### Optional dependencies (graceful degradation)
54
+
55
+ | Package | Without it |
56
+ |---------|------------|
57
+ | `expo-secure-store` | Viewer token stays in memory (lost on app kill); resume position **does not persist** between sessions |
58
+ | `expo-status-bar` | Status bar stays visible during custom fullscreen overlay |
59
+ | `react-native-google-cast` | Chromecast button doesn't appear; player works normally |
60
+
61
+ When a feature requiring an optional package is exercised without that package
62
+ installed, the SDK emits a **single `console.warn`** per session pointing to the
63
+ install command. No crash, no hidden errors.
64
+
65
+ ### API endpoint override (dev only)
66
+
67
+ For local development against a non-prod backend, set env vars in `.env.local`
68
+ (Metro inlines any `EXPO_PUBLIC_*` at build time — no `expo-constants` needed):
69
+
70
+ ```bash
71
+ EXPO_PUBLIC_MOVIIE_API_BASE_URL=http://localhost:3000/api/v1
72
+ EXPO_PUBLIC_MOVIIE_EVENTS_BASE_URL=http://localhost:3000/telemetry/v1
73
+ ```
74
+
75
+ Overrides only apply when `__DEV__ === true`. Production builds always use the
76
+ default `https://api.moviie.ai/v1`.
77
+
78
+ Full docs: [docs.moviie.ai/player-expo](https://docs.moviie.ai/player-expo) ·
79
+ FAQ: [docs.moviie.ai/player-expo/faq](https://docs.moviie.ai/player-expo/faq) ·
80
+ Migration: [docs.moviie.ai/player-expo/migration](https://docs.moviie.ai/player-expo/migration)
81
+
82
+ ---
83
+
84
+ ## Requirements
85
+
86
+ - Expo SDK 52+
87
+ - `expo-video` 2+
88
+ - Dev client recommended (`expo-video` does not run in Expo Go)
89
+ - **Publishable API key** (`mvi_pub_*`) for `MoviieClient.getPlayback` — set `EXPO_PUBLIC_MOVIIE_PUBLISHABLE_KEY` (or equivalent) before fetching playback
90
+
91
+ ## App config
92
+
93
+ Register **`@moviie/player-expo`** so native manifests receive background audio and PiP-related defaults. Pair it with the **`expo-video`** plugin toggles from the [Expo Video docs](https://docs.expo.dev/versions/latest/sdk/video/). After changing plugin options, run **`expo prebuild`** (or trigger an EAS native build) so **Info.plist** and **AndroidManifest.xml** pick up the updates.
94
+
95
+ ```json
96
+ {
97
+ "expo": {
98
+ "plugins": [
99
+ [
100
+ "@moviie/player-expo",
101
+ {
102
+ "backgroundPlayback": true,
103
+ "pictureInPicture": true
104
+ }
105
+ ],
106
+ [
107
+ "expo-video",
108
+ {
109
+ "supportsBackgroundPlayback": true,
110
+ "supportsPictureInPicture": true
111
+ }
112
+ ]
113
+ ]
114
+ }
115
+ }
116
+ ```
117
+
118
+ **Plugin options:**
119
+
120
+ - **`backgroundPlayback`**: iOS `UIBackgroundModes` → `audio`
121
+ - **`pictureInPicture`**: iOS adds `picture-in-picture` plus `audio`; Android main activity `supportsPictureInPicture` + `resizeableActivity`
122
+
123
+ API endpoint overrides são **dev only** via env vars `EXPO_PUBLIC_MOVIIE_API_BASE_URL` / `EXPO_PUBLIC_MOVIIE_EVENTS_BASE_URL` (não expostos no plugin).
124
+
125
+ ## Usage
126
+
127
+ See the internal playground app `apps/playground-expo` and product docs at [docs.moviie.ai](https://docs.moviie.ai).
128
+
129
+ Import the SDK (re-exported from `@moviie/player-sdk`) and Expo bindings from this package:
130
+
131
+ ```typescript
132
+ import { MoviieClient, MOVIIE_PLAYER_EXPO_PKG_VERSION } from "@moviie/player-expo"
133
+ ```
134
+
135
+ ### Fim da reprodução (`ended`)
136
+
137
+ O expo-video emite `playToEnd` quando o vídeo termina naturalmente. Este pacote alinha isso a `PLAYER_API_EVENTS.ENDED`.
138
+
139
+ **React — hook dedicado:**
140
+
141
+ ```typescript
142
+ import { useMoviiePlaybackEnded } from "@moviie/player-expo"
143
+
144
+ useMoviiePlaybackEnded(player, () => {
145
+ // vídeo chegou ao fim
146
+ })
147
+ ```
148
+
149
+ **React — evento genérico:**
150
+
151
+ ```typescript
152
+ import { PLAYER_API_EVENTS, useMoviieEvent } from "@moviie/player-expo"
153
+
154
+ useMoviieEvent(player, PLAYER_API_EVENTS.ENDED, () => {
155
+ // vídeo chegou ao fim
156
+ })
157
+ ```
158
+
159
+ **Fora de React — subscription:**
160
+
161
+ ```typescript
162
+ import { addMoviiePlaybackEndedListener } from "@moviie/player-expo"
163
+
164
+ const sub = addMoviiePlaybackEndedListener(player, () => {
165
+ // vídeo chegou ao fim
166
+ })
167
+
168
+ sub.remove()
169
+ ```
170
+
171
+ **UI derivada de estado (ex.: ícone “repetir” no centro):** use `computeMoviiePlaybackEnded` com `playing`, `currentTime`, `duration`, `isLive` e `loop` — a mesma heurística usada pela skin Moviie, com `MOVIIE_SKIN_TIMELINE_END_SNAP_EPSILON_SECONDS` disponível no pacote se precisar espelhar o snap ao fim da timeline.
172
+
173
+ ### Resilience: error boundary
174
+
175
+ Wrap `MoviieVideo` in `MoviieErrorBoundary` to isolate crashes (bug in
176
+ `expo-video`, missing native module, etc.) from the rest of your app:
177
+
178
+ ```tsx
179
+ import * as Sentry from "@sentry/react-native"
180
+ import { MoviieErrorBoundary, MoviieVideo } from "@moviie/player-expo"
181
+
182
+ <MoviieErrorBoundary
183
+ onError={(err, info) => Sentry.captureException(err, { contexts: { react: info } })}
184
+ fallback={({ error, reset }) => <YourFallback message={error.message} onRetry={reset} />}
185
+ >
186
+ <MoviieVideo {...moviie} />
187
+ </MoviieErrorBoundary>
188
+ ```
189
+
190
+ ### Telemetry hooks (Sentry / PostHog / Datadog)
191
+
192
+ `@moviie/player-expo` ships its own telemetry to the Moviie backend. To **also**
193
+ mirror events into your monitoring pipeline, pass the opt-in callbacks:
194
+
195
+ ```tsx
196
+ <MoviieVideo
197
+ {...moviie}
198
+ onTelemetryError={(error, ctx) =>
199
+ Sentry.captureException(error, { tags: { embedId: ctx.embedId, phase: ctx.phase } })
200
+ }
201
+ onTelemetryEvent={(event) => posthog.capture(`moviie.${event.name}`, event)}
202
+ />
203
+ ```
204
+
205
+ Exceptions thrown by the callbacks are swallowed and logged — they never crash
206
+ the player. No PII flows through these hooks.
207
+
208
+ ### Cast (Chromecast, optional)
209
+
210
+ Cast support is provided via the optional subpath `@moviie/player-expo/cast`. Add
211
+ the peer dependency and wire the adapter:
212
+
213
+ ```bash
214
+ pnpm add react-native-google-cast
215
+ ```
216
+
217
+ ```tsx
218
+ import { createGoogleCastAdapter } from "@moviie/player-expo/cast"
219
+
220
+ const castAdapter = useMemo(() => createGoogleCastAdapter(), [])
221
+
222
+ <MoviieVideo {...moviie} castAdapter={castAdapter} />
223
+ ```
224
+
225
+ If you don't use cast, **don't install `react-native-google-cast`** — the package
226
+ never imports it from the main entry, so your bundle stays slim.
227
+
228
+ ## License
229
+
230
+ MIT — see [`LICENSE`](./LICENSE). Copyright (c) 2026 Moviie.
package/app.plugin.cjs ADDED
@@ -0,0 +1,3 @@
1
+ "use strict"
2
+
3
+ module.exports = require("./dist/plugin/with-moviie.cjs")
package/dist/cast.cjs ADDED
@@ -0,0 +1,100 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+ var reactNative = require('react-native');
5
+ var GoogleCast = require('react-native-google-cast');
6
+
7
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
+
9
+ var React__default = /*#__PURE__*/_interopDefault(React);
10
+ var GoogleCast__default = /*#__PURE__*/_interopDefault(GoogleCast);
11
+
12
+ // src/cast/google-cast-adapter.ts
13
+ var DEFAULT_HLS_CONTENT_TYPE = "application/x-mpegURL";
14
+ function mapCastState(state) {
15
+ switch (state) {
16
+ case GoogleCast.CastState.CONNECTED:
17
+ return "connected";
18
+ case GoogleCast.CastState.CONNECTING:
19
+ return "connecting";
20
+ case GoogleCast.CastState.NOT_CONNECTED:
21
+ return "disconnected";
22
+ case GoogleCast.CastState.NO_DEVICES_AVAILABLE:
23
+ default:
24
+ return "unavailable";
25
+ }
26
+ }
27
+ function createGoogleCastAdapter() {
28
+ let lastKnownState = "unavailable";
29
+ void GoogleCast__default.default.getCastState().then((state) => {
30
+ lastKnownState = mapCastState(state);
31
+ }).catch(() => {
32
+ lastKnownState = "unavailable";
33
+ });
34
+ return {
35
+ isAvailable: () => true,
36
+ getState: () => lastKnownState,
37
+ subscribe(callback) {
38
+ void GoogleCast__default.default.getCastState().then((state) => {
39
+ lastKnownState = mapCastState(state);
40
+ callback(lastKnownState);
41
+ }).catch(() => void 0);
42
+ const subscription = GoogleCast__default.default.onCastStateChanged((castState) => {
43
+ lastKnownState = mapCastState(castState);
44
+ callback(lastKnownState);
45
+ });
46
+ return () => {
47
+ subscription.remove();
48
+ };
49
+ },
50
+ async showDevicePicker() {
51
+ await GoogleCast__default.default.showCastDialog();
52
+ },
53
+ async loadMedia(payload) {
54
+ const sessionManager = GoogleCast__default.default.getSessionManager();
55
+ const session = await sessionManager.getCurrentCastSession();
56
+ if (!session) {
57
+ return;
58
+ }
59
+ await session.client.loadMedia({
60
+ autoplay: true,
61
+ startTime: payload.startSeconds,
62
+ mediaInfo: {
63
+ contentUrl: payload.uri,
64
+ contentType: payload.contentType ?? DEFAULT_HLS_CONTENT_TYPE,
65
+ streamDuration: payload.durationSeconds ?? void 0,
66
+ metadata: {
67
+ type: "movie",
68
+ title: payload.title,
69
+ images: payload.posterUrl ? [{ url: payload.posterUrl }] : void 0
70
+ }
71
+ }
72
+ });
73
+ },
74
+ async endSession() {
75
+ await GoogleCast__default.default.getSessionManager().endCurrentSession(true);
76
+ },
77
+ renderAndroidProxy() {
78
+ if (reactNative.Platform.OS !== "android") return null;
79
+ return React__default.default.createElement(
80
+ reactNative.View,
81
+ {
82
+ style: { position: "absolute", top: -9999, left: -9999 },
83
+ pointerEvents: "none"
84
+ },
85
+ React__default.default.createElement(GoogleCast.CastButton, {
86
+ style: { width: 48, height: 48 }
87
+ })
88
+ );
89
+ }
90
+ };
91
+ }
92
+ function _registerCastAdapter(adapter) {
93
+ }
94
+
95
+ // src/cast/index.ts
96
+ _registerCastAdapter(createGoogleCastAdapter());
97
+
98
+ exports.createGoogleCastAdapter = createGoogleCastAdapter;
99
+ //# sourceMappingURL=cast.cjs.map
100
+ //# sourceMappingURL=cast.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cast/google-cast-adapter.ts","../src/lib/cast-adapter-registry.ts","../src/cast/index.ts"],"names":["CastState","GoogleCast","Platform","React","View","CastButton"],"mappings":";;;;;;;;;;;;AAkBA,IAAM,wBAAA,GAA2B,uBAAA;AAEjC,SAAS,aAAa,KAAA,EAAsD;AAC1E,EAAA,QAAQ,KAAA;AAAO,IACb,KAAKA,oBAAA,CAAU,SAAA;AACb,MAAA,OAAO,WAAA;AAAA,IACT,KAAKA,oBAAA,CAAU,UAAA;AACb,MAAA,OAAO,YAAA;AAAA,IACT,KAAKA,oBAAA,CAAU,aAAA;AACb,MAAA,OAAO,cAAA;AAAA,IACT,KAAKA,oBAAA,CAAU,oBAAA;AAAA,IACf;AACE,MAAA,OAAO,aAAA;AAAA;AAEb;AAEO,SAAS,uBAAA,GAA6C;AAC3D,EAAA,IAAI,cAAA,GAAkC,aAAA;AAGtC,EAAA,KAAKC,2BAAA,CAAW,YAAA,EAAa,CAC1B,IAAA,CAAK,CAAC,KAAA,KAAU;AACf,IAAA,cAAA,GAAiB,aAAa,KAAK,CAAA;AAAA,EACrC,CAAC,CAAA,CACA,KAAA,CAAM,MAAM;AACX,IAAA,cAAA,GAAiB,aAAA;AAAA,EACnB,CAAC,CAAA;AAEH,EAAA,OAAO;AAAA,IACL,aAAa,MAAM,IAAA;AAAA,IACnB,UAAU,MAAM,cAAA;AAAA,IAChB,UAAU,QAAA,EAAU;AAElB,MAAA,KAAKA,2BAAA,CAAW,YAAA,EAAa,CAC1B,IAAA,CAAK,CAAC,KAAA,KAAU;AACf,QAAA,cAAA,GAAiB,aAAa,KAAK,CAAA;AACnC,QAAA,QAAA,CAAS,cAAc,CAAA;AAAA,MACzB,CAAC,CAAA,CACA,KAAA,CAAM,MAAM,MAAS,CAAA;AAExB,MAAA,MAAM,YAAA,GAAeA,2BAAA,CAAW,kBAAA,CAAmB,CAAC,SAAA,KAAc;AAChE,QAAA,cAAA,GAAiB,aAAa,SAAS,CAAA;AACvC,QAAA,QAAA,CAAS,cAAc,CAAA;AAAA,MACzB,CAAC,CAAA;AAED,MAAA,OAAO,MAAM;AACX,QAAA,YAAA,CAAa,MAAA,EAAO;AAAA,MACtB,CAAA;AAAA,IACF,CAAA;AAAA,IACA,MAAM,gBAAA,GAAmB;AACvB,MAAA,MAAMA,4BAAW,cAAA,EAAe;AAAA,IAClC,CAAA;AAAA,IACA,MAAM,UAAU,OAAA,EAAiC;AAC/C,MAAA,MAAM,cAAA,GAAiBA,4BAAW,iBAAA,EAAkB;AACpD,MAAA,MAAM,OAAA,GAAU,MAAM,cAAA,CAAe,qBAAA,EAAsB;AAC3D,MAAA,IAAI,CAAC,OAAA,EAAS;AACZ,QAAA;AAAA,MACF;AACA,MAAA,MAAM,OAAA,CAAQ,OAAO,SAAA,CAAU;AAAA,QAC7B,QAAA,EAAU,IAAA;AAAA,QACV,WAAW,OAAA,CAAQ,YAAA;AAAA,QACnB,SAAA,EAAW;AAAA,UACT,YAAY,OAAA,CAAQ,GAAA;AAAA,UACpB,WAAA,EAAa,QAAQ,WAAA,IAAe,wBAAA;AAAA,UACpC,cAAA,EAAgB,QAAQ,eAAA,IAAmB,MAAA;AAAA,UAC3C,QAAA,EAAU;AAAA,YACR,IAAA,EAAM,OAAA;AAAA,YACN,OAAO,OAAA,CAAQ,KAAA;AAAA,YACf,MAAA,EAAQ,QAAQ,SAAA,GAAY,CAAC,EAAE,GAAA,EAAK,OAAA,CAAQ,SAAA,EAAW,CAAA,GAAI;AAAA;AAC7D;AACF,OACD,CAAA;AAAA,IACH,CAAA;AAAA,IACA,MAAM,UAAA,GAAa;AACjB,MAAA,MAAMA,2BAAA,CAAW,iBAAA,EAAkB,CAAE,iBAAA,CAAkB,IAAI,CAAA;AAAA,IAC7D,CAAA;AAAA,IACA,kBAAA,GAAqB;AACnB,MAAA,IAAIC,oBAAA,CAAS,EAAA,KAAO,SAAA,EAAW,OAAO,IAAA;AAGtC,MAAA,OAAOC,sBAAA,CAAM,aAAA;AAAA,QACXC,gBAAA;AAAA,QACA;AAAA,UACE,OAAO,EAAE,QAAA,EAAU,YAAY,GAAA,EAAK,KAAA,EAAO,MAAM,KAAA,EAAM;AAAA,UACvD,aAAA,EAAe;AAAA,SACjB;AAAA,QACAD,sBAAA,CAAM,cAAcE,qBAAA,EAAY;AAAA,UAC9B,KAAA,EAAO,EAAE,KAAA,EAAO,EAAA,EAAI,QAAQ,EAAA;AAAG,SAChC;AAAA,OACH;AAAA,IACF;AAAA,GACF;AACF;ACzGO,SAAS,qBAAqB,OAAA,EAAkC;AAEvE;;;ACIA,oBAAA,CAAqB,yBAAyB,CAAA","file":"cast.cjs","sourcesContent":["/**\n * Implementação do {@link MoviieCastAdapter} sobre `react-native-google-cast`.\n *\n * **Atenção**: este arquivo importa `react-native-google-cast` no topo. Por isso\n * só deve ser importado via subpath `@moviie/player-expo/cast`, **nunca** pelo\n * package principal. Consumidores que não usam cast não precisam instalar a lib.\n */\n\nimport React from 'react'\nimport { Platform, View } from 'react-native'\nimport GoogleCast, { CastButton, CastState } from 'react-native-google-cast'\n\nimport type {\n MoviieCastAdapter,\n MoviieCastMediaPayload,\n MoviieCastState,\n} from '../lib/moviie-cast-adapter'\n\nconst DEFAULT_HLS_CONTENT_TYPE = 'application/x-mpegURL'\n\nfunction mapCastState(state: CastState | null | undefined): MoviieCastState {\n switch (state) {\n case CastState.CONNECTED:\n return 'connected'\n case CastState.CONNECTING:\n return 'connecting'\n case CastState.NOT_CONNECTED:\n return 'disconnected'\n case CastState.NO_DEVICES_AVAILABLE:\n default:\n return 'unavailable'\n }\n}\n\nexport function createGoogleCastAdapter(): MoviieCastAdapter {\n let lastKnownState: MoviieCastState = 'unavailable'\n\n // Hidrata estado inicial em background; subscribe() já entrega o valor real\n void GoogleCast.getCastState()\n .then((state) => {\n lastKnownState = mapCastState(state)\n })\n .catch(() => {\n lastKnownState = 'unavailable'\n })\n\n return {\n isAvailable: () => true,\n getState: () => lastKnownState,\n subscribe(callback) {\n // Entrega estado atual imediatamente (async, não bloqueia)\n void GoogleCast.getCastState()\n .then((state) => {\n lastKnownState = mapCastState(state)\n callback(lastKnownState)\n })\n .catch(() => undefined)\n\n const subscription = GoogleCast.onCastStateChanged((castState) => {\n lastKnownState = mapCastState(castState)\n callback(lastKnownState)\n })\n\n return () => {\n subscription.remove()\n }\n },\n async showDevicePicker() {\n await GoogleCast.showCastDialog()\n },\n async loadMedia(payload: MoviieCastMediaPayload) {\n const sessionManager = GoogleCast.getSessionManager()\n const session = await sessionManager.getCurrentCastSession()\n if (!session) {\n return\n }\n await session.client.loadMedia({\n autoplay: true,\n startTime: payload.startSeconds,\n mediaInfo: {\n contentUrl: payload.uri,\n contentType: payload.contentType ?? DEFAULT_HLS_CONTENT_TYPE,\n streamDuration: payload.durationSeconds ?? undefined,\n metadata: {\n type: 'movie',\n title: payload.title,\n images: payload.posterUrl ? [{ url: payload.posterUrl }] : undefined,\n },\n },\n })\n },\n async endSession() {\n await GoogleCast.getSessionManager().endCurrentSession(true)\n },\n renderAndroidProxy() {\n if (Platform.OS !== 'android') return null\n // Register a MediaRouteButton in the window so showCastDialog() can use it\n // as the primary path. Positioned off-screen so it is never visible.\n return React.createElement(\n View,\n {\n style: { position: 'absolute', top: -9999, left: -9999 },\n pointerEvents: 'none',\n },\n React.createElement(CastButton, {\n style: { width: 48, height: 48 },\n })\n )\n },\n }\n}\n","import type { MoviieCastAdapter } from './moviie-cast-adapter'\n\nlet _registered: MoviieCastAdapter | null = null\n\n/** Called automatically when `@moviie/player-expo/cast` is imported. */\nexport function _registerCastAdapter(adapter: MoviieCastAdapter): void {\n _registered = adapter\n}\n\n/** Returns the auto-registered adapter, or null if cast subpath was never imported. */\nexport function getRegisteredCastAdapter(): MoviieCastAdapter | null {\n return _registered\n}\n","export { createGoogleCastAdapter } from './google-cast-adapter'\nexport type {\n MoviieCastAdapter,\n MoviieCastMediaPayload,\n MoviieCastState,\n} from '../lib/moviie-cast-adapter'\n\n// Side-effect: auto-register adapter when this subpath is imported.\n// Enables the zero-config pattern: `import \"@moviie/player-expo/cast\"` in _layout.tsx.\nimport { createGoogleCastAdapter } from './google-cast-adapter'\nimport { _registerCastAdapter } from '../lib/cast-adapter-registry'\n_registerCastAdapter(createGoogleCastAdapter())\n"]}
@@ -0,0 +1,15 @@
1
+ import { M as MoviieCastAdapter } from './moviie-cast-adapter-DmSU2u3j.cjs';
2
+ export { a as MoviieCastMediaPayload, b as MoviieCastState } from './moviie-cast-adapter-DmSU2u3j.cjs';
3
+ import 'react';
4
+
5
+ /**
6
+ * Implementação do {@link MoviieCastAdapter} sobre `react-native-google-cast`.
7
+ *
8
+ * **Atenção**: este arquivo importa `react-native-google-cast` no topo. Por isso
9
+ * só deve ser importado via subpath `@moviie/player-expo/cast`, **nunca** pelo
10
+ * package principal. Consumidores que não usam cast não precisam instalar a lib.
11
+ */
12
+
13
+ declare function createGoogleCastAdapter(): MoviieCastAdapter;
14
+
15
+ export { MoviieCastAdapter, createGoogleCastAdapter };
package/dist/cast.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { M as MoviieCastAdapter } from './moviie-cast-adapter-DmSU2u3j.js';
2
+ export { a as MoviieCastMediaPayload, b as MoviieCastState } from './moviie-cast-adapter-DmSU2u3j.js';
3
+ import 'react';
4
+
5
+ /**
6
+ * Implementação do {@link MoviieCastAdapter} sobre `react-native-google-cast`.
7
+ *
8
+ * **Atenção**: este arquivo importa `react-native-google-cast` no topo. Por isso
9
+ * só deve ser importado via subpath `@moviie/player-expo/cast`, **nunca** pelo
10
+ * package principal. Consumidores que não usam cast não precisam instalar a lib.
11
+ */
12
+
13
+ declare function createGoogleCastAdapter(): MoviieCastAdapter;
14
+
15
+ export { MoviieCastAdapter, createGoogleCastAdapter };
package/dist/cast.mjs ADDED
@@ -0,0 +1,92 @@
1
+ import { _registerCastAdapter } from './chunk-7U2LKIGU.mjs';
2
+ import './chunk-BJTO5JO5.mjs';
3
+ import React from 'react';
4
+ import { Platform, View } from 'react-native';
5
+ import GoogleCast, { CastButton, CastState } from 'react-native-google-cast';
6
+
7
+ var DEFAULT_HLS_CONTENT_TYPE = "application/x-mpegURL";
8
+ function mapCastState(state) {
9
+ switch (state) {
10
+ case CastState.CONNECTED:
11
+ return "connected";
12
+ case CastState.CONNECTING:
13
+ return "connecting";
14
+ case CastState.NOT_CONNECTED:
15
+ return "disconnected";
16
+ case CastState.NO_DEVICES_AVAILABLE:
17
+ default:
18
+ return "unavailable";
19
+ }
20
+ }
21
+ function createGoogleCastAdapter() {
22
+ let lastKnownState = "unavailable";
23
+ void GoogleCast.getCastState().then((state) => {
24
+ lastKnownState = mapCastState(state);
25
+ }).catch(() => {
26
+ lastKnownState = "unavailable";
27
+ });
28
+ return {
29
+ isAvailable: () => true,
30
+ getState: () => lastKnownState,
31
+ subscribe(callback) {
32
+ void GoogleCast.getCastState().then((state) => {
33
+ lastKnownState = mapCastState(state);
34
+ callback(lastKnownState);
35
+ }).catch(() => void 0);
36
+ const subscription = GoogleCast.onCastStateChanged((castState) => {
37
+ lastKnownState = mapCastState(castState);
38
+ callback(lastKnownState);
39
+ });
40
+ return () => {
41
+ subscription.remove();
42
+ };
43
+ },
44
+ async showDevicePicker() {
45
+ await GoogleCast.showCastDialog();
46
+ },
47
+ async loadMedia(payload) {
48
+ const sessionManager = GoogleCast.getSessionManager();
49
+ const session = await sessionManager.getCurrentCastSession();
50
+ if (!session) {
51
+ return;
52
+ }
53
+ await session.client.loadMedia({
54
+ autoplay: true,
55
+ startTime: payload.startSeconds,
56
+ mediaInfo: {
57
+ contentUrl: payload.uri,
58
+ contentType: payload.contentType ?? DEFAULT_HLS_CONTENT_TYPE,
59
+ streamDuration: payload.durationSeconds ?? void 0,
60
+ metadata: {
61
+ type: "movie",
62
+ title: payload.title,
63
+ images: payload.posterUrl ? [{ url: payload.posterUrl }] : void 0
64
+ }
65
+ }
66
+ });
67
+ },
68
+ async endSession() {
69
+ await GoogleCast.getSessionManager().endCurrentSession(true);
70
+ },
71
+ renderAndroidProxy() {
72
+ if (Platform.OS !== "android") return null;
73
+ return React.createElement(
74
+ View,
75
+ {
76
+ style: { position: "absolute", top: -9999, left: -9999 },
77
+ pointerEvents: "none"
78
+ },
79
+ React.createElement(CastButton, {
80
+ style: { width: 48, height: 48 }
81
+ })
82
+ );
83
+ }
84
+ };
85
+ }
86
+
87
+ // src/cast/index.ts
88
+ _registerCastAdapter(createGoogleCastAdapter());
89
+
90
+ export { createGoogleCastAdapter };
91
+ //# sourceMappingURL=cast.mjs.map
92
+ //# sourceMappingURL=cast.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cast/google-cast-adapter.ts","../src/cast/index.ts"],"names":[],"mappings":"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","file":"cast.mjs","sourcesContent":["/**\n * Implementação do {@link MoviieCastAdapter} sobre `react-native-google-cast`.\n *\n * **Atenção**: este arquivo importa `react-native-google-cast` no topo. Por isso\n * só deve ser importado via subpath `@moviie/player-expo/cast`, **nunca** pelo\n * package principal. Consumidores que não usam cast não precisam instalar a lib.\n */\n\nimport React from 'react'\nimport { Platform, View } from 'react-native'\nimport GoogleCast, { CastButton, CastState } from 'react-native-google-cast'\n\nimport type {\n MoviieCastAdapter,\n MoviieCastMediaPayload,\n MoviieCastState,\n} from '../lib/moviie-cast-adapter'\n\nconst DEFAULT_HLS_CONTENT_TYPE = 'application/x-mpegURL'\n\nfunction mapCastState(state: CastState | null | undefined): MoviieCastState {\n switch (state) {\n case CastState.CONNECTED:\n return 'connected'\n case CastState.CONNECTING:\n return 'connecting'\n case CastState.NOT_CONNECTED:\n return 'disconnected'\n case CastState.NO_DEVICES_AVAILABLE:\n default:\n return 'unavailable'\n }\n}\n\nexport function createGoogleCastAdapter(): MoviieCastAdapter {\n let lastKnownState: MoviieCastState = 'unavailable'\n\n // Hidrata estado inicial em background; subscribe() já entrega o valor real\n void GoogleCast.getCastState()\n .then((state) => {\n lastKnownState = mapCastState(state)\n })\n .catch(() => {\n lastKnownState = 'unavailable'\n })\n\n return {\n isAvailable: () => true,\n getState: () => lastKnownState,\n subscribe(callback) {\n // Entrega estado atual imediatamente (async, não bloqueia)\n void GoogleCast.getCastState()\n .then((state) => {\n lastKnownState = mapCastState(state)\n callback(lastKnownState)\n })\n .catch(() => undefined)\n\n const subscription = GoogleCast.onCastStateChanged((castState) => {\n lastKnownState = mapCastState(castState)\n callback(lastKnownState)\n })\n\n return () => {\n subscription.remove()\n }\n },\n async showDevicePicker() {\n await GoogleCast.showCastDialog()\n },\n async loadMedia(payload: MoviieCastMediaPayload) {\n const sessionManager = GoogleCast.getSessionManager()\n const session = await sessionManager.getCurrentCastSession()\n if (!session) {\n return\n }\n await session.client.loadMedia({\n autoplay: true,\n startTime: payload.startSeconds,\n mediaInfo: {\n contentUrl: payload.uri,\n contentType: payload.contentType ?? DEFAULT_HLS_CONTENT_TYPE,\n streamDuration: payload.durationSeconds ?? undefined,\n metadata: {\n type: 'movie',\n title: payload.title,\n images: payload.posterUrl ? [{ url: payload.posterUrl }] : undefined,\n },\n },\n })\n },\n async endSession() {\n await GoogleCast.getSessionManager().endCurrentSession(true)\n },\n renderAndroidProxy() {\n if (Platform.OS !== 'android') return null\n // Register a MediaRouteButton in the window so showCastDialog() can use it\n // as the primary path. Positioned off-screen so it is never visible.\n return React.createElement(\n View,\n {\n style: { position: 'absolute', top: -9999, left: -9999 },\n pointerEvents: 'none',\n },\n React.createElement(CastButton, {\n style: { width: 48, height: 48 },\n })\n )\n },\n }\n}\n","export { createGoogleCastAdapter } from './google-cast-adapter'\nexport type {\n MoviieCastAdapter,\n MoviieCastMediaPayload,\n MoviieCastState,\n} from '../lib/moviie-cast-adapter'\n\n// Side-effect: auto-register adapter when this subpath is imported.\n// Enables the zero-config pattern: `import \"@moviie/player-expo/cast\"` in _layout.tsx.\nimport { createGoogleCastAdapter } from './google-cast-adapter'\nimport { _registerCastAdapter } from '../lib/cast-adapter-registry'\n_registerCastAdapter(createGoogleCastAdapter())\n"]}