@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.
- package/LICENSE +21 -0
- package/README.md +230 -0
- package/app.plugin.cjs +3 -0
- package/dist/cast.cjs +100 -0
- package/dist/cast.cjs.map +1 -0
- package/dist/cast.d.cts +15 -0
- package/dist/cast.d.ts +15 -0
- package/dist/cast.mjs +92 -0
- package/dist/cast.mjs.map +1 -0
- package/dist/chunk-67DJ7NOB.mjs +294 -0
- package/dist/chunk-67DJ7NOB.mjs.map +1 -0
- package/dist/chunk-7U2LKIGU.mjs +12 -0
- package/dist/chunk-7U2LKIGU.mjs.map +1 -0
- package/dist/chunk-BJTO5JO5.mjs +10 -0
- package/dist/chunk-BJTO5JO5.mjs.map +1 -0
- package/dist/index.cjs +3934 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +450 -0
- package/dist/index.d.ts +450 -0
- package/dist/index.mjs +3571 -0
- package/dist/index.mjs.map +1 -0
- package/dist/layout.cjs +217 -0
- package/dist/layout.cjs.map +1 -0
- package/dist/layout.d.cts +20 -0
- package/dist/layout.d.ts +20 -0
- package/dist/layout.mjs +4 -0
- package/dist/layout.mjs.map +1 -0
- package/dist/moviie-cast-adapter-DmSU2u3j.d.cts +53 -0
- package/dist/moviie-cast-adapter-DmSU2u3j.d.ts +53 -0
- package/dist/plugin/with-moviie.cjs +88 -0
- package/dist/plugin/with-moviie.cjs.map +1 -0
- package/dist/plugin/with-moviie.d.cts +52 -0
- package/dist/plugin/with-moviie.d.ts +52 -0
- package/dist/plugin/with-moviie.mjs +76 -0
- package/dist/plugin/with-moviie.mjs.map +1 -0
- package/package.json +134 -0
- package/plugin/validate-options.ts +31 -0
- package/plugin/with-moviie-types.ts +21 -0
- package/plugin/with-moviie.ts +84 -0
- package/src/apply-expo-moviie-endpoints.ts +47 -0
- package/src/cast/google-cast-adapter.ts +111 -0
- package/src/cast/index.ts +12 -0
- package/src/components/controls/moviie-bottom-timeline.tsx +477 -0
- package/src/components/controls/moviie-cast-buttons.tsx +96 -0
- package/src/components/controls/moviie-chrome-edge-gradients.tsx +162 -0
- package/src/components/controls/moviie-controls.tsx +585 -0
- package/src/components/controls/moviie-skin-chrome-context.tsx +374 -0
- package/src/components/controls/moviie-skin-smart-progress.tsx +157 -0
- package/src/components/icons/embed-media-icons.tsx +282 -0
- package/src/components/icons/moviie-embed-brand-mark.tsx +58 -0
- package/src/components/moviie-error-boundary.tsx +80 -0
- package/src/components/moviie-player-error-shell.tsx +143 -0
- package/src/components/moviie-player-loading-shell.tsx +59 -0
- package/src/components/moviie-skin-custom-fullscreen-modal.tsx +232 -0
- package/src/components/moviie-video-props.ts +134 -0
- package/src/components/moviie-video.tsx +568 -0
- package/src/components/moviie-video.web.tsx +167 -0
- package/src/components/overlays/moviie-watermark.tsx +53 -0
- package/src/constants.ts +374 -0
- package/src/hooks/use-moviie-event.ts +103 -0
- package/src/hooks/use-moviie-playback-ended.ts +14 -0
- package/src/hooks/use-moviie-playback-resume-persistence.ts +76 -0
- package/src/hooks/use-moviie-playback.ts +99 -0
- package/src/hooks/use-moviie-player-types.ts +55 -0
- package/src/hooks/use-moviie-player.ts +236 -0
- package/src/hooks/use-moviie-player.web.ts +57 -0
- package/src/hooks/use-moviie-telemetry.ts +133 -0
- package/src/index.ts +90 -0
- package/src/layout.ts +4 -0
- package/src/lib/add-moviie-playback-ended-listener.ts +16 -0
- package/src/lib/build-moviie-branding-marketing-url.ts +14 -0
- package/src/lib/build-moviie-embed-brand-home-url.ts +16 -0
- package/src/lib/build-moviie-skin-layout-metrics.ts +166 -0
- package/src/lib/build-moviie-video-presentation.ts +42 -0
- package/src/lib/build-moviie-watch-embed-url.ts +30 -0
- package/src/lib/cast-adapter-registry.ts +13 -0
- package/src/lib/clamp-unit-interval.ts +3 -0
- package/src/lib/compute-custom-fullscreen-stage-dimensions.ts +39 -0
- package/src/lib/compute-moviie-playback-ended.ts +31 -0
- package/src/lib/compute-playback-buffering.ts +41 -0
- package/src/lib/compute-playback-progress-ratio.ts +33 -0
- package/src/lib/compute-timeline-scrub-commit-time.ts +40 -0
- package/src/lib/custom-fullscreen-native-orientation.ts +88 -0
- package/src/lib/format-playback-clock.ts +27 -0
- package/src/lib/jsx-native-bridge.ts +45 -0
- package/src/lib/map-smart-progress-display-ratio.ts +43 -0
- package/src/lib/moviie-cast-adapter.ts +53 -0
- package/src/lib/moviie-error-display.ts +149 -0
- package/src/lib/moviie-shell-tokens.ts +27 -0
- package/src/lib/moviie-telemetry-callbacks.ts +76 -0
- package/src/lib/optional-status-bar.ts +48 -0
- package/src/lib/partition-moviie-video-host-style.ts +54 -0
- package/src/lib/remember-playback-position-storage.ts +98 -0
- package/src/lib/resolve-moviie-skin-layout-scale.ts +17 -0
- package/src/lib/resolve-orientation-lock-for-rotate-z-deg.ts +21 -0
- package/src/lib/resolve-skin-accent-color.ts +10 -0
- package/src/lib/resolve-web-embed-iframe-src.ts +17 -0
- package/src/lib/warn-once.ts +23 -0
- package/src/platform/native-application-id.ts +10 -0
- package/src/platform/platform-client-info.ts +31 -0
- package/src/playback/fetch-playback-fresh.ts +27 -0
- package/src/playback/playback-memory-cache.ts +52 -0
- package/src/provider/moviie-provider.tsx +99 -0
- 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,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
|
+
}
|