react-native-video-provider 0.1.1

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 (141) hide show
  1. package/AuVideo.podspec +28 -0
  2. package/LICENSE +20 -0
  3. package/README.md +183 -0
  4. package/android/build.gradle +59 -0
  5. package/android/src/main/AndroidManifest.xml +2 -0
  6. package/android/src/main/java/com/auvideo/AuVideoModule.kt +259 -0
  7. package/android/src/main/java/com/auvideo/AuVideoPackage.kt +35 -0
  8. package/android/src/main/java/com/auvideo/AuVideoSurfaceView.kt +40 -0
  9. package/android/src/main/java/com/auvideo/AuVideoSurfaceViewManager.kt +38 -0
  10. package/android/src/main/java/com/auvideo/PlayerCore.kt +366 -0
  11. package/android/src/main/java/com/auvideo/SurfaceRegistry.kt +26 -0
  12. package/android/src/main/res/layout/au_video_player_view.xml +10 -0
  13. package/ios/AuVideo.h +10 -0
  14. package/ios/AuVideo.mm +232 -0
  15. package/ios/AuVideoOrientation.swift +59 -0
  16. package/ios/AuVideoPlayerCore.swift +415 -0
  17. package/ios/AuVideoSurfaceRegistry.swift +30 -0
  18. package/ios/AuVideoSurfaceView.h +15 -0
  19. package/ios/AuVideoSurfaceView.mm +63 -0
  20. package/lib/module/AuVideoSurfaceNativeComponent.ts +8 -0
  21. package/lib/module/NativeAuVideo.js +5 -0
  22. package/lib/module/NativeAuVideo.js.map +1 -0
  23. package/lib/module/components/FloatingPlayer.js +119 -0
  24. package/lib/module/components/FloatingPlayer.js.map +1 -0
  25. package/lib/module/components/FullscreenPlayer.js +59 -0
  26. package/lib/module/components/FullscreenPlayer.js.map +1 -0
  27. package/lib/module/components/GestureOverlay.js +56 -0
  28. package/lib/module/components/GestureOverlay.js.map +1 -0
  29. package/lib/module/components/MiniPlayer.js +89 -0
  30. package/lib/module/components/MiniPlayer.js.map +1 -0
  31. package/lib/module/components/VideoControls.js +191 -0
  32. package/lib/module/components/VideoControls.js.map +1 -0
  33. package/lib/module/components/VideoPlayer.js +65 -0
  34. package/lib/module/components/VideoPlayer.js.map +1 -0
  35. package/lib/module/components/VideoSurface.js +35 -0
  36. package/lib/module/components/VideoSurface.js.map +1 -0
  37. package/lib/module/core/VideoManager.js +436 -0
  38. package/lib/module/core/VideoManager.js.map +1 -0
  39. package/lib/module/hooks/useFullscreen.js +19 -0
  40. package/lib/module/hooks/useFullscreen.js.map +1 -0
  41. package/lib/module/hooks/usePiP.js +17 -0
  42. package/lib/module/hooks/usePiP.js.map +1 -0
  43. package/lib/module/hooks/usePlayback.js +21 -0
  44. package/lib/module/hooks/usePlayback.js.map +1 -0
  45. package/lib/module/hooks/useVideo.js +17 -0
  46. package/lib/module/hooks/useVideo.js.map +1 -0
  47. package/lib/module/hooks/useVideoEvents.js +26 -0
  48. package/lib/module/hooks/useVideoEvents.js.map +1 -0
  49. package/lib/module/index.js +19 -0
  50. package/lib/module/index.js.map +1 -0
  51. package/lib/module/package.json +1 -0
  52. package/lib/module/provider/VideoContext.js +14 -0
  53. package/lib/module/provider/VideoContext.js.map +1 -0
  54. package/lib/module/provider/VideoProvider.js +36 -0
  55. package/lib/module/provider/VideoProvider.js.map +1 -0
  56. package/lib/module/state/createVideoStore.js +33 -0
  57. package/lib/module/state/createVideoStore.js.map +1 -0
  58. package/lib/module/types/events.js +4 -0
  59. package/lib/module/types/events.js.map +1 -0
  60. package/lib/module/types/index.js +2 -0
  61. package/lib/module/types/index.js.map +1 -0
  62. package/lib/module/types/video.js +2 -0
  63. package/lib/module/types/video.js.map +1 -0
  64. package/lib/module/utils/Emitter.js +39 -0
  65. package/lib/module/utils/Emitter.js.map +1 -0
  66. package/lib/module/utils/formatTime.js +15 -0
  67. package/lib/module/utils/formatTime.js.map +1 -0
  68. package/lib/typescript/package.json +1 -0
  69. package/lib/typescript/src/AuVideoSurfaceNativeComponent.d.ts +8 -0
  70. package/lib/typescript/src/AuVideoSurfaceNativeComponent.d.ts.map +1 -0
  71. package/lib/typescript/src/NativeAuVideo.d.ts +85 -0
  72. package/lib/typescript/src/NativeAuVideo.d.ts.map +1 -0
  73. package/lib/typescript/src/components/FloatingPlayer.d.ts +12 -0
  74. package/lib/typescript/src/components/FloatingPlayer.d.ts.map +1 -0
  75. package/lib/typescript/src/components/FullscreenPlayer.d.ts +12 -0
  76. package/lib/typescript/src/components/FullscreenPlayer.d.ts.map +1 -0
  77. package/lib/typescript/src/components/GestureOverlay.d.ts +14 -0
  78. package/lib/typescript/src/components/GestureOverlay.d.ts.map +1 -0
  79. package/lib/typescript/src/components/MiniPlayer.d.ts +16 -0
  80. package/lib/typescript/src/components/MiniPlayer.d.ts.map +1 -0
  81. package/lib/typescript/src/components/VideoControls.d.ts +17 -0
  82. package/lib/typescript/src/components/VideoControls.d.ts.map +1 -0
  83. package/lib/typescript/src/components/VideoPlayer.d.ts +27 -0
  84. package/lib/typescript/src/components/VideoPlayer.d.ts.map +1 -0
  85. package/lib/typescript/src/components/VideoSurface.d.ts +18 -0
  86. package/lib/typescript/src/components/VideoSurface.d.ts.map +1 -0
  87. package/lib/typescript/src/core/VideoManager.d.ts +88 -0
  88. package/lib/typescript/src/core/VideoManager.d.ts.map +1 -0
  89. package/lib/typescript/src/hooks/useFullscreen.d.ts +7 -0
  90. package/lib/typescript/src/hooks/useFullscreen.d.ts.map +1 -0
  91. package/lib/typescript/src/hooks/usePiP.d.ts +6 -0
  92. package/lib/typescript/src/hooks/usePiP.d.ts.map +1 -0
  93. package/lib/typescript/src/hooks/usePlayback.d.ts +13 -0
  94. package/lib/typescript/src/hooks/usePlayback.d.ts.map +1 -0
  95. package/lib/typescript/src/hooks/useVideo.d.ts +13 -0
  96. package/lib/typescript/src/hooks/useVideo.d.ts.map +1 -0
  97. package/lib/typescript/src/hooks/useVideoEvents.d.ts +14 -0
  98. package/lib/typescript/src/hooks/useVideoEvents.d.ts.map +1 -0
  99. package/lib/typescript/src/index.d.ts +18 -0
  100. package/lib/typescript/src/index.d.ts.map +1 -0
  101. package/lib/typescript/src/provider/VideoContext.d.ts +8 -0
  102. package/lib/typescript/src/provider/VideoContext.d.ts.map +1 -0
  103. package/lib/typescript/src/provider/VideoProvider.d.ts +18 -0
  104. package/lib/typescript/src/provider/VideoProvider.d.ts.map +1 -0
  105. package/lib/typescript/src/state/createVideoStore.d.ts +6 -0
  106. package/lib/typescript/src/state/createVideoStore.d.ts.map +1 -0
  107. package/lib/typescript/src/types/events.d.ts +46 -0
  108. package/lib/typescript/src/types/events.d.ts.map +1 -0
  109. package/lib/typescript/src/types/index.d.ts +3 -0
  110. package/lib/typescript/src/types/index.d.ts.map +1 -0
  111. package/lib/typescript/src/types/video.d.ts +70 -0
  112. package/lib/typescript/src/types/video.d.ts.map +1 -0
  113. package/lib/typescript/src/utils/Emitter.d.ts +15 -0
  114. package/lib/typescript/src/utils/Emitter.d.ts.map +1 -0
  115. package/lib/typescript/src/utils/formatTime.d.ts +2 -0
  116. package/lib/typescript/src/utils/formatTime.d.ts.map +1 -0
  117. package/package.json +154 -0
  118. package/src/AuVideoSurfaceNativeComponent.ts +8 -0
  119. package/src/NativeAuVideo.ts +106 -0
  120. package/src/components/FloatingPlayer.tsx +130 -0
  121. package/src/components/FullscreenPlayer.tsx +64 -0
  122. package/src/components/GestureOverlay.tsx +76 -0
  123. package/src/components/MiniPlayer.tsx +93 -0
  124. package/src/components/VideoControls.tsx +213 -0
  125. package/src/components/VideoPlayer.tsx +77 -0
  126. package/src/components/VideoSurface.tsx +39 -0
  127. package/src/core/VideoManager.ts +441 -0
  128. package/src/hooks/useFullscreen.ts +14 -0
  129. package/src/hooks/usePiP.ts +13 -0
  130. package/src/hooks/usePlayback.ts +26 -0
  131. package/src/hooks/useVideo.ts +16 -0
  132. package/src/hooks/useVideoEvents.ts +33 -0
  133. package/src/index.ts +54 -0
  134. package/src/provider/VideoContext.ts +12 -0
  135. package/src/provider/VideoProvider.tsx +41 -0
  136. package/src/state/createVideoStore.ts +33 -0
  137. package/src/types/events.ts +22 -0
  138. package/src/types/index.ts +11 -0
  139. package/src/types/video.ts +85 -0
  140. package/src/utils/Emitter.ts +51 -0
  141. package/src/utils/formatTime.ts +12 -0
@@ -0,0 +1,76 @@
1
+ import { useCallback, useRef, type PropsWithChildren } from 'react';
2
+ import {
3
+ Pressable,
4
+ StyleSheet,
5
+ type GestureResponderEvent,
6
+ type LayoutChangeEvent,
7
+ } from 'react-native';
8
+
9
+ export interface GestureOverlayProps extends PropsWithChildren {
10
+ onSingleTap?: () => void;
11
+ onDoubleTapLeft?: () => void;
12
+ onDoubleTapRight?: () => void;
13
+ onLongPress?: () => void;
14
+ }
15
+
16
+ const DOUBLE_TAP_MS = 280;
17
+
18
+ /**
19
+ * Tap layer for player chrome: single tap (toggle controls), double tap
20
+ * left/right (seek back/forward), long press. Pinch/brightness/volume
21
+ * gestures are on the roadmap (need native cooperation).
22
+ */
23
+ export function GestureOverlay({
24
+ onSingleTap,
25
+ onDoubleTapLeft,
26
+ onDoubleTapRight,
27
+ onLongPress,
28
+ children,
29
+ }: GestureOverlayProps) {
30
+ const width = useRef(0);
31
+ const lastTap = useRef(0);
32
+ const singleTapTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
33
+
34
+ const onLayout = useCallback((e: LayoutChangeEvent) => {
35
+ width.current = e.nativeEvent.layout.width;
36
+ }, []);
37
+
38
+ const handlePress = useCallback(
39
+ (e: GestureResponderEvent) => {
40
+ const now = Date.now();
41
+ const x = e.nativeEvent.locationX;
42
+
43
+ if (now - lastTap.current < DOUBLE_TAP_MS) {
44
+ lastTap.current = 0;
45
+ if (singleTapTimer.current) {
46
+ clearTimeout(singleTapTimer.current);
47
+ singleTapTimer.current = null;
48
+ }
49
+ if (x < width.current / 2) {
50
+ onDoubleTapLeft?.();
51
+ } else {
52
+ onDoubleTapRight?.();
53
+ }
54
+ return;
55
+ }
56
+
57
+ lastTap.current = now;
58
+ singleTapTimer.current = setTimeout(() => {
59
+ singleTapTimer.current = null;
60
+ onSingleTap?.();
61
+ }, DOUBLE_TAP_MS);
62
+ },
63
+ [onSingleTap, onDoubleTapLeft, onDoubleTapRight]
64
+ );
65
+
66
+ return (
67
+ <Pressable
68
+ style={StyleSheet.absoluteFill}
69
+ onLayout={onLayout}
70
+ onPress={handlePress}
71
+ onLongPress={onLongPress}
72
+ >
73
+ {children}
74
+ </Pressable>
75
+ );
76
+ }
@@ -0,0 +1,93 @@
1
+ import {
2
+ Pressable,
3
+ StyleSheet,
4
+ Text,
5
+ View,
6
+ type ViewProps,
7
+ } from 'react-native';
8
+ import { usePlayback } from '../hooks/usePlayback';
9
+ import { useVideoManager } from '../provider/VideoContext';
10
+ import { VideoSurface } from './VideoSurface';
11
+
12
+ export interface MiniPlayerProps extends ViewProps {
13
+ /** Surface id. Default "__au_mini__". */
14
+ surfaceId?: string;
15
+ /** Tapping the bar (e.g. navigate back to the detail screen). */
16
+ onPress?: () => void;
17
+ /** Called by the ✕ button; button hidden when omitted. */
18
+ onClose?: () => void;
19
+ }
20
+
21
+ /**
22
+ * Docked mini player bar (Spotify style): thumbnail surface, title,
23
+ * play/pause and close. Attach it with `attach(surfaceId)` — typically when
24
+ * the user leaves the screen that owned playback.
25
+ */
26
+ export function MiniPlayer({
27
+ surfaceId = '__au_mini__',
28
+ onPress,
29
+ onClose,
30
+ style,
31
+ ...rest
32
+ }: MiniPlayerProps) {
33
+ const manager = useVideoManager();
34
+ const playing = usePlayback((s) => s.playing);
35
+ const title = usePlayback((s) => s.currentVideo?.title ?? '');
36
+
37
+ return (
38
+ <View style={[styles.bar, style]} {...rest}>
39
+ <Pressable style={styles.body} onPress={onPress}>
40
+ <VideoSurface surfaceId={surfaceId} style={styles.thumb} />
41
+ <Text style={styles.title} numberOfLines={1}>
42
+ {title}
43
+ </Text>
44
+ </Pressable>
45
+ <Pressable
46
+ style={styles.button}
47
+ hitSlop={8}
48
+ onPress={() => manager.toggle()}
49
+ >
50
+ <Text style={styles.icon}>{playing ? '❙❙' : '▶'}</Text>
51
+ </Pressable>
52
+ {onClose ? (
53
+ <Pressable style={styles.button} hitSlop={8} onPress={onClose}>
54
+ <Text style={styles.icon}>✕</Text>
55
+ </Pressable>
56
+ ) : null}
57
+ </View>
58
+ );
59
+ }
60
+
61
+ const styles = StyleSheet.create({
62
+ bar: {
63
+ flexDirection: 'row',
64
+ alignItems: 'center',
65
+ backgroundColor: '#1c1c1e',
66
+ height: 56,
67
+ paddingRight: 8,
68
+ },
69
+ body: {
70
+ flex: 1,
71
+ flexDirection: 'row',
72
+ alignItems: 'center',
73
+ gap: 10,
74
+ },
75
+ thumb: {
76
+ width: 56 * (16 / 9),
77
+ height: 56,
78
+ backgroundColor: '#000',
79
+ },
80
+ title: {
81
+ flex: 1,
82
+ color: '#fff',
83
+ fontSize: 14,
84
+ },
85
+ button: {
86
+ paddingHorizontal: 10,
87
+ paddingVertical: 8,
88
+ },
89
+ icon: {
90
+ color: '#fff',
91
+ fontSize: 16,
92
+ },
93
+ });
@@ -0,0 +1,213 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import {
3
+ Pressable,
4
+ StyleSheet,
5
+ Text,
6
+ View,
7
+ type GestureResponderEvent,
8
+ type LayoutChangeEvent,
9
+ } from 'react-native';
10
+ import { useVideoManager } from '../provider/VideoContext';
11
+ import { usePlayback } from '../hooks/usePlayback';
12
+ import { formatTime } from '../utils/formatTime';
13
+ import { GestureOverlay } from './GestureOverlay';
14
+
15
+ export interface VideoControlsProps {
16
+ /** Seconds jumped by double-tap. Default 10. */
17
+ doubleTapSeek?: number;
18
+ /** Auto-hide delay in ms. Default 3000. */
19
+ hideAfter?: number;
20
+ /** Show the fullscreen toggle button. Default true. */
21
+ showFullscreenButton?: boolean;
22
+ /** Called by the close (✕) button; button hidden when omitted. */
23
+ onClose?: () => void;
24
+ }
25
+
26
+ /**
27
+ * Minimal built-in chrome: play/pause, seek bar, time, mute and fullscreen
28
+ * toggles, with tap-to-show / double-tap-to-seek gestures. Apps wanting a
29
+ * custom design can ignore this and build on usePlayback()/useVideo().
30
+ */
31
+ export function VideoControls({
32
+ doubleTapSeek = 10,
33
+ hideAfter = 3000,
34
+ showFullscreenButton = true,
35
+ onClose,
36
+ }: VideoControlsProps) {
37
+ const manager = useVideoManager();
38
+ const playing = usePlayback((s) => s.playing);
39
+ const buffering = usePlayback((s) => s.buffering);
40
+ const position = usePlayback((s) => s.position);
41
+ const duration = usePlayback((s) => s.duration);
42
+ const muted = usePlayback((s) => s.muted);
43
+ const fullscreen = usePlayback((s) => s.fullscreen);
44
+
45
+ const [visible, setVisible] = useState(true);
46
+ const hideTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
47
+ const trackWidth = useRef(0);
48
+
49
+ const scheduleHide = useCallback(() => {
50
+ if (hideTimer.current) {
51
+ clearTimeout(hideTimer.current);
52
+ }
53
+ hideTimer.current = setTimeout(() => setVisible(false), hideAfter);
54
+ }, [hideAfter]);
55
+
56
+ useEffect(() => {
57
+ if (visible && playing) {
58
+ scheduleHide();
59
+ }
60
+ return () => {
61
+ if (hideTimer.current) {
62
+ clearTimeout(hideTimer.current);
63
+ }
64
+ };
65
+ }, [visible, playing, scheduleHide]);
66
+
67
+ const toggleVisible = useCallback(() => setVisible((v) => !v), []);
68
+
69
+ const onTrackLayout = useCallback((e: LayoutChangeEvent) => {
70
+ trackWidth.current = e.nativeEvent.layout.width;
71
+ }, []);
72
+
73
+ const onTrackPress = useCallback(
74
+ (e: GestureResponderEvent) => {
75
+ if (trackWidth.current > 0 && duration > 0) {
76
+ const ratio = e.nativeEvent.locationX / trackWidth.current;
77
+ manager.seek(ratio * duration);
78
+ }
79
+ scheduleHide();
80
+ },
81
+ [manager, duration, scheduleHide]
82
+ );
83
+
84
+ const progress = duration > 0 ? Math.min(position / duration, 1) : 0;
85
+
86
+ return (
87
+ <View style={StyleSheet.absoluteFill} pointerEvents="box-none">
88
+ <GestureOverlay
89
+ onSingleTap={toggleVisible}
90
+ onDoubleTapLeft={() => manager.seekBy(-doubleTapSeek)}
91
+ onDoubleTapRight={() => manager.seekBy(doubleTapSeek)}
92
+ />
93
+ {visible ? (
94
+ <View style={styles.chrome} pointerEvents="box-none">
95
+ <View style={styles.topRow}>
96
+ {onClose ? (
97
+ <Pressable style={styles.button} onPress={onClose} hitSlop={8}>
98
+ <Text style={styles.icon}>✕</Text>
99
+ </Pressable>
100
+ ) : (
101
+ <View />
102
+ )}
103
+ <Pressable
104
+ style={styles.button}
105
+ onPress={() => (muted ? manager.unmute() : manager.mute())}
106
+ hitSlop={8}
107
+ >
108
+ <Text style={styles.icon}>{muted ? '🔇' : '🔊'}</Text>
109
+ </Pressable>
110
+ </View>
111
+
112
+ <Pressable
113
+ style={styles.playButton}
114
+ onPress={() => {
115
+ manager.toggle();
116
+ scheduleHide();
117
+ }}
118
+ hitSlop={16}
119
+ >
120
+ <Text style={styles.playIcon}>
121
+ {buffering ? '…' : playing ? '❙❙' : '▶'}
122
+ </Text>
123
+ </Pressable>
124
+
125
+ <View style={styles.bottomRow}>
126
+ <Text style={styles.time}>{formatTime(position)}</Text>
127
+ <Pressable
128
+ style={styles.track}
129
+ onLayout={onTrackLayout}
130
+ onPress={onTrackPress}
131
+ >
132
+ <View style={styles.trackBg} />
133
+ <View
134
+ style={[styles.trackFill, { width: `${progress * 100}%` }]}
135
+ />
136
+ </Pressable>
137
+ <Text style={styles.time}>{formatTime(duration)}</Text>
138
+ {showFullscreenButton ? (
139
+ <Pressable
140
+ style={styles.button}
141
+ onPress={() => manager.toggleFullscreen()}
142
+ hitSlop={8}
143
+ >
144
+ <Text style={styles.icon}>{fullscreen ? '⤡' : '⤢'}</Text>
145
+ </Pressable>
146
+ ) : null}
147
+ </View>
148
+ </View>
149
+ ) : null}
150
+ </View>
151
+ );
152
+ }
153
+
154
+ const styles = StyleSheet.create({
155
+ chrome: {
156
+ position: 'absolute',
157
+ top: 0,
158
+ left: 0,
159
+ right: 0,
160
+ bottom: 0,
161
+ backgroundColor: 'rgba(0,0,0,0.35)',
162
+ justifyContent: 'space-between',
163
+ },
164
+ topRow: {
165
+ flexDirection: 'row',
166
+ justifyContent: 'space-between',
167
+ padding: 12,
168
+ },
169
+ playButton: {
170
+ alignSelf: 'center',
171
+ },
172
+ playIcon: {
173
+ color: '#fff',
174
+ fontSize: 34,
175
+ },
176
+ bottomRow: {
177
+ flexDirection: 'row',
178
+ alignItems: 'center',
179
+ paddingHorizontal: 12,
180
+ paddingBottom: 12,
181
+ gap: 8,
182
+ },
183
+ time: {
184
+ color: '#fff',
185
+ fontSize: 12,
186
+ fontVariant: ['tabular-nums'],
187
+ },
188
+ track: {
189
+ flex: 1,
190
+ height: 24,
191
+ justifyContent: 'center',
192
+ },
193
+ trackBg: {
194
+ position: 'absolute',
195
+ left: 0,
196
+ right: 0,
197
+ height: 3,
198
+ borderRadius: 1.5,
199
+ backgroundColor: 'rgba(255,255,255,0.35)',
200
+ },
201
+ trackFill: {
202
+ height: 3,
203
+ borderRadius: 1.5,
204
+ backgroundColor: '#fff',
205
+ },
206
+ button: {
207
+ padding: 4,
208
+ },
209
+ icon: {
210
+ color: '#fff',
211
+ fontSize: 18,
212
+ },
213
+ });
@@ -0,0 +1,77 @@
1
+ import { useEffect } from 'react';
2
+ import { StyleSheet, View, type ViewProps } from 'react-native';
3
+ import { useVideoManager } from '../provider/VideoContext';
4
+ import type { ResizeMode, VideoSource } from '../types/video';
5
+ import { VideoControls } from './VideoControls';
6
+ import { VideoSurface } from './VideoSurface';
7
+
8
+ export interface VideoPlayerProps extends ViewProps {
9
+ source: VideoSource;
10
+ /** Default true. */
11
+ autoplay?: boolean;
12
+ /**
13
+ * Surface id to register/attach under. Defaults to `player:<source.id>`,
14
+ * so a feed item and a detail screen showing the same video naturally
15
+ * hand the player off to whichever mounted last.
16
+ */
17
+ surfaceId?: string;
18
+ /** Show built-in controls. Default true. */
19
+ controls?: boolean;
20
+ resizeMode?: ResizeMode;
21
+ }
22
+
23
+ /**
24
+ * Convenience all-in-one player: `setSource` (same-video handoff aware) +
25
+ * `attach` + surface + optional controls. Rendering two VideoPlayers with
26
+ * the same source id moves the ONE engine — it never creates a second one.
27
+ *
28
+ * ```tsx
29
+ * <VideoPlayer source={{ id: '123', uri }} style={{ aspectRatio: 16 / 9 }} />
30
+ * ```
31
+ */
32
+ export function VideoPlayer({
33
+ source,
34
+ autoplay = true,
35
+ surfaceId,
36
+ controls = true,
37
+ resizeMode,
38
+ style,
39
+ ...rest
40
+ }: VideoPlayerProps) {
41
+ const manager = useVideoManager();
42
+ const id = surfaceId ?? `player:${source.id}`;
43
+
44
+ useEffect(() => {
45
+ manager.setSource(source, { autoplay, surfaceId: id });
46
+ // Attach on mount / when the video identity changes. Other source
47
+ // fields (title, headers) don't retrigger: identity is source.id.
48
+ // eslint-disable-next-line react-hooks/exhaustive-deps
49
+ }, [manager, source.id, id]);
50
+
51
+ useEffect(() => {
52
+ if (resizeMode) {
53
+ manager.setResizeMode(resizeMode);
54
+ }
55
+ }, [manager, resizeMode]);
56
+
57
+ return (
58
+ <View style={[styles.container, style]} {...rest}>
59
+ <VideoSurface surfaceId={id} style={styles.surface} />
60
+ {controls ? <VideoControls /> : null}
61
+ </View>
62
+ );
63
+ }
64
+
65
+ const styles = StyleSheet.create({
66
+ container: {
67
+ backgroundColor: '#000',
68
+ overflow: 'hidden',
69
+ },
70
+ surface: {
71
+ position: 'absolute',
72
+ top: 0,
73
+ left: 0,
74
+ right: 0,
75
+ bottom: 0,
76
+ },
77
+ });
@@ -0,0 +1,39 @@
1
+ import { useEffect } from 'react';
2
+ import type { ViewProps } from 'react-native';
3
+ import AuVideoSurfaceNativeComponent from '../AuVideoSurfaceNativeComponent';
4
+ import { useVideoManager } from '../provider/VideoContext';
5
+
6
+ export interface VideoSurfaceProps extends ViewProps {
7
+ /** Id this surface registers under. Attach the player with `attach(id)`. */
8
+ surfaceId: string;
9
+ /** Attach the player to this surface as soon as it mounts. Default false. */
10
+ autoAttach?: boolean;
11
+ }
12
+
13
+ /**
14
+ * A dumb rendering surface. It never creates a player — it only marks a
15
+ * spot where the singleton engine can render. Mount as many as you like;
16
+ * the player shows in at most one at a time.
17
+ *
18
+ * ```tsx
19
+ * <VideoSurface surfaceId="feed" style={{ aspectRatio: 16 / 9 }} />
20
+ * ```
21
+ */
22
+ export function VideoSurface({
23
+ surfaceId,
24
+ autoAttach = false,
25
+ ...rest
26
+ }: VideoSurfaceProps) {
27
+ const manager = useVideoManager();
28
+
29
+ useEffect(() => {
30
+ if (autoAttach) {
31
+ manager.attach(surfaceId);
32
+ }
33
+ return () => {
34
+ manager.handleSurfaceUnmount(surfaceId);
35
+ };
36
+ }, [manager, surfaceId, autoAttach]);
37
+
38
+ return <AuVideoSurfaceNativeComponent surfaceId={surfaceId} {...rest} />;
39
+ }