@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
package/src/index.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import './apply-expo-moviie-endpoints'
|
|
2
|
+
export * from '@moviie/player-sdk'
|
|
3
|
+
|
|
4
|
+
export {
|
|
5
|
+
MOVIIE_PLAYER_EXPO_PKG_VERSION,
|
|
6
|
+
MOVIIE_PLAYBACK_MEMORY_CACHE_TTL_MS,
|
|
7
|
+
MOVIIE_PLAYBACK_PROFILE,
|
|
8
|
+
MOVIIE_VIDEO_DEFAULT_CONTENT_FIT,
|
|
9
|
+
MOVIIE_SKIN_CONTROLS_AUTO_HIDE_MS,
|
|
10
|
+
MOVIIE_SKIN_CONTROL_ICON_PX,
|
|
11
|
+
MOVIIE_SKIN_DEFAULT_ACCENT_HEX,
|
|
12
|
+
MOVIIE_SKIN_VOLUME_LOW_ICON_THRESHOLD,
|
|
13
|
+
MOVIIE_SKIN_TIMELINE_END_SNAP_EPSILON_SECONDS,
|
|
14
|
+
MOVIIE_REMEMBER_POSITION_DEBOUNCE_MS,
|
|
15
|
+
MOVIIE_REMEMBER_POSITION_STORAGE_PREFIX,
|
|
16
|
+
EXPO_PUBLIC_MOVIIE_API_BASE_URL_ENV,
|
|
17
|
+
EXPO_PUBLIC_MOVIIE_EVENTS_BASE_URL_ENV,
|
|
18
|
+
MOVIIE_WATCH_ORIGIN_DEFAULT,
|
|
19
|
+
MOVIIE_WATCH_EMBED_JS_PATH,
|
|
20
|
+
MOVIIE_PLAYER_EXPO_EMBED_JS_MARKER_ATTR,
|
|
21
|
+
EXPO_PUBLIC_MOVIIE_WATCH_ORIGIN_ENV,
|
|
22
|
+
} from './constants'
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
MOVIIE_VIEWER_TOKEN_SECURE_STORE_KEY,
|
|
26
|
+
SecureStoreViewerTokenStore,
|
|
27
|
+
} from './secure-store-viewer-token-store'
|
|
28
|
+
|
|
29
|
+
export { MoviieProvider, useMoviieContext } from './provider/moviie-provider'
|
|
30
|
+
export type { MoviieContextValue } from './provider/moviie-provider'
|
|
31
|
+
|
|
32
|
+
export { useMoviiePlayback } from './hooks/use-moviie-playback'
|
|
33
|
+
export type { UseMoviiePlaybackArgs, UseMoviiePlaybackResult } from './hooks/use-moviie-playback'
|
|
34
|
+
|
|
35
|
+
export { useMoviiePlayer } from './hooks/use-moviie-player'
|
|
36
|
+
export type { UseMoviiePlayerOptions, UseMoviiePlayerResult } from './hooks/use-moviie-player-types'
|
|
37
|
+
|
|
38
|
+
export {
|
|
39
|
+
buildMoviieVideoPresentationProps,
|
|
40
|
+
type MoviieVideoPresentationProps,
|
|
41
|
+
} from './lib/build-moviie-video-presentation'
|
|
42
|
+
|
|
43
|
+
export { useMoviieEvent } from './hooks/use-moviie-event'
|
|
44
|
+
|
|
45
|
+
export { useMoviiePlaybackEnded } from './hooks/use-moviie-playback-ended'
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
computeMoviiePlaybackEnded,
|
|
49
|
+
type MoviiePlaybackEndedUiArgs,
|
|
50
|
+
} from './lib/compute-moviie-playback-ended'
|
|
51
|
+
|
|
52
|
+
export {
|
|
53
|
+
addMoviiePlaybackEndedListener,
|
|
54
|
+
type MoviiePlaybackEndedSubscription,
|
|
55
|
+
} from './lib/add-moviie-playback-ended-listener'
|
|
56
|
+
|
|
57
|
+
export {
|
|
58
|
+
buildMoviieWatchEmbedUrl,
|
|
59
|
+
normalizeWatchOrigin,
|
|
60
|
+
resolveMoviieWatchOrigin,
|
|
61
|
+
} from './lib/build-moviie-watch-embed-url'
|
|
62
|
+
|
|
63
|
+
export type {
|
|
64
|
+
MoviieCastAdapter,
|
|
65
|
+
MoviieCastMediaPayload,
|
|
66
|
+
MoviieCastState,
|
|
67
|
+
} from './lib/moviie-cast-adapter'
|
|
68
|
+
|
|
69
|
+
export { MoviieVideo } from './components/moviie-video'
|
|
70
|
+
export type {
|
|
71
|
+
MoviieVideoProps,
|
|
72
|
+
MoviieWatchEmbedIframeHandle,
|
|
73
|
+
MoviieWatchEmbedIframeRefCallback,
|
|
74
|
+
} from './components/moviie-video-props'
|
|
75
|
+
|
|
76
|
+
export { MoviieErrorBoundary } from './components/moviie-error-boundary'
|
|
77
|
+
export type { MoviieErrorBoundaryProps } from './components/moviie-error-boundary'
|
|
78
|
+
|
|
79
|
+
export type {
|
|
80
|
+
MoviieTelemetryErrorContext,
|
|
81
|
+
MoviieTelemetryErrorPhase,
|
|
82
|
+
MoviieTelemetryLifecycleEvent,
|
|
83
|
+
MoviieTelemetryLifecycleEventName,
|
|
84
|
+
MoviieTelemetryOnError,
|
|
85
|
+
MoviieTelemetryOnEvent,
|
|
86
|
+
} from './lib/moviie-telemetry-callbacks'
|
|
87
|
+
|
|
88
|
+
export type { MoviieVideoSurfaceRef } from './components/controls/moviie-controls'
|
|
89
|
+
|
|
90
|
+
export { PLAYER_API_EVENTS, PLAYER_API_METHODS, PLAYER_API_PROPERTIES } from '@moviie/player-types'
|
package/src/layout.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { VideoPlayer } from "expo-video"
|
|
2
|
+
|
|
3
|
+
export type MoviiePlaybackEndedSubscription = {
|
|
4
|
+
remove: () => void
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Imperative listener for expo-video `playToEnd`. Prefer {@link useMoviiePlaybackEnded} in React components.
|
|
9
|
+
*/
|
|
10
|
+
export function addMoviiePlaybackEndedListener(
|
|
11
|
+
player: VideoPlayer,
|
|
12
|
+
onEnded: () => void
|
|
13
|
+
): MoviiePlaybackEndedSubscription {
|
|
14
|
+
const sub = player.addListener("playToEnd", onEnded)
|
|
15
|
+
return { remove: () => sub.remove() }
|
|
16
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MOVIIE_BRANDING_MARKETING_ORIGIN,
|
|
3
|
+
MOVIIE_BRANDING_UTM_MEDIUM_VALUE,
|
|
4
|
+
MOVIIE_BRANDING_UTM_PARAM_MEDIUM,
|
|
5
|
+
MOVIIE_BRANDING_UTM_PARAM_SOURCE,
|
|
6
|
+
MOVIIE_BRANDING_UTM_SOURCE_VALUE,
|
|
7
|
+
} from "../constants"
|
|
8
|
+
|
|
9
|
+
export function buildMoviieBrandingMarketingUrl(): string {
|
|
10
|
+
const url = new URL(MOVIIE_BRANDING_MARKETING_ORIGIN)
|
|
11
|
+
url.searchParams.set(MOVIIE_BRANDING_UTM_PARAM_SOURCE, MOVIIE_BRANDING_UTM_SOURCE_VALUE)
|
|
12
|
+
url.searchParams.set(MOVIIE_BRANDING_UTM_PARAM_MEDIUM, MOVIIE_BRANDING_UTM_MEDIUM_VALUE)
|
|
13
|
+
return url.toString()
|
|
14
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MOVIIE_BRANDING_MARKETING_ORIGIN,
|
|
3
|
+
MOVIIE_BRANDING_UTM_PARAM_CAMPAIGN,
|
|
4
|
+
MOVIIE_BRANDING_UTM_PARAM_MEDIUM,
|
|
5
|
+
MOVIIE_BRANDING_UTM_PARAM_SOURCE,
|
|
6
|
+
MOVIIE_EMBED_BRAND_UTM,
|
|
7
|
+
} from "../constants"
|
|
8
|
+
|
|
9
|
+
/** Espelha `buildPlayerBrandHomeHref` do embed web (`apps/web/src/app/embed/[id]/utils.ts`). */
|
|
10
|
+
export function buildMoviieEmbedBrandHomeUrl(): string {
|
|
11
|
+
const url = new URL(`${MOVIIE_BRANDING_MARKETING_ORIGIN}/`)
|
|
12
|
+
url.searchParams.set(MOVIIE_BRANDING_UTM_PARAM_SOURCE, MOVIIE_EMBED_BRAND_UTM.SOURCE)
|
|
13
|
+
url.searchParams.set(MOVIIE_BRANDING_UTM_PARAM_MEDIUM, MOVIIE_EMBED_BRAND_UTM.MEDIUM)
|
|
14
|
+
url.searchParams.set(MOVIIE_BRANDING_UTM_PARAM_CAMPAIGN, MOVIIE_EMBED_BRAND_UTM.CAMPAIGN)
|
|
15
|
+
return url.toString()
|
|
16
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MOVIIE_SKIN_BRAND_MARK_ARTBOARD_HEIGHT_PX,
|
|
3
|
+
MOVIIE_SKIN_BRAND_MARK_ARTBOARD_WIDTH_PX,
|
|
4
|
+
MOVIIE_SKIN_BRAND_MARK_TARGET_WIDTH_AT_REFERENCE_PT,
|
|
5
|
+
MOVIIE_SKIN_BUFFERING_INDICATOR_SLOT_PX,
|
|
6
|
+
MOVIIE_SKIN_CENTER_CLUSTER_COLUMN_GAP_PX,
|
|
7
|
+
MOVIIE_SKIN_CENTER_PLAY_ICON_PX,
|
|
8
|
+
MOVIIE_SKIN_CENTER_PLAY_SCRIM_DIAMETER_AT_REFERENCE_PT,
|
|
9
|
+
MOVIIE_SKIN_CUSTOM_FULLSCREEN_HUD_EDGE_BOOST_REFERENCE_PT,
|
|
10
|
+
MOVIIE_SKIN_CHROME_EDGE_GRADIENT_HEIGHT_AT_REFERENCE_PT,
|
|
11
|
+
MOVIIE_SKIN_CLOCK_TEXT_FONT_SIZE_AT_REFERENCE_PT,
|
|
12
|
+
MOVIIE_SKIN_CONTROL_ICON_PX,
|
|
13
|
+
MOVIIE_SKIN_FLOATING_FULLSCREEN_ICON_AT_REFERENCE_PT,
|
|
14
|
+
MOVIIE_SKIN_FLOATING_FULLSCREEN_SCRIM_DIAMETER_AT_REFERENCE_PT,
|
|
15
|
+
MOVIIE_SKIN_FLOATING_FULLSCREEN_TOP_ICON_AT_REFERENCE_PT,
|
|
16
|
+
MOVIIE_SKIN_FLOATING_HUD_BUTTON_GAP_AT_REFERENCE_PT,
|
|
17
|
+
MOVIIE_SKIN_FLOATING_HUD_CLOCK_CORNER_RADIUS_PX,
|
|
18
|
+
MOVIIE_SKIN_FLOATING_HUD_EDGE_INSET_PX,
|
|
19
|
+
MOVIIE_SKIN_FLOATING_HUD_ABOVE_TIMELINE_GAP_AT_REFERENCE_PT,
|
|
20
|
+
MOVIIE_SKIN_SEEK_ICON_PX,
|
|
21
|
+
MOVIIE_SKIN_CENTER_SEEK_SCRIM_DIAMETER_AT_REFERENCE_PT,
|
|
22
|
+
MOVIIE_SKIN_TIMELINE_BAR_HEIGHT_PX,
|
|
23
|
+
MOVIIE_SKIN_TIMELINE_THUMB_DIAMETER_PX,
|
|
24
|
+
MOVIIE_SKIN_TIMELINE_TOUCH_PADDING_VERTICAL_PX,
|
|
25
|
+
} from '../constants'
|
|
26
|
+
|
|
27
|
+
import { resolveMoviieSkinLayoutScale } from './resolve-moviie-skin-layout-scale'
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Métricas em unidades lógicas (o mesmo tipo de número que `StyleSheet`: dp/pt por plataforma).
|
|
31
|
+
* O React Native já aplica a densidade do ecrã — evitar arredondamento manual com PixelRatio aqui.
|
|
32
|
+
* O único escalonamento extra é {@link resolveMoviieSkinLayoutScale} pela largura da janela (layout responsivo).
|
|
33
|
+
*/
|
|
34
|
+
export type MoviieSkinLayoutMetrics = {
|
|
35
|
+
readonly layoutScale: number
|
|
36
|
+
readonly brandMarkWidthPx: number
|
|
37
|
+
readonly brandMarkHeightPx: number
|
|
38
|
+
readonly hudEdgeInsetPx: number
|
|
39
|
+
readonly floatingHudAboveTimelineGapPx: number
|
|
40
|
+
readonly hudClockCornerRadiusPx: number
|
|
41
|
+
readonly controlIconPx: number
|
|
42
|
+
readonly seekIconPx: number
|
|
43
|
+
readonly seekScrimOuterDiameterPx: number
|
|
44
|
+
readonly centerPlayIconPx: number
|
|
45
|
+
readonly centerClusterColumnGapPx: number
|
|
46
|
+
readonly playScrimOuterDiameterPx: number
|
|
47
|
+
readonly clockTextFontSizePx: number
|
|
48
|
+
readonly timelineThumbDiameterPx: number
|
|
49
|
+
readonly timelineTouchPaddingVerticalPx: number
|
|
50
|
+
readonly timelineBarHeightPx: number
|
|
51
|
+
readonly timelineThumbBottomInsetPx: number
|
|
52
|
+
readonly timelineStackHeightChromeVisiblePx: number
|
|
53
|
+
readonly bufferingIndicatorSlotPx: number
|
|
54
|
+
readonly chromeEdgeGradientHeightPx: number
|
|
55
|
+
readonly floatingHudFullscreenScrimDiameterPx: number
|
|
56
|
+
readonly floatingHudFullscreenIconPx: number
|
|
57
|
+
readonly floatingHudFullscreenTopIconPx: number
|
|
58
|
+
readonly floatingHudButtonGapPx: number
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function scaleSkinLayoutDp(layoutScale: number, baseAtReference: number): number {
|
|
62
|
+
return baseAtReference * layoutScale
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type BuildMoviieSkinLayoutMetricsOptions = {
|
|
66
|
+
/** Fullscreen JS rotacionado: inset extra no HUD/timeline para não encostar à zona segura. */
|
|
67
|
+
fullscreenHudProtection?: boolean
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function buildMoviieSkinLayoutMetrics(
|
|
71
|
+
windowWidth: number,
|
|
72
|
+
options?: BuildMoviieSkinLayoutMetricsOptions
|
|
73
|
+
): MoviieSkinLayoutMetrics {
|
|
74
|
+
const layoutScale = resolveMoviieSkinLayoutScale(windowWidth)
|
|
75
|
+
|
|
76
|
+
const brandMarkWidthPx = scaleSkinLayoutDp(
|
|
77
|
+
layoutScale,
|
|
78
|
+
MOVIIE_SKIN_BRAND_MARK_TARGET_WIDTH_AT_REFERENCE_PT
|
|
79
|
+
)
|
|
80
|
+
const brandMarkHeightPx =
|
|
81
|
+
brandMarkWidthPx *
|
|
82
|
+
(MOVIIE_SKIN_BRAND_MARK_ARTBOARD_HEIGHT_PX / MOVIIE_SKIN_BRAND_MARK_ARTBOARD_WIDTH_PX)
|
|
83
|
+
|
|
84
|
+
const timelineThumbDiameterPx = scaleSkinLayoutDp(
|
|
85
|
+
layoutScale,
|
|
86
|
+
MOVIIE_SKIN_TIMELINE_THUMB_DIAMETER_PX
|
|
87
|
+
)
|
|
88
|
+
const timelineBarHeightPx = scaleSkinLayoutDp(layoutScale, MOVIIE_SKIN_TIMELINE_BAR_HEIGHT_PX)
|
|
89
|
+
const timelineThumbBottomInsetPx = timelineBarHeightPx / 2 - timelineThumbDiameterPx / 2
|
|
90
|
+
|
|
91
|
+
const timelineTouchPaddingVerticalPx = scaleSkinLayoutDp(
|
|
92
|
+
layoutScale,
|
|
93
|
+
MOVIIE_SKIN_TIMELINE_TOUCH_PADDING_VERTICAL_PX
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
const fullscreenHudBoostPx =
|
|
97
|
+
options?.fullscreenHudProtection === true
|
|
98
|
+
? scaleSkinLayoutDp(layoutScale, MOVIIE_SKIN_CUSTOM_FULLSCREEN_HUD_EDGE_BOOST_REFERENCE_PT)
|
|
99
|
+
: 0
|
|
100
|
+
|
|
101
|
+
const hudEdgeInsetPx =
|
|
102
|
+
scaleSkinLayoutDp(layoutScale, MOVIIE_SKIN_FLOATING_HUD_EDGE_INSET_PX) + fullscreenHudBoostPx
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
layoutScale,
|
|
106
|
+
brandMarkWidthPx,
|
|
107
|
+
brandMarkHeightPx,
|
|
108
|
+
hudEdgeInsetPx,
|
|
109
|
+
floatingHudAboveTimelineGapPx: scaleSkinLayoutDp(
|
|
110
|
+
layoutScale,
|
|
111
|
+
MOVIIE_SKIN_FLOATING_HUD_ABOVE_TIMELINE_GAP_AT_REFERENCE_PT
|
|
112
|
+
),
|
|
113
|
+
hudClockCornerRadiusPx: scaleSkinLayoutDp(
|
|
114
|
+
layoutScale,
|
|
115
|
+
MOVIIE_SKIN_FLOATING_HUD_CLOCK_CORNER_RADIUS_PX
|
|
116
|
+
),
|
|
117
|
+
controlIconPx: scaleSkinLayoutDp(layoutScale, MOVIIE_SKIN_CONTROL_ICON_PX),
|
|
118
|
+
seekIconPx: scaleSkinLayoutDp(layoutScale, MOVIIE_SKIN_SEEK_ICON_PX),
|
|
119
|
+
seekScrimOuterDiameterPx: scaleSkinLayoutDp(
|
|
120
|
+
layoutScale,
|
|
121
|
+
MOVIIE_SKIN_CENTER_SEEK_SCRIM_DIAMETER_AT_REFERENCE_PT
|
|
122
|
+
),
|
|
123
|
+
centerPlayIconPx: scaleSkinLayoutDp(layoutScale, MOVIIE_SKIN_CENTER_PLAY_ICON_PX),
|
|
124
|
+
centerClusterColumnGapPx: scaleSkinLayoutDp(
|
|
125
|
+
layoutScale,
|
|
126
|
+
MOVIIE_SKIN_CENTER_CLUSTER_COLUMN_GAP_PX
|
|
127
|
+
),
|
|
128
|
+
playScrimOuterDiameterPx: scaleSkinLayoutDp(
|
|
129
|
+
layoutScale,
|
|
130
|
+
MOVIIE_SKIN_CENTER_PLAY_SCRIM_DIAMETER_AT_REFERENCE_PT
|
|
131
|
+
),
|
|
132
|
+
clockTextFontSizePx: scaleSkinLayoutDp(
|
|
133
|
+
layoutScale,
|
|
134
|
+
MOVIIE_SKIN_CLOCK_TEXT_FONT_SIZE_AT_REFERENCE_PT
|
|
135
|
+
),
|
|
136
|
+
timelineThumbDiameterPx,
|
|
137
|
+
timelineTouchPaddingVerticalPx,
|
|
138
|
+
timelineBarHeightPx,
|
|
139
|
+
timelineThumbBottomInsetPx,
|
|
140
|
+
timelineStackHeightChromeVisiblePx: timelineTouchPaddingVerticalPx + timelineThumbDiameterPx,
|
|
141
|
+
bufferingIndicatorSlotPx: scaleSkinLayoutDp(
|
|
142
|
+
layoutScale,
|
|
143
|
+
MOVIIE_SKIN_BUFFERING_INDICATOR_SLOT_PX
|
|
144
|
+
),
|
|
145
|
+
chromeEdgeGradientHeightPx: scaleSkinLayoutDp(
|
|
146
|
+
layoutScale,
|
|
147
|
+
MOVIIE_SKIN_CHROME_EDGE_GRADIENT_HEIGHT_AT_REFERENCE_PT
|
|
148
|
+
),
|
|
149
|
+
floatingHudFullscreenScrimDiameterPx: scaleSkinLayoutDp(
|
|
150
|
+
layoutScale,
|
|
151
|
+
MOVIIE_SKIN_FLOATING_FULLSCREEN_SCRIM_DIAMETER_AT_REFERENCE_PT
|
|
152
|
+
),
|
|
153
|
+
floatingHudFullscreenIconPx: scaleSkinLayoutDp(
|
|
154
|
+
layoutScale,
|
|
155
|
+
MOVIIE_SKIN_FLOATING_FULLSCREEN_ICON_AT_REFERENCE_PT
|
|
156
|
+
),
|
|
157
|
+
floatingHudFullscreenTopIconPx: scaleSkinLayoutDp(
|
|
158
|
+
layoutScale,
|
|
159
|
+
MOVIIE_SKIN_FLOATING_FULLSCREEN_TOP_ICON_AT_REFERENCE_PT
|
|
160
|
+
),
|
|
161
|
+
floatingHudButtonGapPx: scaleSkinLayoutDp(
|
|
162
|
+
layoutScale,
|
|
163
|
+
MOVIIE_SKIN_FLOATING_HUD_BUTTON_GAP_AT_REFERENCE_PT
|
|
164
|
+
),
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { MOVIIE_PLAYBACK_PROFILE } from "../constants"
|
|
2
|
+
|
|
3
|
+
export type MoviieVideoPresentationProps = {
|
|
4
|
+
allowsPictureInPicture: boolean
|
|
5
|
+
startsPictureInPictureAutomatically: boolean
|
|
6
|
+
requiresLinearPlayback: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface BuildMoviieVideoPresentationArgs {
|
|
10
|
+
profile: string
|
|
11
|
+
showPip: boolean
|
|
12
|
+
/**
|
|
13
|
+
* Integrator override: when set, replaces embed `showPip` for `allowsPictureInPicture`.
|
|
14
|
+
* When omitted, `showPip` is used.
|
|
15
|
+
*/
|
|
16
|
+
pictureInPicture?: boolean
|
|
17
|
+
/**
|
|
18
|
+
* Integrator override for `startsPictureInPictureAutomatically`.
|
|
19
|
+
* When omitted, defaults to `false` (no embed field today).
|
|
20
|
+
*/
|
|
21
|
+
pictureInPictureAutoStart?: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Maps embed playback flags onto `VideoView` presentation props.
|
|
26
|
+
* Optional fields let integrators override embed defaults explicitly; omit them to follow the API.
|
|
27
|
+
*/
|
|
28
|
+
export function buildMoviieVideoPresentationProps(
|
|
29
|
+
args: BuildMoviieVideoPresentationArgs
|
|
30
|
+
): MoviieVideoPresentationProps {
|
|
31
|
+
const allowsPictureInPicture =
|
|
32
|
+
args.pictureInPicture !== undefined
|
|
33
|
+
? args.pictureInPicture
|
|
34
|
+
: args.showPip
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
allowsPictureInPicture,
|
|
38
|
+
startsPictureInPictureAutomatically:
|
|
39
|
+
args.pictureInPictureAutoStart ?? false,
|
|
40
|
+
requiresLinearPlayback: args.profile === MOVIIE_PLAYBACK_PROFILE.VSL,
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { MOVIIE_WATCH_ORIGIN_DEFAULT } from "../constants"
|
|
2
|
+
|
|
3
|
+
export function normalizeWatchOrigin(origin: string): string {
|
|
4
|
+
return origin.trim().replace(/\/+$/, "")
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function buildMoviieWatchEmbedUrl(embedId: string, watchOrigin: string): string {
|
|
8
|
+
const base = normalizeWatchOrigin(watchOrigin)
|
|
9
|
+
const id = embedId.trim()
|
|
10
|
+
return `${base}/embed/${encodeURIComponent(id)}`
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolve o origin público da página watch/embed.
|
|
15
|
+
* Usa `process.env.EXPO_PUBLIC_MOVIIE_WATCH_ORIGIN` quando definido (nome documentado como `EXPO_PUBLIC_MOVIIE_WATCH_ORIGIN_ENV` em `@moviie/player-expo`).
|
|
16
|
+
*/
|
|
17
|
+
export function resolveMoviieWatchOrigin(): string {
|
|
18
|
+
try {
|
|
19
|
+
const raw =
|
|
20
|
+
typeof process !== "undefined"
|
|
21
|
+
? process.env.EXPO_PUBLIC_MOVIIE_WATCH_ORIGIN?.trim()
|
|
22
|
+
: undefined
|
|
23
|
+
if (raw) {
|
|
24
|
+
return normalizeWatchOrigin(raw)
|
|
25
|
+
}
|
|
26
|
+
} catch {
|
|
27
|
+
/* ignore */
|
|
28
|
+
}
|
|
29
|
+
return MOVIIE_WATCH_ORIGIN_DEFAULT
|
|
30
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { MoviieCastAdapter } from './moviie-cast-adapter'
|
|
2
|
+
|
|
3
|
+
let _registered: MoviieCastAdapter | null = null
|
|
4
|
+
|
|
5
|
+
/** Called automatically when `@moviie/player-expo/cast` is imported. */
|
|
6
|
+
export function _registerCastAdapter(adapter: MoviieCastAdapter): void {
|
|
7
|
+
_registered = adapter
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Returns the auto-registered adapter, or null if cast subpath was never imported. */
|
|
11
|
+
export function getRegisteredCastAdapter(): MoviieCastAdapter | null {
|
|
12
|
+
return _registered
|
|
13
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { EdgeInsets } from "react-native-safe-area-context"
|
|
2
|
+
|
|
3
|
+
export type CustomFullscreenStageDimensions = {
|
|
4
|
+
longSide: number
|
|
5
|
+
shortSide: number
|
|
6
|
+
layoutMetricsBasisWidth: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function computeCustomFullscreenStageDimensions(args: {
|
|
10
|
+
windowWidth: number
|
|
11
|
+
windowHeight: number
|
|
12
|
+
safeInsets: EdgeInsets
|
|
13
|
+
stageOverscanPx: number
|
|
14
|
+
}): CustomFullscreenStageDimensions {
|
|
15
|
+
const pad = args.stageOverscanPx
|
|
16
|
+
const usableW = Math.max(
|
|
17
|
+
0,
|
|
18
|
+
args.windowWidth -
|
|
19
|
+
args.safeInsets.left -
|
|
20
|
+
args.safeInsets.right -
|
|
21
|
+
2 * pad
|
|
22
|
+
)
|
|
23
|
+
const usableH = Math.max(
|
|
24
|
+
0,
|
|
25
|
+
args.windowHeight -
|
|
26
|
+
args.safeInsets.top -
|
|
27
|
+
args.safeInsets.bottom -
|
|
28
|
+
2 * pad
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
const longSide = Math.max(usableW, usableH)
|
|
32
|
+
const shortSide = Math.min(usableW, usableH)
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
longSide,
|
|
36
|
+
shortSide,
|
|
37
|
+
layoutMetricsBasisWidth: longSide,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { MOVIIE_SKIN_TIMELINE_END_SNAP_EPSILON_SECONDS } from "../constants"
|
|
2
|
+
|
|
3
|
+
export type MoviiePlaybackEndedUiArgs = {
|
|
4
|
+
playing: boolean
|
|
5
|
+
currentTime: number
|
|
6
|
+
duration: number
|
|
7
|
+
isLive: boolean
|
|
8
|
+
loop: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function computeMoviiePlaybackEnded(args: MoviiePlaybackEndedUiArgs): boolean {
|
|
12
|
+
if (args.loop) {
|
|
13
|
+
return false
|
|
14
|
+
}
|
|
15
|
+
if (args.isLive) {
|
|
16
|
+
return false
|
|
17
|
+
}
|
|
18
|
+
if (args.playing) {
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
if (!Number.isFinite(args.duration) || args.duration <= 0) {
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
if (!Number.isFinite(args.currentTime)) {
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
return (
|
|
28
|
+
args.currentTime >=
|
|
29
|
+
args.duration - MOVIIE_SKIN_TIMELINE_END_SNAP_EPSILON_SECONDS
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { MOVIIE_SKIN_BUFFER_UNDERRUN_LEAD_SECONDS } from "../constants"
|
|
2
|
+
import { computeMoviiePlaybackEnded } from "./compute-moviie-playback-ended"
|
|
3
|
+
|
|
4
|
+
const PLAYER_STATUS_LOADING = "loading" as const
|
|
5
|
+
|
|
6
|
+
export function computePlaybackBuffering(state: {
|
|
7
|
+
bufferedPosition: number
|
|
8
|
+
currentTime: number
|
|
9
|
+
duration: number
|
|
10
|
+
playing: boolean
|
|
11
|
+
status: string
|
|
12
|
+
isLive: boolean
|
|
13
|
+
loop: boolean
|
|
14
|
+
}): boolean {
|
|
15
|
+
if (
|
|
16
|
+
computeMoviiePlaybackEnded({
|
|
17
|
+
playing: state.playing,
|
|
18
|
+
currentTime: state.currentTime,
|
|
19
|
+
duration: state.duration,
|
|
20
|
+
isLive: state.isLive,
|
|
21
|
+
loop: state.loop,
|
|
22
|
+
})
|
|
23
|
+
) {
|
|
24
|
+
return false
|
|
25
|
+
}
|
|
26
|
+
if (state.status === PLAYER_STATUS_LOADING) {
|
|
27
|
+
return true
|
|
28
|
+
}
|
|
29
|
+
if (
|
|
30
|
+
state.playing &&
|
|
31
|
+
Number.isFinite(state.duration) &&
|
|
32
|
+
state.duration > 0 &&
|
|
33
|
+
state.bufferedPosition >= 0 &&
|
|
34
|
+
state.currentTime > 0.08 &&
|
|
35
|
+
state.bufferedPosition <
|
|
36
|
+
state.currentTime + MOVIIE_SKIN_BUFFER_UNDERRUN_LEAD_SECONDS
|
|
37
|
+
) {
|
|
38
|
+
return true
|
|
39
|
+
}
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { MOVIIE_SKIN_TIMELINE_END_SNAP_EPSILON_SECONDS } from "../constants"
|
|
2
|
+
|
|
3
|
+
import { clampUnitInterval } from "./clamp-unit-interval"
|
|
4
|
+
|
|
5
|
+
export function computePlaybackProgressRatio(state: {
|
|
6
|
+
durationOk: boolean
|
|
7
|
+
currentTime: number
|
|
8
|
+
duration: number
|
|
9
|
+
playing: boolean
|
|
10
|
+
}): number {
|
|
11
|
+
if (
|
|
12
|
+
!state.durationOk ||
|
|
13
|
+
!Number.isFinite(state.currentTime) ||
|
|
14
|
+
!Number.isFinite(state.duration) ||
|
|
15
|
+
state.duration <= 0
|
|
16
|
+
) {
|
|
17
|
+
return 0
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const raw = state.currentTime / state.duration
|
|
21
|
+
if (raw >= 1) {
|
|
22
|
+
return 1
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const snapToEndWhilePaused =
|
|
26
|
+
!state.playing &&
|
|
27
|
+
state.currentTime >=
|
|
28
|
+
state.duration - MOVIIE_SKIN_TIMELINE_END_SNAP_EPSILON_SECONDS
|
|
29
|
+
|
|
30
|
+
const ratio = snapToEndWhilePaused ? 1 : raw
|
|
31
|
+
|
|
32
|
+
return clampUnitInterval(ratio)
|
|
33
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { MOVIIE_SKIN_TIMELINE_CHAPTER_SNAP_THRESHOLD_SECONDS } from "../constants"
|
|
2
|
+
|
|
3
|
+
export type TimelineChapterBoundary = {
|
|
4
|
+
readonly startTimeSeconds: number
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Converte um `ratio` [0–1] de scrub em segundos absolutos, com snap automático
|
|
9
|
+
* ao boundary de capítulo mais próximo dentro do threshold configurado.
|
|
10
|
+
*/
|
|
11
|
+
export function computeTimelineScrubCommitTime(params: {
|
|
12
|
+
ratio: number
|
|
13
|
+
durationSeconds: number
|
|
14
|
+
chapters: ReadonlyArray<TimelineChapterBoundary>
|
|
15
|
+
snapThresholdSeconds?: number
|
|
16
|
+
}): number {
|
|
17
|
+
const {
|
|
18
|
+
ratio,
|
|
19
|
+
durationSeconds,
|
|
20
|
+
chapters,
|
|
21
|
+
snapThresholdSeconds = MOVIIE_SKIN_TIMELINE_CHAPTER_SNAP_THRESHOLD_SECONDS,
|
|
22
|
+
} = params
|
|
23
|
+
|
|
24
|
+
const rawTime = ratio * durationSeconds
|
|
25
|
+
|
|
26
|
+
let nearest: number | null = null
|
|
27
|
+
let nearestDist = Infinity
|
|
28
|
+
|
|
29
|
+
for (const chapter of chapters) {
|
|
30
|
+
const dist = Math.abs(chapter.startTimeSeconds - rawTime)
|
|
31
|
+
if (dist < nearestDist) {
|
|
32
|
+
nearestDist = dist
|
|
33
|
+
nearest = chapter.startTimeSeconds
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return nearest !== null && nearestDist <= snapThresholdSeconds
|
|
38
|
+
? nearest
|
|
39
|
+
: rawTime
|
|
40
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OrientationLock,
|
|
3
|
+
getOrientationLockAsync,
|
|
4
|
+
lockAsync,
|
|
5
|
+
unlockAsync,
|
|
6
|
+
} from "expo-screen-orientation"
|
|
7
|
+
import { Platform } from "react-native"
|
|
8
|
+
|
|
9
|
+
import type { MoviieExpoOrientationLockValue } from "../constants"
|
|
10
|
+
|
|
11
|
+
async function restoreOrientationLockPolicy(
|
|
12
|
+
previousLock: OrientationLock | undefined
|
|
13
|
+
): Promise<void> {
|
|
14
|
+
try {
|
|
15
|
+
if (
|
|
16
|
+
previousLock === undefined ||
|
|
17
|
+
previousLock === OrientationLock.DEFAULT ||
|
|
18
|
+
previousLock === OrientationLock.UNKNOWN
|
|
19
|
+
) {
|
|
20
|
+
await unlockAsync()
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
await lockAsync(previousLock)
|
|
24
|
+
} catch {
|
|
25
|
+
try {
|
|
26
|
+
await unlockAsync()
|
|
27
|
+
} catch {
|
|
28
|
+
// noop
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type CustomFullscreenNativeOrientationSubscription = {
|
|
34
|
+
finish: () => Promise<void>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function subscribeCustomFullscreenNativeOrientation(
|
|
38
|
+
targetLock: MoviieExpoOrientationLockValue
|
|
39
|
+
): CustomFullscreenNativeOrientationSubscription {
|
|
40
|
+
if (Platform.OS === "web") {
|
|
41
|
+
return { finish: async () => {} }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let cancelled = false
|
|
45
|
+
let locked = false
|
|
46
|
+
let previousLock: OrientationLock | undefined
|
|
47
|
+
|
|
48
|
+
const setup = (async () => {
|
|
49
|
+
try {
|
|
50
|
+
previousLock = await getOrientationLockAsync()
|
|
51
|
+
if (cancelled) {
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await lockAsync(targetLock as OrientationLock)
|
|
56
|
+
if (cancelled) {
|
|
57
|
+
await restoreOrientationLockPolicy(previousLock)
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
locked = true
|
|
62
|
+
} catch {
|
|
63
|
+
// noop
|
|
64
|
+
}
|
|
65
|
+
})()
|
|
66
|
+
|
|
67
|
+
let finishPromise: Promise<void> | null = null
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
finish: () => {
|
|
71
|
+
if (finishPromise != null) {
|
|
72
|
+
return finishPromise
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
cancelled = true
|
|
76
|
+
|
|
77
|
+
finishPromise = (async () => {
|
|
78
|
+
await setup
|
|
79
|
+
if (locked) {
|
|
80
|
+
locked = false
|
|
81
|
+
await restoreOrientationLockPolicy(previousLock)
|
|
82
|
+
}
|
|
83
|
+
})()
|
|
84
|
+
|
|
85
|
+
return finishPromise
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
}
|