@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
@@ -0,0 +1,17 @@
1
+ import type { MoviiePlaybackData } from "@moviie/player-sdk"
2
+
3
+ import {
4
+ buildMoviieWatchEmbedUrl,
5
+ resolveMoviieWatchOrigin,
6
+ } from "./build-moviie-watch-embed-url"
7
+
8
+ export function resolveWebEmbedIframeSrc(
9
+ playback: MoviiePlaybackData | null | undefined,
10
+ watchOriginResolver: () => string = resolveMoviieWatchOrigin
11
+ ): string | null {
12
+ const embedId = playback?.embedId?.trim()
13
+ if (!embedId) {
14
+ return null
15
+ }
16
+ return buildMoviieWatchEmbedUrl(embedId, watchOriginResolver())
17
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Console warning idempotente por chave. Útil para dependências opcionais ausentes:
3
+ * dispara uma única vez por sessão para a mesma chave, evitando ruído no console.
4
+ *
5
+ * Reusável para qualquer feature gated por dep opcional (cast, DRM custom,
6
+ * analytics provider, etc.).
7
+ */
8
+
9
+ const FIRED_KEYS = new Set<string>()
10
+
11
+ export function warnOnce(key: string, message: string): void {
12
+ if (FIRED_KEYS.has(key)) {
13
+ return
14
+ }
15
+ FIRED_KEYS.add(key)
16
+ // eslint-disable-next-line no-console
17
+ console.warn(message)
18
+ }
19
+
20
+ /** Limpa o set de chaves disparadas. Use apenas em testes. */
21
+ export function __resetWarnOnceForTests(): void {
22
+ FIRED_KEYS.clear()
23
+ }
@@ -0,0 +1,10 @@
1
+ import * as Application from "expo-application"
2
+
3
+ export function readNativeApplicationId(): string | undefined {
4
+ try {
5
+ const id = Application.applicationId
6
+ return id ?? undefined
7
+ } catch {
8
+ return undefined
9
+ }
10
+ }
@@ -0,0 +1,31 @@
1
+ import { Platform } from "react-native"
2
+
3
+ import type { MoviieClientInfo } from "@moviie/player-sdk"
4
+
5
+ import { readNativeApplicationId } from "./native-application-id"
6
+
7
+ let cached: MoviieClientInfo | null = null
8
+
9
+ export function buildPlatformClientInfo(sdkVersion: string): MoviieClientInfo {
10
+ if (cached) {
11
+ return cached
12
+ }
13
+
14
+ const os = Platform.OS
15
+ const platform: MoviieClientInfo["platform"] =
16
+ os === "ios" ? "ios" : os === "android" ? "android" : "web"
17
+
18
+ const bundleId =
19
+ platform === "web" ? undefined : readNativeApplicationId()
20
+
21
+ cached = {
22
+ platform,
23
+ bundleId,
24
+ sdkVersion,
25
+ }
26
+ return cached
27
+ }
28
+
29
+ export function resetPlatformClientInfoCache(): void {
30
+ cached = null
31
+ }
@@ -0,0 +1,27 @@
1
+ import type { MoviiePlaybackData } from "@moviie/player-sdk"
2
+
3
+ import { writePlaybackCache } from "./playback-memory-cache"
4
+
5
+ export async function fetchPlaybackFreshWriteCache(
6
+ scope: string | null | undefined,
7
+ embedId: string,
8
+ signal: AbortSignal,
9
+ getPlayback: (
10
+ embedId: string,
11
+ signal: AbortSignal,
12
+ ) => Promise<MoviiePlaybackData>,
13
+ ): Promise<
14
+ { ok: true; data: MoviiePlaybackData } | { ok: false; error: Error }
15
+ > {
16
+ try {
17
+ const data = await getPlayback(embedId, signal)
18
+ writePlaybackCache(scope, embedId, data)
19
+ return { ok: true, data }
20
+ } catch (unknownError) {
21
+ const error =
22
+ unknownError instanceof Error
23
+ ? unknownError
24
+ : new Error(String(unknownError))
25
+ return { ok: false, error }
26
+ }
27
+ }
@@ -0,0 +1,52 @@
1
+ import type { MoviiePlaybackData } from "@moviie/player-sdk"
2
+
3
+ import { MOVIIE_PLAYBACK_MEMORY_CACHE_TTL_MS } from "../constants"
4
+
5
+ interface CacheEntry {
6
+ data: MoviiePlaybackData
7
+ expiresAt: number
8
+ }
9
+
10
+ const playbackCache = new Map<string, CacheEntry>()
11
+
12
+ function cacheKey(scope: string | null | undefined, embedId: string): string {
13
+ return `${scope ?? "__anon__"}::${embedId}`
14
+ }
15
+
16
+ function resolveEntryExpiry(data: MoviiePlaybackData): number {
17
+ const now = Date.now()
18
+ const fallback = now + MOVIIE_PLAYBACK_MEMORY_CACHE_TTL_MS
19
+ const serverExpiry = data.playback?.expiresAt
20
+ if (!serverExpiry) return fallback
21
+ const parsed = Date.parse(serverExpiry)
22
+ if (!Number.isFinite(parsed)) return fallback
23
+ return Math.min(fallback, parsed)
24
+ }
25
+
26
+ export function readPlaybackCache(
27
+ scope: string | null | undefined,
28
+ embedId: string
29
+ ): MoviiePlaybackData | null {
30
+ const key = cacheKey(scope, embedId)
31
+ const hit = playbackCache.get(key)
32
+ if (!hit || hit.expiresAt <= Date.now()) {
33
+ playbackCache.delete(key)
34
+ return null
35
+ }
36
+ return hit.data
37
+ }
38
+
39
+ export function writePlaybackCache(
40
+ scope: string | null | undefined,
41
+ embedId: string,
42
+ data: MoviiePlaybackData
43
+ ): void {
44
+ playbackCache.set(cacheKey(scope, embedId), {
45
+ data,
46
+ expiresAt: resolveEntryExpiry(data),
47
+ })
48
+ }
49
+
50
+ export function clearPlaybackMemoryCache(): void {
51
+ playbackCache.clear()
52
+ }
@@ -0,0 +1,99 @@
1
+ import {
2
+ createContext,
3
+ useContext,
4
+ useMemo,
5
+ type ReactNode,
6
+ } from "react"
7
+
8
+ import {
9
+ buildClientHeaders,
10
+ MemoryViewerTokenStore,
11
+ MoviieClient,
12
+ type MoviieClientInfo,
13
+ type ViewerTokenStore,
14
+ } from "@moviie/player-sdk"
15
+
16
+ import { MOVIIE_PLAYER_EXPO_PKG_VERSION } from "../constants"
17
+ import { buildPlatformClientInfo } from "../platform/platform-client-info"
18
+ import { SecureStoreViewerTokenStore } from "../secure-store-viewer-token-store"
19
+
20
+ export interface MoviieContextValue {
21
+ client: MoviieClient
22
+ viewerTokenStore: ViewerTokenStore
23
+ publishableKey: string | null
24
+ clientInfo: MoviieClientInfo
25
+ sdkVersion: string
26
+ /** Headers reused by TelemetryClient (Bearer + X-Moviie-Client when aplicável) */
27
+ telemetryHeaders: Record<string, string>
28
+ }
29
+
30
+ const MoviieContext = createContext<MoviieContextValue | null>(null)
31
+
32
+ let fallbackContext: MoviieContextValue | null = null
33
+
34
+ function getOrCreateFallbackContext(): MoviieContextValue {
35
+ if (!fallbackContext) {
36
+ const sdkVersion = MOVIIE_PLAYER_EXPO_PKG_VERSION
37
+ const clientInfo = buildPlatformClientInfo(sdkVersion)
38
+ const viewerTokenStore = new MemoryViewerTokenStore()
39
+ const client = new MoviieClient({
40
+ publishableKey: undefined,
41
+ clientInfo,
42
+ sdkVersion,
43
+ })
44
+ fallbackContext = {
45
+ client,
46
+ viewerTokenStore,
47
+ publishableKey: null,
48
+ clientInfo,
49
+ sdkVersion,
50
+ telemetryHeaders: buildClientHeaders({
51
+ clientInfo,
52
+ sdkVersion,
53
+ }),
54
+ }
55
+ }
56
+ return fallbackContext
57
+ }
58
+
59
+ export function useMoviieContext(): MoviieContextValue {
60
+ const ctx = useContext(MoviieContext)
61
+ return ctx ?? getOrCreateFallbackContext()
62
+ }
63
+
64
+ export function MoviieProvider({
65
+ publishableKey,
66
+ children,
67
+ }: {
68
+ publishableKey?: string | null
69
+ children: ReactNode
70
+ }) {
71
+ const value = useMemo((): MoviieContextValue => {
72
+ const sdkVersion = MOVIIE_PLAYER_EXPO_PKG_VERSION
73
+ const clientInfo = buildPlatformClientInfo(sdkVersion)
74
+ const trimmedKey = publishableKey?.trim()
75
+ const viewerTokenStore = new SecureStoreViewerTokenStore()
76
+ const client = new MoviieClient({
77
+ publishableKey: trimmedKey || undefined,
78
+ clientInfo,
79
+ sdkVersion,
80
+ })
81
+ const telemetryHeaders = buildClientHeaders({
82
+ publishableKey: trimmedKey || undefined,
83
+ clientInfo,
84
+ sdkVersion,
85
+ })
86
+ return {
87
+ client,
88
+ viewerTokenStore,
89
+ publishableKey: trimmedKey ?? null,
90
+ clientInfo,
91
+ sdkVersion,
92
+ telemetryHeaders,
93
+ }
94
+ }, [publishableKey])
95
+
96
+ return (
97
+ <MoviieContext.Provider value={value}>{children}</MoviieContext.Provider>
98
+ )
99
+ }
@@ -0,0 +1,80 @@
1
+ import type { ViewerTokenStore } from '@moviie/player-sdk'
2
+ import { MemoryViewerTokenStore } from '@moviie/player-sdk'
3
+
4
+ import { warnOnce } from './lib/warn-once'
5
+
6
+ export const MOVIIE_VIEWER_TOKEN_SECURE_STORE_KEY = 'moviie_viewer_token_v1' as const
7
+
8
+ const SECURE_STORE_MISSING_WARNING =
9
+ '[@moviie/player-expo] `expo-secure-store` não está instalado — viewer token será mantido apenas em memória (perde ao matar app). ' +
10
+ 'Instala com `pnpm add expo-secure-store` se quiseres persistência entre sessões.'
11
+
12
+ interface StoredEnvelope {
13
+ token: string
14
+ expiresAt: number
15
+ }
16
+
17
+ function isStoredEnvelope(value: unknown): value is StoredEnvelope {
18
+ if (!value || typeof value !== 'object') return false
19
+ const v = value as Record<string, unknown>
20
+ return typeof v.token === 'string' && typeof v.expiresAt === 'number'
21
+ }
22
+
23
+ export class SecureStoreViewerTokenStore implements ViewerTokenStore {
24
+ private readonly fallback: ViewerTokenStore
25
+ private readonly storageKey: string
26
+
27
+ constructor(options?: { fallback?: ViewerTokenStore; storageKey?: string }) {
28
+ this.fallback = options?.fallback ?? new MemoryViewerTokenStore()
29
+ this.storageKey = options?.storageKey ?? MOVIIE_VIEWER_TOKEN_SECURE_STORE_KEY
30
+ }
31
+
32
+ private async loadSecureStore(): Promise<typeof import('expo-secure-store') | null> {
33
+ try {
34
+ return await import('expo-secure-store')
35
+ } catch {
36
+ warnOnce('moviie:expo-secure-store-missing', SECURE_STORE_MISSING_WARNING)
37
+ return null
38
+ }
39
+ }
40
+
41
+ async get(): Promise<string | null> {
42
+ const SecureStore = await this.loadSecureStore()
43
+ if (!SecureStore) {
44
+ return this.fallback.get()
45
+ }
46
+ try {
47
+ const raw = await SecureStore.getItemAsync(this.storageKey)
48
+ if (!raw) return null
49
+ const parsed: unknown = JSON.parse(raw)
50
+ if (!isStoredEnvelope(parsed)) {
51
+ await SecureStore.deleteItemAsync(this.storageKey).catch(() => {})
52
+ return null
53
+ }
54
+ if (parsed.expiresAt <= Date.now()) {
55
+ await SecureStore.deleteItemAsync(this.storageKey).catch(() => {})
56
+ return null
57
+ }
58
+ return parsed.token
59
+ } catch {
60
+ return this.fallback.get()
61
+ }
62
+ }
63
+
64
+ async set(token: string, ttlSec: number): Promise<void> {
65
+ const SecureStore = await this.loadSecureStore()
66
+ const envelope: StoredEnvelope = {
67
+ token,
68
+ expiresAt: Date.now() + Math.max(0, ttlSec) * 1000,
69
+ }
70
+ if (!SecureStore) {
71
+ await this.fallback.set(token, ttlSec)
72
+ return
73
+ }
74
+ try {
75
+ await SecureStore.setItemAsync(this.storageKey, JSON.stringify(envelope))
76
+ } catch {
77
+ await this.fallback.set(token, ttlSec)
78
+ }
79
+ }
80
+ }