react-native-video-provider 0.2.4 → 0.2.6
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/README.md +90 -3
- package/android/build.gradle +25 -0
- package/android/src/main/java/com/auvideo/PlayerCore.kt +22 -0
- package/lib/module/components/VideoControls.js +5 -9
- package/lib/module/components/VideoControls.js.map +1 -1
- package/lib/module/components/VideoFeed.js +20 -0
- package/lib/module/components/VideoFeed.js.map +1 -1
- package/lib/module/components/VideoPlayer.js +81 -4
- package/lib/module/components/VideoPlayer.js.map +1 -1
- package/lib/module/components/icons.js +32 -1
- package/lib/module/components/icons.js.map +1 -1
- package/lib/typescript/src/components/VideoControls.d.ts +12 -1
- package/lib/typescript/src/components/VideoControls.d.ts.map +1 -1
- package/lib/typescript/src/components/VideoFeed.d.ts +8 -1
- package/lib/typescript/src/components/VideoFeed.d.ts.map +1 -1
- package/lib/typescript/src/components/VideoPlayer.d.ts +35 -0
- package/lib/typescript/src/components/VideoPlayer.d.ts.map +1 -1
- package/lib/typescript/src/components/icons.d.ts +1 -0
- package/lib/typescript/src/components/icons.d.ts.map +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.js +21 -7
- package/src/components/VideoControls.tsx +22 -9
- package/src/components/VideoFeed.tsx +30 -0
- package/src/components/VideoPlayer.tsx +136 -4
- package/src/components/icons.tsx +35 -1
package/README.md
CHANGED
|
@@ -18,8 +18,14 @@ Feed ──▶ Detail ──▶ Fullscreen (rotation unlocked) ──▶ Floatin
|
|
|
18
18
|
navigation, tab switches, unmounts, Fast Refresh
|
|
19
19
|
- 🔁 **Same-video handoff** — `setSource` with the same `id` is a no-op for
|
|
20
20
|
the engine; the player just re-parents to the new surface
|
|
21
|
+
- 📱 **Single-engine feed** — `VideoFeed`, a TikTok/Reels-style list where
|
|
22
|
+
only the scrolled-into-focus video plays, on one player, flat memory
|
|
21
23
|
- 📺 **Fullscreen** — built-in host that unlocks rotation while visible and
|
|
22
24
|
restores the previous orientation lock on exit
|
|
25
|
+
- 🧭 **Orientation control** — force portrait/landscape (+ inverted) per
|
|
26
|
+
player, scoped to fullscreen, or as a standing lock
|
|
27
|
+
- ⏸️ **Focus-aware** — auto-pause on app background and on screen navigation
|
|
28
|
+
(React Navigation's `useIsFocused()`)
|
|
23
29
|
- 🎈 **Floating player** — built-in draggable in-app window
|
|
24
30
|
- 🖼 **Picture in Picture** — Android + iOS
|
|
25
31
|
- ⚡ **New Architecture native** — TurboModule + Fabric, typed end to end
|
|
@@ -31,11 +37,15 @@ See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the full design and
|
|
|
31
37
|
## Installation
|
|
32
38
|
|
|
33
39
|
```sh
|
|
34
|
-
npm install react-native-video-provider
|
|
40
|
+
npm install react-native-video-provider react-native-svg
|
|
35
41
|
cd ios && pod install
|
|
36
42
|
```
|
|
37
43
|
|
|
38
|
-
|
|
44
|
+
`react-native-svg` is a peer dependency (used by the built-in control icons).
|
|
45
|
+
|
|
46
|
+
Requires the New Architecture. Works on React Native **0.79+** — the
|
|
47
|
+
TurboModule spec uses direct codegen-type imports so it parses on 0.79's
|
|
48
|
+
codegen as well as 0.80+.
|
|
39
49
|
|
|
40
50
|
## Quick start
|
|
41
51
|
|
|
@@ -99,6 +109,14 @@ const { enter, toggle } = useFullscreen();
|
|
|
99
109
|
enter('landscape');
|
|
100
110
|
```
|
|
101
111
|
|
|
112
|
+
Set `fullscreenOrientation` to a **landscape** value and physically rotating
|
|
113
|
+
the device to landscape auto-enters fullscreen (rotating back to portrait
|
|
114
|
+
exits) — YouTube-style. Requires the app to allow landscape at the OS level:
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
<VideoPlayer source={video} fullscreenOrientation="landscape" />
|
|
118
|
+
```
|
|
119
|
+
|
|
102
120
|
Or as a standing lock, independent of fullscreen:
|
|
103
121
|
|
|
104
122
|
```tsx
|
|
@@ -131,7 +149,76 @@ player.attach('feed'); // render here
|
|
|
131
149
|
```
|
|
132
150
|
|
|
133
151
|
`<VideoPlayer>` is the convenience wrapper that does `setSource` + `attach` +
|
|
134
|
-
optional controls in one component.
|
|
152
|
+
optional controls in one component. Handy props:
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
<VideoPlayer
|
|
156
|
+
source={video}
|
|
157
|
+
autoplay muted repeat // playback flags
|
|
158
|
+
resizeMode="contain" // contain | cover | stretch
|
|
159
|
+
controls // built-in chrome (SVG icons)
|
|
160
|
+
onLoadComplete={(m) => {}} // duration/dimensions ready
|
|
161
|
+
onBuffering={(b) => {}}
|
|
162
|
+
onError={(e) => {}}
|
|
163
|
+
ref={playerRef} // → the VideoManager (playerRef.current.seek(…))
|
|
164
|
+
/>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Pass `live` for a live stream: the controls hide the seek bar/times and show
|
|
168
|
+
just mute + fullscreen. `liveIcon` renders a live indicator (e.g. a Lottie
|
|
169
|
+
badge), and `thumbnail` shows a poster over the video during the initial load:
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
<VideoPlayer
|
|
173
|
+
source={liveSource}
|
|
174
|
+
live
|
|
175
|
+
liveIcon={() => <LottieView source={liveAnim} autoPlay loop style={{ width: 44, height: 20 }} />}
|
|
176
|
+
thumbnail={() => <Image source={{ uri: poster }} style={StyleSheet.absoluteFill} resizeMode="cover" />}
|
|
177
|
+
/>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Video feed (single engine, only the focused one plays)
|
|
181
|
+
|
|
182
|
+
`<VideoFeed>` is a TikTok/Reels-style vertical feed. It renders many videos in
|
|
183
|
+
a FlatList but plays **only the one scrolled into focus** — on the same single
|
|
184
|
+
engine, so memory and CPU stay flat no matter how long the feed. Each item is
|
|
185
|
+
just a surface; scrolling hands the one player off to the focused item.
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
import { VideoFeed } from 'react-native-video-provider';
|
|
189
|
+
|
|
190
|
+
<VideoFeed
|
|
191
|
+
data={videos} // [{ id, uri, title? }, …] — each needs a stable id
|
|
192
|
+
renderOverlay={({ item, focused }) => (
|
|
193
|
+
<Caption title={item.title} paused={!focused} />
|
|
194
|
+
)}
|
|
195
|
+
/>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Any extra `FlatList` prop passes through (`onEndReached` for infinite scroll,
|
|
199
|
+
`ListHeaderComponent`, …).
|
|
200
|
+
|
|
201
|
+
## Pausing on focus loss
|
|
202
|
+
|
|
203
|
+
Both `<VideoPlayer>` and `<VideoFeed>` pause automatically when the **app is
|
|
204
|
+
backgrounded** (opt out with `pauseOnFocusLost={false}` for background audio).
|
|
205
|
+
|
|
206
|
+
For **screen navigation** (navigating to another screen while the app stays
|
|
207
|
+
foregrounded), pass your navigation library's focus flag — React Navigation
|
|
208
|
+
keeps screens mounted, so there is no other reliable signal:
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
import { useIsFocused } from '@react-navigation/native';
|
|
212
|
+
|
|
213
|
+
function Screen() {
|
|
214
|
+
const isFocused = useIsFocused();
|
|
215
|
+
return <VideoPlayer source={video} isFocused={isFocused} />;
|
|
216
|
+
// VideoFeed takes the same prop.
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`false` pauses; returning to `true` resumes (reclaiming the engine if another
|
|
221
|
+
video took it while you were away).
|
|
135
222
|
|
|
136
223
|
## State & events
|
|
137
224
|
|
package/android/build.gradle
CHANGED
|
@@ -57,3 +57,28 @@ dependencies {
|
|
|
57
57
|
implementation "androidx.media3:media3-ui:${media3Version}"
|
|
58
58
|
implementation "androidx.core:core-ktx:1.13.1"
|
|
59
59
|
}
|
|
60
|
+
|
|
61
|
+
// Print the package banner while the app compiles. Best-effort and fully
|
|
62
|
+
// guarded — a missing node binary or any failure never breaks the build.
|
|
63
|
+
tasks.register("auVideoBanner") {
|
|
64
|
+
doLast {
|
|
65
|
+
try {
|
|
66
|
+
def script = new File(projectDir, "../scripts/postinstall.js")
|
|
67
|
+
if (script.exists()) {
|
|
68
|
+
def banner = providers.exec {
|
|
69
|
+
commandLine "node", script.absolutePath, "--force"
|
|
70
|
+
}.standardOutput.asText.get()
|
|
71
|
+
logger.lifecycle(banner.trim())
|
|
72
|
+
}
|
|
73
|
+
} catch (Exception e) {
|
|
74
|
+
// Never fail the build over a banner (e.g. node not on PATH).
|
|
75
|
+
logger.info("auVideoBanner skipped: ${e.message}")
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
afterEvaluate {
|
|
81
|
+
tasks.matching { it.name == "preBuild" }.configureEach {
|
|
82
|
+
dependsOn("auVideoBanner")
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -284,9 +284,31 @@ object PlayerCore {
|
|
|
284
284
|
)
|
|
285
285
|
currentSurfaceId = surfaceId
|
|
286
286
|
pendingSurfaceId = null
|
|
287
|
+
reassertVideoOutput(pv, surfaceId, container)
|
|
287
288
|
listener?.onAttach(surfaceId)
|
|
288
289
|
}
|
|
289
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Re-parenting the PlayerView destroys and recreates its TextureView
|
|
293
|
+
* SurfaceTexture. VOD re-renders the last decoded frame onto the new
|
|
294
|
+
* surface immediately, but a LIVE stream (no buffered frame to re-render)
|
|
295
|
+
* can stay black. Re-binding the player to the view on the next frame
|
|
296
|
+
* forces the video output onto the fresh surface. No-op-safe for VOD.
|
|
297
|
+
*/
|
|
298
|
+
private fun reassertVideoOutput(
|
|
299
|
+
pv: PlayerView,
|
|
300
|
+
surfaceId: String,
|
|
301
|
+
container: AuVideoSurfaceView,
|
|
302
|
+
) {
|
|
303
|
+
mainHandler.post {
|
|
304
|
+
if (currentSurfaceId == surfaceId && pv.parent === container) {
|
|
305
|
+
val exo = player ?: return@post
|
|
306
|
+
pv.player = null
|
|
307
|
+
pv.player = exo
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
290
312
|
// --------------------------------------------------------------- events
|
|
291
313
|
|
|
292
314
|
private val playerListener = object : Player.Listener {
|
|
@@ -6,7 +6,7 @@ import { useVideoManager } from "../provider/VideoContext.js";
|
|
|
6
6
|
import { usePlayback } from "../hooks/usePlayback.js";
|
|
7
7
|
import { formatTime } from "../utils/formatTime.js";
|
|
8
8
|
import { GestureOverlay } from "./GestureOverlay.js";
|
|
9
|
-
import {
|
|
9
|
+
import { BackIcon } from "./icons.js";
|
|
10
10
|
import SvgIcons from "./SvgIcons.js";
|
|
11
11
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
12
12
|
/**
|
|
@@ -18,21 +18,17 @@ export function VideoControls({
|
|
|
18
18
|
doubleTapSeek = 10,
|
|
19
19
|
hideAfter = 3000,
|
|
20
20
|
showFullscreenButton = true,
|
|
21
|
+
live = false,
|
|
22
|
+
liveIcon,
|
|
21
23
|
onClose
|
|
22
24
|
}) {
|
|
23
25
|
const manager = useVideoManager();
|
|
24
|
-
const status = usePlayback(s => s.status);
|
|
25
26
|
const playing = usePlayback(s => s.playing);
|
|
26
27
|
const buffering = usePlayback(s => s.buffering);
|
|
27
|
-
const loading = usePlayback(s => s.loading);
|
|
28
28
|
const position = usePlayback(s => s.position);
|
|
29
29
|
const duration = usePlayback(s => s.duration);
|
|
30
30
|
const muted = usePlayback(s => s.muted);
|
|
31
31
|
const fullscreen = usePlayback(s => s.fullscreen);
|
|
32
|
-
|
|
33
|
-
// No known duration once loaded — a live stream or a video with no fixed
|
|
34
|
-
// end. There's nothing to seek, so hide the seek bar entirely.
|
|
35
|
-
const live = status !== 'idle' && !loading && duration <= 0;
|
|
36
32
|
const [visible, setVisible] = useState(true);
|
|
37
33
|
const hideTimer = useRef(null);
|
|
38
34
|
const trackWidth = useRef(0);
|
|
@@ -110,7 +106,7 @@ export function VideoControls({
|
|
|
110
106
|
style: styles.button,
|
|
111
107
|
onPress: onClose,
|
|
112
108
|
hitSlop: 8,
|
|
113
|
-
children: /*#__PURE__*/_jsx(
|
|
109
|
+
children: /*#__PURE__*/_jsx(BackIcon, {
|
|
114
110
|
size: 18,
|
|
115
111
|
color: "#fff"
|
|
116
112
|
})
|
|
@@ -139,7 +135,7 @@ export function VideoControls({
|
|
|
139
135
|
}), /*#__PURE__*/_jsx(View, {
|
|
140
136
|
style: styles.bottomRow,
|
|
141
137
|
children: live ? /*#__PURE__*/_jsxs(_Fragment, {
|
|
142
|
-
children: [muteButton, /*#__PURE__*/_jsx(View, {
|
|
138
|
+
children: [liveIcon ? liveIcon() : null, muteButton, /*#__PURE__*/_jsx(View, {
|
|
143
139
|
style: styles.spacer
|
|
144
140
|
}), fullscreenButton]
|
|
145
141
|
}) : /*#__PURE__*/_jsxs(_Fragment, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useCallback","useEffect","useRef","useState","Pressable","StyleSheet","Text","View","useVideoManager","usePlayback","formatTime","GestureOverlay","
|
|
1
|
+
{"version":3,"names":["useCallback","useEffect","useRef","useState","Pressable","StyleSheet","Text","View","useVideoManager","usePlayback","formatTime","GestureOverlay","BackIcon","SvgIcons","jsx","_jsx","jsxs","_jsxs","Fragment","_Fragment","VideoControls","doubleTapSeek","hideAfter","showFullscreenButton","live","liveIcon","onClose","manager","playing","s","buffering","position","duration","muted","fullscreen","visible","setVisible","hideTimer","trackWidth","scheduleHide","current","clearTimeout","setTimeout","toggleVisible","v","onTrackLayout","e","nativeEvent","layout","width","onTrackPress","ratio","locationX","seek","progress","Math","min","muteButton","style","styles","button","onPress","unmute","mute","hitSlop","children","icon","size","fill","type","fullscreenButton","toggleFullscreen","absoluteFill","pointerEvents","onSingleTap","onDoubleTapLeft","seekBy","onDoubleTapRight","chrome","topRow","color","playButton","toggle","playIcon","bottomRow","spacer","time","track","onLayout","trackBg","trackFill","create","top","left","right","bottom","backgroundColor","justifyContent","flexDirection","padding","alignSelf","fontSize","alignItems","paddingHorizontal","paddingBottom","gap","fontVariant","flex","height","borderRadius"],"sourceRoot":"../../../src","sources":["components/VideoControls.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -38,6 +38,7 @@ export function VideoFeed({
|
|
|
38
38
|
renderOverlay,
|
|
39
39
|
onFocusChange,
|
|
40
40
|
pauseOnUnmount = true,
|
|
41
|
+
isFocused,
|
|
41
42
|
...rest
|
|
42
43
|
}) {
|
|
43
44
|
const manager = useVideoManager();
|
|
@@ -70,6 +71,25 @@ export function VideoFeed({
|
|
|
70
71
|
}
|
|
71
72
|
};
|
|
72
73
|
}, [manager, pauseOnUnmount]);
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (isFocused === undefined) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (!isFocused) {
|
|
79
|
+
if (manager.store.getState().surfaceId === feedSurfaceId(focusedId ?? '')) {
|
|
80
|
+
manager.pause();
|
|
81
|
+
}
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
// Screen regained focus — resume the item that was in view.
|
|
85
|
+
const item = data.find(d => d.id === focusedId);
|
|
86
|
+
if (item) {
|
|
87
|
+
manager.setSource(item, {
|
|
88
|
+
autoplay: true,
|
|
89
|
+
surfaceId: feedSurfaceId(item.id)
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}, [manager, isFocused, focusedId, data]);
|
|
73
93
|
const focus = useCallback((item, index) => {
|
|
74
94
|
setFocusedId(item?.id ?? null);
|
|
75
95
|
if (item) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useCallback","useEffect","useRef","useState","FlatList","StyleSheet","useWindowDimensions","View","useVideoManager","VideoSurface","jsx","_jsx","jsxs","_jsxs","feedSurfaceId","id","VideoFeed","data","itemHeight","resizeMode","muted","repeat","visibilityThreshold","renderOverlay","onFocusChange","pauseOnUnmount","rest","manager","height","windowHeight","focusedId","setFocusedId","mute","unmute","setRepeat","setResizeMode","pause","
|
|
1
|
+
{"version":3,"names":["useCallback","useEffect","useRef","useState","FlatList","StyleSheet","useWindowDimensions","View","useVideoManager","VideoSurface","jsx","_jsx","jsxs","_jsxs","feedSurfaceId","id","VideoFeed","data","itemHeight","resizeMode","muted","repeat","visibilityThreshold","renderOverlay","onFocusChange","pauseOnUnmount","isFocused","rest","manager","height","windowHeight","focusedId","setFocusedId","mute","unmute","setRepeat","setResizeMode","pause","undefined","store","getState","surfaceId","item","find","d","setSource","autoplay","focus","index","latest","current","viewabilityConfig","itemVisiblePercentThreshold","minimumViewTime","onViewableItemsChanged","viewableItems","focusedToken","v","isViewable","renderItem","style","styles","children","absoluteFill","focused","keyExtractor","getItemLayout","_","length","offset","showsVerticalScrollIndicator","snapToInterval","snapToAlignment","decelerationRate","create","width","backgroundColor","overflow"],"sourceRoot":"../../../src","sources":["components/VideoFeed.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { forwardRef, useEffect, useImperativeHandle } from 'react';
|
|
4
|
-
import { AppState, StyleSheet, View } from 'react-native';
|
|
3
|
+
import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
|
|
4
|
+
import { AppState, Dimensions, StyleSheet, View } from 'react-native';
|
|
5
5
|
import { useVideoManager } from "../provider/VideoContext.js";
|
|
6
|
+
import { usePlayback } from "../hooks/usePlayback.js";
|
|
6
7
|
import { useVideoEvents } from "../hooks/useVideoEvents.js";
|
|
7
8
|
import { VideoControls } from "./VideoControls.js";
|
|
8
9
|
import { VideoSurface } from "./VideoSurface.js";
|
|
@@ -31,6 +32,10 @@ export const VideoPlayer = /*#__PURE__*/forwardRef(({
|
|
|
31
32
|
orientation,
|
|
32
33
|
fullscreenOrientation,
|
|
33
34
|
pauseOnFocusLost = true,
|
|
35
|
+
isFocused,
|
|
36
|
+
live = false,
|
|
37
|
+
liveIcon,
|
|
38
|
+
thumbnail,
|
|
34
39
|
onLoadComplete,
|
|
35
40
|
onBuffering,
|
|
36
41
|
onError,
|
|
@@ -39,10 +44,21 @@ export const VideoPlayer = /*#__PURE__*/forwardRef(({
|
|
|
39
44
|
}, ref) => {
|
|
40
45
|
const manager = useVideoManager();
|
|
41
46
|
const id = surfaceId ?? `player:${source.id}`;
|
|
47
|
+
|
|
48
|
+
// Poster is shown only during the initial load — `loading` is true from
|
|
49
|
+
// setSource until onLoad, and stays false for mid-stream buffering.
|
|
50
|
+
const loading = usePlayback(s => s.loading);
|
|
51
|
+
|
|
52
|
+
// Read the latest source without retriggering effects on every render
|
|
53
|
+
// (source is usually a fresh object literal each render).
|
|
54
|
+
const sourceRef = useRef(source);
|
|
55
|
+
sourceRef.current = source;
|
|
42
56
|
useImperativeHandle(ref, () => manager, [manager]);
|
|
43
57
|
useEffect(() => {
|
|
58
|
+
// Don't autoplay a player that mounts already unfocused (isFocused
|
|
59
|
+
// false), else it'd flash play → pause on the next screen.
|
|
44
60
|
manager.setSource(source, {
|
|
45
|
-
autoplay,
|
|
61
|
+
autoplay: autoplay && isFocused !== false,
|
|
46
62
|
surfaceId: id
|
|
47
63
|
});
|
|
48
64
|
// Attach on mount / when the video identity changes. Other source
|
|
@@ -84,6 +100,37 @@ export const VideoPlayer = /*#__PURE__*/forwardRef(({
|
|
|
84
100
|
manager.setFullscreenOrientation(fullscreenOrientation);
|
|
85
101
|
return () => manager.setFullscreenOrientation(null);
|
|
86
102
|
}, [manager, fullscreenOrientation]);
|
|
103
|
+
|
|
104
|
+
// Auto fullscreen on physical rotation (YouTube-style): when
|
|
105
|
+
// `fullscreenOrientation` is a landscape value, rotating the device to
|
|
106
|
+
// landscape enters fullscreen and rotating back to portrait exits.
|
|
107
|
+
// Enters with 'auto' (not a forced lock) so the device sensor keeps
|
|
108
|
+
// driving and rotating back can exit. Requires the app to actually allow
|
|
109
|
+
// landscape (Info.plist / AndroidManifest) — otherwise the window never
|
|
110
|
+
// reports landscape and this stays inert.
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
const wantsLandscape = fullscreenOrientation === 'landscape' || fullscreenOrientation === 'inverted-landscape';
|
|
113
|
+
if (!wantsLandscape) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const onChange = ({
|
|
117
|
+
window
|
|
118
|
+
}) => {
|
|
119
|
+
const landscape = window.width > window.height;
|
|
120
|
+
const state = manager.store.getState();
|
|
121
|
+
// Ignore when another video owns the engine and we're not fullscreen.
|
|
122
|
+
if (state.surfaceId !== id && !state.fullscreen) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (landscape && !state.fullscreen) {
|
|
126
|
+
manager.enterFullscreen('auto');
|
|
127
|
+
} else if (!landscape && state.fullscreen) {
|
|
128
|
+
manager.exitFullscreen();
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const sub = Dimensions.addEventListener('change', onChange);
|
|
132
|
+
return () => sub.remove();
|
|
133
|
+
}, [manager, id, fullscreenOrientation]);
|
|
87
134
|
useEffect(() => {
|
|
88
135
|
if (!pauseOnFocusLost) {
|
|
89
136
|
return;
|
|
@@ -97,6 +144,29 @@ export const VideoPlayer = /*#__PURE__*/forwardRef(({
|
|
|
97
144
|
});
|
|
98
145
|
return () => sub.remove();
|
|
99
146
|
}, [manager, id, pauseOnFocusLost]);
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
if (isFocused === undefined) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const state = manager.store.getState();
|
|
152
|
+
if (isFocused) {
|
|
153
|
+
// Regained screen focus. If the engine is still ours, just resume;
|
|
154
|
+
// otherwise it moved to another video while we were blurred — reclaim
|
|
155
|
+
// it (same-id handoff means no reload if it never actually left).
|
|
156
|
+
if (state.surfaceId === id && state.currentVideo?.id === sourceRef.current.id) {
|
|
157
|
+
manager.play();
|
|
158
|
+
} else {
|
|
159
|
+
manager.setSource(sourceRef.current, {
|
|
160
|
+
autoplay: true,
|
|
161
|
+
surfaceId: id
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
} else if (state.surfaceId === id) {
|
|
165
|
+
// Lost screen focus while playing our video — pause. Guarded so we
|
|
166
|
+
// never pause a video that has already handed off elsewhere.
|
|
167
|
+
manager.pause();
|
|
168
|
+
}
|
|
169
|
+
}, [manager, id, isFocused]);
|
|
100
170
|
useVideoEvents({
|
|
101
171
|
onLoad: onLoadComplete,
|
|
102
172
|
onBuffer: e => onBuffering?.(e.buffering),
|
|
@@ -108,7 +178,14 @@ export const VideoPlayer = /*#__PURE__*/forwardRef(({
|
|
|
108
178
|
children: [/*#__PURE__*/_jsx(VideoSurface, {
|
|
109
179
|
surfaceId: id,
|
|
110
180
|
style: styles.surface
|
|
111
|
-
}),
|
|
181
|
+
}), thumbnail && loading ? /*#__PURE__*/_jsx(View, {
|
|
182
|
+
style: styles.surface,
|
|
183
|
+
pointerEvents: "none",
|
|
184
|
+
children: thumbnail()
|
|
185
|
+
}) : null, controls ? /*#__PURE__*/_jsx(VideoControls, {
|
|
186
|
+
live: live,
|
|
187
|
+
liveIcon: liveIcon
|
|
188
|
+
}) : null]
|
|
112
189
|
});
|
|
113
190
|
});
|
|
114
191
|
VideoPlayer.displayName = 'VideoPlayer';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["forwardRef","useEffect","useImperativeHandle","AppState","StyleSheet","View","useVideoManager","useVideoEvents","VideoControls","VideoSurface","jsx","_jsx","jsxs","_jsxs","VideoPlayer","source","autoplay","surfaceId","controls","resizeMode","repeat","muted","orientation","fullscreenOrientation","pauseOnFocusLost","onLoadComplete","onBuffering","onError","style","rest","ref","manager","id","setSource","setResizeMode","undefined","setRepeat","mute","unmute","setOrientation","setFullscreenOrientation","
|
|
1
|
+
{"version":3,"names":["forwardRef","useEffect","useImperativeHandle","useRef","AppState","Dimensions","StyleSheet","View","useVideoManager","usePlayback","useVideoEvents","VideoControls","VideoSurface","jsx","_jsx","jsxs","_jsxs","VideoPlayer","source","autoplay","surfaceId","controls","resizeMode","repeat","muted","orientation","fullscreenOrientation","pauseOnFocusLost","isFocused","live","liveIcon","thumbnail","onLoadComplete","onBuffering","onError","style","rest","ref","manager","id","loading","s","sourceRef","current","setSource","setResizeMode","undefined","setRepeat","mute","unmute","setOrientation","setFullscreenOrientation","wantsLandscape","onChange","window","landscape","width","height","state","store","getState","fullscreen","enterFullscreen","exitFullscreen","sub","addEventListener","remove","next","pause","currentVideo","play","onLoad","onBuffer","e","buffering","styles","container","children","surface","pointerEvents","displayName","create","backgroundColor","overflow","position","top","left","right","bottom"],"sourceRoot":"../../../src","sources":["components/VideoPlayer.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import Svg, { Line, Path, Polygon, Rect } from 'react-native-svg';
|
|
3
|
+
import Svg, { ClipPath, Defs, G, Line, Path, Polygon, Rect } from 'react-native-svg';
|
|
4
4
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
5
5
|
export function PlayIcon({
|
|
6
6
|
size,
|
|
@@ -41,6 +41,37 @@ export function PauseIcon({
|
|
|
41
41
|
export function CloseIcon({
|
|
42
42
|
size,
|
|
43
43
|
color
|
|
44
|
+
}) {
|
|
45
|
+
return /*#__PURE__*/_jsxs(Svg, {
|
|
46
|
+
width: size || 20,
|
|
47
|
+
height: size || 21,
|
|
48
|
+
viewBox: "0 0 20 21",
|
|
49
|
+
fill: "none",
|
|
50
|
+
children: [/*#__PURE__*/_jsx(G, {
|
|
51
|
+
clipPath: "url(#clip0_25_6005)",
|
|
52
|
+
children: /*#__PURE__*/_jsx(Path, {
|
|
53
|
+
d: "M12.5 16.3069L6.25 10.0569L12.5 3.80688",
|
|
54
|
+
stroke: color || '#000000',
|
|
55
|
+
strokeWidth: 1.25,
|
|
56
|
+
strokeLinecap: "round",
|
|
57
|
+
strokeLinejoin: "round"
|
|
58
|
+
})
|
|
59
|
+
}), /*#__PURE__*/_jsx(Defs, {
|
|
60
|
+
children: /*#__PURE__*/_jsx(ClipPath, {
|
|
61
|
+
id: "clip0_25_6005",
|
|
62
|
+
children: /*#__PURE__*/_jsx(Rect, {
|
|
63
|
+
width: size || 20,
|
|
64
|
+
height: size || 21,
|
|
65
|
+
fill: "white",
|
|
66
|
+
transform: "translate(0 0.0568848)"
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
})]
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
export function BackIcon({
|
|
73
|
+
size,
|
|
74
|
+
color
|
|
44
75
|
}) {
|
|
45
76
|
return /*#__PURE__*/_jsxs(Svg, {
|
|
46
77
|
width: size,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["Svg","Line","Path","Polygon","Rect","jsx","_jsx","jsxs","_jsxs","PlayIcon","size","color","width","height","viewBox","fill","children","points","PauseIcon","x","y","CloseIcon","stroke","strokeWidth","strokeLinecap","
|
|
1
|
+
{"version":3,"names":["Svg","ClipPath","Defs","G","Line","Path","Polygon","Rect","jsx","_jsx","jsxs","_jsxs","PlayIcon","size","color","width","height","viewBox","fill","children","points","PauseIcon","x","y","CloseIcon","clipPath","d","stroke","strokeWidth","strokeLinecap","strokeLinejoin","id","transform","BackIcon","x1","y1","x2","y2","VolumeOnIcon","VolumeOffIcon","EnterFullscreenIcon","ExitFullscreenIcon"],"sourceRoot":"../../../src","sources":["components/icons.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
1
2
|
export interface VideoControlsProps {
|
|
2
3
|
/** Seconds jumped by double-tap. Default 10. */
|
|
3
4
|
doubleTapSeek?: number;
|
|
@@ -5,6 +6,16 @@ export interface VideoControlsProps {
|
|
|
5
6
|
hideAfter?: number;
|
|
6
7
|
/** Show the fullscreen toggle button. Default true. */
|
|
7
8
|
showFullscreenButton?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Mark this as a live stream: hides the seek bar/times and moves mute to
|
|
11
|
+
* the bottom-left. Default false.
|
|
12
|
+
*/
|
|
13
|
+
live?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Render a live indicator (e.g. a Lottie animation or a "LIVE" badge),
|
|
16
|
+
* shown in the control bar only while `live`.
|
|
17
|
+
*/
|
|
18
|
+
liveIcon?: () => ReactNode;
|
|
8
19
|
/** Called by the close (✕) button; button hidden when omitted. */
|
|
9
20
|
onClose?: () => void;
|
|
10
21
|
}
|
|
@@ -13,5 +24,5 @@ export interface VideoControlsProps {
|
|
|
13
24
|
* toggles, with tap-to-show / double-tap-to-seek gestures. Apps wanting a
|
|
14
25
|
* custom design can ignore this and build on usePlayback()/useVideo().
|
|
15
26
|
*/
|
|
16
|
-
export declare function VideoControls({ doubleTapSeek, hideAfter, showFullscreenButton, onClose, }: VideoControlsProps): import("react").JSX.Element;
|
|
27
|
+
export declare function VideoControls({ doubleTapSeek, hideAfter, showFullscreenButton, live, liveIcon, onClose, }: VideoControlsProps): import("react").JSX.Element;
|
|
17
28
|
//# sourceMappingURL=VideoControls.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoControls.d.ts","sourceRoot":"","sources":["../../../../src/components/VideoControls.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"VideoControls.d.ts","sourceRoot":"","sources":["../../../../src/components/VideoControls.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAgBf,MAAM,WAAW,kBAAkB;IACjC,gDAAgD;IAChD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,SAAS,CAAC;IAC3B,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAC5B,aAAkB,EAClB,SAAgB,EAChB,oBAA2B,EAC3B,IAAY,EACZ,QAAQ,EACR,OAAO,GACR,EAAE,kBAAkB,+BAiJpB"}
|
|
@@ -27,6 +27,13 @@ export interface VideoFeedProps<T extends VideoSource> extends Omit<FlatListProp
|
|
|
27
27
|
onFocusChange?: (item: T | null, index: number) => void;
|
|
28
28
|
/** Pause playback when the feed unmounts. Default `true`. */
|
|
29
29
|
pauseOnUnmount?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Screen-focus flag from your navigation library — e.g. React Navigation's
|
|
32
|
+
* `useIsFocused()`. `false` pauses the feed; returning to `true` resumes the
|
|
33
|
+
* item that was in view. Needed because navigating away keeps the feed
|
|
34
|
+
* mounted and the app foregrounded. Leave undefined if you don't navigate.
|
|
35
|
+
*/
|
|
36
|
+
isFocused?: boolean;
|
|
30
37
|
}
|
|
31
38
|
/**
|
|
32
39
|
* A single-engine video feed: render many videos in a scrollable list, but
|
|
@@ -45,5 +52,5 @@ export interface VideoFeedProps<T extends VideoSource> extends Omit<FlatListProp
|
|
|
45
52
|
* />
|
|
46
53
|
* ```
|
|
47
54
|
*/
|
|
48
|
-
export declare function VideoFeed<T extends VideoSource>({ data, itemHeight, resizeMode, muted, repeat, visibilityThreshold, renderOverlay, onFocusChange, pauseOnUnmount, ...rest }: VideoFeedProps<T>): import("react").JSX.Element;
|
|
55
|
+
export declare function VideoFeed<T extends VideoSource>({ data, itemHeight, resizeMode, muted, repeat, visibilityThreshold, renderOverlay, onFocusChange, pauseOnUnmount, isFocused, ...rest }: VideoFeedProps<T>): import("react").JSX.Element;
|
|
49
56
|
//# sourceMappingURL=VideoFeed.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoFeed.d.ts","sourceRoot":"","sources":["../../../../src/components/VideoFeed.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,EAKL,KAAK,aAAa,EACnB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,mBAAgB,CAAC;AAG9D,8CAA8C;AAC9C,eAAO,MAAM,aAAa,GAAI,IAAI,MAAM,WAAiB,CAAC;AAS1D,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,IAAI,EAAE,CAAC,CAAC;IACR,KAAK,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,WAAW,CAAE,SAAQ,IAAI,CACjE,aAAa,CAAC,CAAC,CAAC,EACd,MAAM,GACN,YAAY,GACZ,cAAc,GACd,wBAAwB,GACxB,mBAAmB,GACnB,eAAe,CAClB;IACC,6EAA6E;IAC7E,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,kFAAkF;IAClF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,yEAAyE;IACzE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,8CAA8C;IAC9C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,6EAA6E;IAC7E,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,+EAA+E;IAC/E,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC;IAC5D,wEAAwE;IACxE,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD,6DAA6D;IAC7D,cAAc,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"VideoFeed.d.ts","sourceRoot":"","sources":["../../../../src/components/VideoFeed.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,EAKL,KAAK,aAAa,EACnB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,mBAAgB,CAAC;AAG9D,8CAA8C;AAC9C,eAAO,MAAM,aAAa,GAAI,IAAI,MAAM,WAAiB,CAAC;AAS1D,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,IAAI,EAAE,CAAC,CAAC;IACR,KAAK,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,WAAW,CAAE,SAAQ,IAAI,CACjE,aAAa,CAAC,CAAC,CAAC,EACd,MAAM,GACN,YAAY,GACZ,cAAc,GACd,wBAAwB,GACxB,mBAAmB,GACnB,eAAe,CAClB;IACC,6EAA6E;IAC7E,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,kFAAkF;IAClF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,yEAAyE;IACzE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,8CAA8C;IAC9C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,6EAA6E;IAC7E,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,+EAA+E;IAC/E,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC;IAC5D,wEAAwE;IACxE,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD,6DAA6D;IAC7D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,WAAW,EAAE,EAC/C,IAAI,EACJ,UAAU,EACV,UAAU,EACV,KAAY,EACZ,MAAa,EACb,mBAAwB,EACxB,aAAa,EACb,aAAa,EACb,cAAqB,EACrB,SAAS,EACT,GAAG,IAAI,EACR,EAAE,cAAc,CAAC,CAAC,CAAC,+BA2HnB"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
1
2
|
import { type ViewProps } from 'react-native';
|
|
2
3
|
import { VideoManager } from '../core/VideoManager.js';
|
|
3
4
|
import type { VideoEventMap } from '../types/events.js';
|
|
@@ -30,6 +31,11 @@ export interface VideoPlayerProps extends ViewProps {
|
|
|
30
31
|
* `'portrait'` for a vertical video). Applied when fullscreen opens —
|
|
31
32
|
* including via the built-in controls' fullscreen button — and restored
|
|
32
33
|
* when it closes, so the rest of the app is unaffected.
|
|
34
|
+
*
|
|
35
|
+
* When set to a landscape value (`'landscape'` / `'inverted-landscape'`),
|
|
36
|
+
* physically rotating the device to landscape also auto-enters fullscreen,
|
|
37
|
+
* and rotating back to portrait exits it (YouTube-style). This needs the
|
|
38
|
+
* app to allow landscape orientations at the OS level.
|
|
33
39
|
*/
|
|
34
40
|
fullscreenOrientation?: OrientationLock;
|
|
35
41
|
/**
|
|
@@ -39,6 +45,35 @@ export interface VideoPlayerProps extends ViewProps {
|
|
|
39
45
|
* Only the focused player acts, so a video attached elsewhere is untouched.
|
|
40
46
|
*/
|
|
41
47
|
pauseOnFocusLost?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Screen-focus flag from your navigation library — e.g. React Navigation's
|
|
50
|
+
* `useIsFocused()`. `false` pauses this player; returning to `true` reclaims
|
|
51
|
+
* the engine and resumes this video. This is the only reliable way to pause
|
|
52
|
+
* on screen navigation (React Navigation keeps screens mounted and the app
|
|
53
|
+
* stays foregrounded, so unmount/AppState never fire). Leave undefined if
|
|
54
|
+
* you don't use navigation.
|
|
55
|
+
*
|
|
56
|
+
* ```tsx
|
|
57
|
+
* const isFocused = useIsFocused(); // @react-navigation/native
|
|
58
|
+
* <VideoPlayer source={video} isFocused={isFocused} />
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
isFocused?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Mark this as a live stream: the built-in controls hide the seek bar/times
|
|
64
|
+
* and show `liveIcon` (if given). Default false.
|
|
65
|
+
*/
|
|
66
|
+
live?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Render a live indicator shown in the controls while `live` — e.g. a
|
|
69
|
+
* Lottie animation or a "LIVE" badge: `liveIcon={() => <LottieView … />}`.
|
|
70
|
+
*/
|
|
71
|
+
liveIcon?: () => ReactNode;
|
|
72
|
+
/**
|
|
73
|
+
* Render a poster shown over the video only during the initial load
|
|
74
|
+
* (before the first frame) — e.g. `thumbnail={() => <Image … />}`.
|
|
75
|
+
*/
|
|
76
|
+
thumbnail?: () => ReactNode;
|
|
42
77
|
/** Fires once metadata (duration, dimensions) is available. */
|
|
43
78
|
onLoadComplete?: (info: VideoEventMap['onLoad']) => void;
|
|
44
79
|
/** Fires whenever buffering starts or stops. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoPlayer.d.ts","sourceRoot":"","sources":["../../../../src/components/VideoPlayer.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"VideoPlayer.d.ts","sourceRoot":"","sources":["../../../../src/components/VideoPlayer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAsB,CAAC;AAIpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAiB,CAAC;AACrD,OAAO,KAAK,EACV,eAAe,EACf,UAAU,EACV,UAAU,EACV,WAAW,EACZ,MAAM,mBAAgB,CAAC;AAIxB,MAAM,WAAW,gBAAiB,SAAQ,SAAS;IACjD,MAAM,EAAE,WAAW,CAAC;IACpB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,iDAAiD;IACjD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,qBAAqB;IACrB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B;;;;;;;;;;OAUG;IACH,qBAAqB,CAAC,EAAE,eAAe,CAAC;IACxC;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;;;;;;OAYG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,SAAS,CAAC;IAC3B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,SAAS,CAAC;IAC5B,+DAA+D;IAC/D,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;IACzD,gDAAgD;IAChD,WAAW,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CACvC;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,2GAyLvB,CAAC"}
|
|
@@ -5,6 +5,7 @@ interface IconProps {
|
|
|
5
5
|
export declare function PlayIcon({ size, color }: IconProps): import("react").JSX.Element;
|
|
6
6
|
export declare function PauseIcon({ size, color }: IconProps): import("react").JSX.Element;
|
|
7
7
|
export declare function CloseIcon({ size, color }: IconProps): import("react").JSX.Element;
|
|
8
|
+
export declare function BackIcon({ size, color }: IconProps): import("react").JSX.Element;
|
|
8
9
|
export declare function VolumeOnIcon({ size, color }: IconProps): import("react").JSX.Element;
|
|
9
10
|
export declare function VolumeOffIcon({ size, color }: IconProps): import("react").JSX.Element;
|
|
10
11
|
export declare function EnterFullscreenIcon({ size, color }: IconProps): import("react").JSX.Element;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../../../../src/components/icons.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../../../../src/components/icons.tsx"],"names":[],"mappings":"AAUA,UAAU,SAAS;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,SAAS,+BAMlD;AAED,wBAAgB,SAAS,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,SAAS,+BAOnD;AAED,wBAAgB,SAAS,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,SAAS,+BAwBnD;AAED,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,SAAS,+BAelD;AAED,wBAAgB,YAAY,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,SAAS,+BAgBtD;AAED,wBAAgB,aAAa,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,SAAS,+BAiBvD;AAED,wBAAgB,mBAAmB,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,SAAS,+BAkB7D;AAED,wBAAgB,kBAAkB,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,SAAS,+BAkB5D"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-video-provider",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "Singleton-engine video library for React Native (one native player, many surfaces)",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
package/scripts/postinstall.js
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Prints a banner
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Prints a banner. Wrapped so it can NEVER fail a consumer's `npm install`
|
|
5
|
+
* or native build — any error is swallowed and the process always exits 0.
|
|
6
|
+
*
|
|
7
|
+
* Run modes:
|
|
8
|
+
* node scripts/postinstall.js → install banner (skipped in CI /
|
|
9
|
+
* non-interactive shells)
|
|
10
|
+
* node scripts/postinstall.js --force → always print (used by the
|
|
11
|
+
* Android build so it shows while
|
|
12
|
+
* compiling the app, where stdout
|
|
13
|
+
* is not a TTY)
|
|
7
14
|
*/
|
|
8
15
|
|
|
16
|
+
const force =
|
|
17
|
+
process.argv.includes('--force') || process.env.AU_VIDEO_BANNER === '1';
|
|
18
|
+
|
|
9
19
|
const BANNER = `
|
|
10
20
|
++
|
|
11
21
|
+++ +++ +++++++++++ ++
|
|
@@ -24,10 +34,14 @@ const BANNER = `
|
|
|
24
34
|
|
|
25
35
|
try {
|
|
26
36
|
const quiet = process.env.CI || process.env.npm_config_loglevel === 'silent';
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
37
|
+
const show = force || (!quiet && process.stdout && process.stdout.isTTY);
|
|
38
|
+
if (show) {
|
|
39
|
+
// Colorize only in a real terminal — under Gradle/CI (--force, not a TTY)
|
|
40
|
+
// stay plain so the log doesn't fill with raw escape codes.
|
|
41
|
+
const color = process.stdout && process.stdout.isTTY;
|
|
42
|
+
const cyan = color ? '\x1b[36m' : '';
|
|
43
|
+
const dim = color ? '\x1b[2m' : '';
|
|
44
|
+
const reset = color ? '\x1b[0m' : '';
|
|
31
45
|
process.stdout.write(cyan + BANNER + reset + '\n');
|
|
32
46
|
process.stdout.write(
|
|
33
47
|
dim +
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useRef,
|
|
5
|
+
useState,
|
|
6
|
+
type ReactNode,
|
|
7
|
+
} from 'react';
|
|
2
8
|
import {
|
|
3
9
|
Pressable,
|
|
4
10
|
StyleSheet,
|
|
@@ -11,7 +17,7 @@ import { useVideoManager } from '../provider/VideoContext';
|
|
|
11
17
|
import { usePlayback } from '../hooks/usePlayback';
|
|
12
18
|
import { formatTime } from '../utils/formatTime';
|
|
13
19
|
import { GestureOverlay } from './GestureOverlay';
|
|
14
|
-
import {
|
|
20
|
+
import { BackIcon } from './icons';
|
|
15
21
|
import SvgIcons from './SvgIcons';
|
|
16
22
|
|
|
17
23
|
export interface VideoControlsProps {
|
|
@@ -21,6 +27,16 @@ export interface VideoControlsProps {
|
|
|
21
27
|
hideAfter?: number;
|
|
22
28
|
/** Show the fullscreen toggle button. Default true. */
|
|
23
29
|
showFullscreenButton?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Mark this as a live stream: hides the seek bar/times and moves mute to
|
|
32
|
+
* the bottom-left. Default false.
|
|
33
|
+
*/
|
|
34
|
+
live?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Render a live indicator (e.g. a Lottie animation or a "LIVE" badge),
|
|
37
|
+
* shown in the control bar only while `live`.
|
|
38
|
+
*/
|
|
39
|
+
liveIcon?: () => ReactNode;
|
|
24
40
|
/** Called by the close (✕) button; button hidden when omitted. */
|
|
25
41
|
onClose?: () => void;
|
|
26
42
|
}
|
|
@@ -34,22 +50,18 @@ export function VideoControls({
|
|
|
34
50
|
doubleTapSeek = 10,
|
|
35
51
|
hideAfter = 3000,
|
|
36
52
|
showFullscreenButton = true,
|
|
53
|
+
live = false,
|
|
54
|
+
liveIcon,
|
|
37
55
|
onClose,
|
|
38
56
|
}: VideoControlsProps) {
|
|
39
57
|
const manager = useVideoManager();
|
|
40
|
-
const status = usePlayback((s) => s.status);
|
|
41
58
|
const playing = usePlayback((s) => s.playing);
|
|
42
59
|
const buffering = usePlayback((s) => s.buffering);
|
|
43
|
-
const loading = usePlayback((s) => s.loading);
|
|
44
60
|
const position = usePlayback((s) => s.position);
|
|
45
61
|
const duration = usePlayback((s) => s.duration);
|
|
46
62
|
const muted = usePlayback((s) => s.muted);
|
|
47
63
|
const fullscreen = usePlayback((s) => s.fullscreen);
|
|
48
64
|
|
|
49
|
-
// No known duration once loaded — a live stream or a video with no fixed
|
|
50
|
-
// end. There's nothing to seek, so hide the seek bar entirely.
|
|
51
|
-
const live = status !== 'idle' && !loading && duration <= 0;
|
|
52
|
-
|
|
53
65
|
const [visible, setVisible] = useState(true);
|
|
54
66
|
const hideTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
55
67
|
const trackWidth = useRef(0);
|
|
@@ -131,7 +143,7 @@ export function VideoControls({
|
|
|
131
143
|
<View style={styles.topRow}>
|
|
132
144
|
{onClose ? (
|
|
133
145
|
<Pressable style={styles.button} onPress={onClose} hitSlop={8}>
|
|
134
|
-
<
|
|
146
|
+
<BackIcon size={18} color="#fff" />
|
|
135
147
|
</Pressable>
|
|
136
148
|
) : (
|
|
137
149
|
<View />
|
|
@@ -159,6 +171,7 @@ export function VideoControls({
|
|
|
159
171
|
<View style={styles.bottomRow}>
|
|
160
172
|
{live ? (
|
|
161
173
|
<>
|
|
174
|
+
{liveIcon ? liveIcon() : null}
|
|
162
175
|
{muteButton}
|
|
163
176
|
<View style={styles.spacer} />
|
|
164
177
|
{fullscreenButton}
|
|
@@ -59,6 +59,13 @@ export interface VideoFeedProps<T extends VideoSource> extends Omit<
|
|
|
59
59
|
onFocusChange?: (item: T | null, index: number) => void;
|
|
60
60
|
/** Pause playback when the feed unmounts. Default `true`. */
|
|
61
61
|
pauseOnUnmount?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Screen-focus flag from your navigation library — e.g. React Navigation's
|
|
64
|
+
* `useIsFocused()`. `false` pauses the feed; returning to `true` resumes the
|
|
65
|
+
* item that was in view. Needed because navigating away keeps the feed
|
|
66
|
+
* mounted and the app foregrounded. Leave undefined if you don't navigate.
|
|
67
|
+
*/
|
|
68
|
+
isFocused?: boolean;
|
|
62
69
|
}
|
|
63
70
|
|
|
64
71
|
/**
|
|
@@ -88,6 +95,7 @@ export function VideoFeed<T extends VideoSource>({
|
|
|
88
95
|
renderOverlay,
|
|
89
96
|
onFocusChange,
|
|
90
97
|
pauseOnUnmount = true,
|
|
98
|
+
isFocused,
|
|
91
99
|
...rest
|
|
92
100
|
}: VideoFeedProps<T>) {
|
|
93
101
|
const manager = useVideoManager();
|
|
@@ -122,6 +130,28 @@ export function VideoFeed<T extends VideoSource>({
|
|
|
122
130
|
};
|
|
123
131
|
}, [manager, pauseOnUnmount]);
|
|
124
132
|
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
if (isFocused === undefined) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (!isFocused) {
|
|
138
|
+
if (
|
|
139
|
+
manager.store.getState().surfaceId === feedSurfaceId(focusedId ?? '')
|
|
140
|
+
) {
|
|
141
|
+
manager.pause();
|
|
142
|
+
}
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
// Screen regained focus — resume the item that was in view.
|
|
146
|
+
const item = data.find((d) => d.id === focusedId);
|
|
147
|
+
if (item) {
|
|
148
|
+
manager.setSource(item, {
|
|
149
|
+
autoplay: true,
|
|
150
|
+
surfaceId: feedSurfaceId(item.id),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}, [manager, isFocused, focusedId, data]);
|
|
154
|
+
|
|
125
155
|
const focus = useCallback(
|
|
126
156
|
(item: T | null, index: number) => {
|
|
127
157
|
setFocusedId(item?.id ?? null);
|
|
@@ -1,7 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useEffect,
|
|
4
|
+
useImperativeHandle,
|
|
5
|
+
useRef,
|
|
6
|
+
type ReactNode,
|
|
7
|
+
} from 'react';
|
|
8
|
+
import {
|
|
9
|
+
AppState,
|
|
10
|
+
Dimensions,
|
|
11
|
+
StyleSheet,
|
|
12
|
+
View,
|
|
13
|
+
type ViewProps,
|
|
14
|
+
} from 'react-native';
|
|
3
15
|
import { VideoManager } from '../core/VideoManager';
|
|
4
16
|
import { useVideoManager } from '../provider/VideoContext';
|
|
17
|
+
import { usePlayback } from '../hooks/usePlayback';
|
|
5
18
|
import { useVideoEvents } from '../hooks/useVideoEvents';
|
|
6
19
|
import type { VideoEventMap } from '../types/events';
|
|
7
20
|
import type {
|
|
@@ -41,6 +54,11 @@ export interface VideoPlayerProps extends ViewProps {
|
|
|
41
54
|
* `'portrait'` for a vertical video). Applied when fullscreen opens —
|
|
42
55
|
* including via the built-in controls' fullscreen button — and restored
|
|
43
56
|
* when it closes, so the rest of the app is unaffected.
|
|
57
|
+
*
|
|
58
|
+
* When set to a landscape value (`'landscape'` / `'inverted-landscape'`),
|
|
59
|
+
* physically rotating the device to landscape also auto-enters fullscreen,
|
|
60
|
+
* and rotating back to portrait exits it (YouTube-style). This needs the
|
|
61
|
+
* app to allow landscape orientations at the OS level.
|
|
44
62
|
*/
|
|
45
63
|
fullscreenOrientation?: OrientationLock;
|
|
46
64
|
/**
|
|
@@ -50,6 +68,35 @@ export interface VideoPlayerProps extends ViewProps {
|
|
|
50
68
|
* Only the focused player acts, so a video attached elsewhere is untouched.
|
|
51
69
|
*/
|
|
52
70
|
pauseOnFocusLost?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Screen-focus flag from your navigation library — e.g. React Navigation's
|
|
73
|
+
* `useIsFocused()`. `false` pauses this player; returning to `true` reclaims
|
|
74
|
+
* the engine and resumes this video. This is the only reliable way to pause
|
|
75
|
+
* on screen navigation (React Navigation keeps screens mounted and the app
|
|
76
|
+
* stays foregrounded, so unmount/AppState never fire). Leave undefined if
|
|
77
|
+
* you don't use navigation.
|
|
78
|
+
*
|
|
79
|
+
* ```tsx
|
|
80
|
+
* const isFocused = useIsFocused(); // @react-navigation/native
|
|
81
|
+
* <VideoPlayer source={video} isFocused={isFocused} />
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
isFocused?: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Mark this as a live stream: the built-in controls hide the seek bar/times
|
|
87
|
+
* and show `liveIcon` (if given). Default false.
|
|
88
|
+
*/
|
|
89
|
+
live?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Render a live indicator shown in the controls while `live` — e.g. a
|
|
92
|
+
* Lottie animation or a "LIVE" badge: `liveIcon={() => <LottieView … />}`.
|
|
93
|
+
*/
|
|
94
|
+
liveIcon?: () => ReactNode;
|
|
95
|
+
/**
|
|
96
|
+
* Render a poster shown over the video only during the initial load
|
|
97
|
+
* (before the first frame) — e.g. `thumbnail={() => <Image … />}`.
|
|
98
|
+
*/
|
|
99
|
+
thumbnail?: () => ReactNode;
|
|
53
100
|
/** Fires once metadata (duration, dimensions) is available. */
|
|
54
101
|
onLoadComplete?: (info: VideoEventMap['onLoad']) => void;
|
|
55
102
|
/** Fires whenever buffering starts or stops. */
|
|
@@ -83,6 +130,10 @@ export const VideoPlayer = forwardRef<VideoManager, VideoPlayerProps>(
|
|
|
83
130
|
orientation,
|
|
84
131
|
fullscreenOrientation,
|
|
85
132
|
pauseOnFocusLost = true,
|
|
133
|
+
isFocused,
|
|
134
|
+
live = false,
|
|
135
|
+
liveIcon,
|
|
136
|
+
thumbnail,
|
|
86
137
|
onLoadComplete,
|
|
87
138
|
onBuffering,
|
|
88
139
|
onError,
|
|
@@ -94,10 +145,24 @@ export const VideoPlayer = forwardRef<VideoManager, VideoPlayerProps>(
|
|
|
94
145
|
const manager = useVideoManager();
|
|
95
146
|
const id = surfaceId ?? `player:${source.id}`;
|
|
96
147
|
|
|
148
|
+
// Poster is shown only during the initial load — `loading` is true from
|
|
149
|
+
// setSource until onLoad, and stays false for mid-stream buffering.
|
|
150
|
+
const loading = usePlayback((s) => s.loading);
|
|
151
|
+
|
|
152
|
+
// Read the latest source without retriggering effects on every render
|
|
153
|
+
// (source is usually a fresh object literal each render).
|
|
154
|
+
const sourceRef = useRef(source);
|
|
155
|
+
sourceRef.current = source;
|
|
156
|
+
|
|
97
157
|
useImperativeHandle(ref, () => manager, [manager]);
|
|
98
158
|
|
|
99
159
|
useEffect(() => {
|
|
100
|
-
|
|
160
|
+
// Don't autoplay a player that mounts already unfocused (isFocused
|
|
161
|
+
// false), else it'd flash play → pause on the next screen.
|
|
162
|
+
manager.setSource(source, {
|
|
163
|
+
autoplay: autoplay && isFocused !== false,
|
|
164
|
+
surfaceId: id,
|
|
165
|
+
});
|
|
101
166
|
// Attach on mount / when the video identity changes. Other source
|
|
102
167
|
// fields (title, headers) don't retrigger: identity is source.id.
|
|
103
168
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -143,6 +208,41 @@ export const VideoPlayer = forwardRef<VideoManager, VideoPlayerProps>(
|
|
|
143
208
|
return () => manager.setFullscreenOrientation(null);
|
|
144
209
|
}, [manager, fullscreenOrientation]);
|
|
145
210
|
|
|
211
|
+
// Auto fullscreen on physical rotation (YouTube-style): when
|
|
212
|
+
// `fullscreenOrientation` is a landscape value, rotating the device to
|
|
213
|
+
// landscape enters fullscreen and rotating back to portrait exits.
|
|
214
|
+
// Enters with 'auto' (not a forced lock) so the device sensor keeps
|
|
215
|
+
// driving and rotating back can exit. Requires the app to actually allow
|
|
216
|
+
// landscape (Info.plist / AndroidManifest) — otherwise the window never
|
|
217
|
+
// reports landscape and this stays inert.
|
|
218
|
+
useEffect(() => {
|
|
219
|
+
const wantsLandscape =
|
|
220
|
+
fullscreenOrientation === 'landscape' ||
|
|
221
|
+
fullscreenOrientation === 'inverted-landscape';
|
|
222
|
+
if (!wantsLandscape) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const onChange = ({
|
|
226
|
+
window,
|
|
227
|
+
}: {
|
|
228
|
+
window: { width: number; height: number };
|
|
229
|
+
}) => {
|
|
230
|
+
const landscape = window.width > window.height;
|
|
231
|
+
const state = manager.store.getState();
|
|
232
|
+
// Ignore when another video owns the engine and we're not fullscreen.
|
|
233
|
+
if (state.surfaceId !== id && !state.fullscreen) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (landscape && !state.fullscreen) {
|
|
237
|
+
manager.enterFullscreen('auto');
|
|
238
|
+
} else if (!landscape && state.fullscreen) {
|
|
239
|
+
manager.exitFullscreen();
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
const sub = Dimensions.addEventListener('change', onChange);
|
|
243
|
+
return () => sub.remove();
|
|
244
|
+
}, [manager, id, fullscreenOrientation]);
|
|
245
|
+
|
|
146
246
|
useEffect(() => {
|
|
147
247
|
if (!pauseOnFocusLost) {
|
|
148
248
|
return;
|
|
@@ -157,6 +257,33 @@ export const VideoPlayer = forwardRef<VideoManager, VideoPlayerProps>(
|
|
|
157
257
|
return () => sub.remove();
|
|
158
258
|
}, [manager, id, pauseOnFocusLost]);
|
|
159
259
|
|
|
260
|
+
useEffect(() => {
|
|
261
|
+
if (isFocused === undefined) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const state = manager.store.getState();
|
|
265
|
+
if (isFocused) {
|
|
266
|
+
// Regained screen focus. If the engine is still ours, just resume;
|
|
267
|
+
// otherwise it moved to another video while we were blurred — reclaim
|
|
268
|
+
// it (same-id handoff means no reload if it never actually left).
|
|
269
|
+
if (
|
|
270
|
+
state.surfaceId === id &&
|
|
271
|
+
state.currentVideo?.id === sourceRef.current.id
|
|
272
|
+
) {
|
|
273
|
+
manager.play();
|
|
274
|
+
} else {
|
|
275
|
+
manager.setSource(sourceRef.current, {
|
|
276
|
+
autoplay: true,
|
|
277
|
+
surfaceId: id,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
} else if (state.surfaceId === id) {
|
|
281
|
+
// Lost screen focus while playing our video — pause. Guarded so we
|
|
282
|
+
// never pause a video that has already handed off elsewhere.
|
|
283
|
+
manager.pause();
|
|
284
|
+
}
|
|
285
|
+
}, [manager, id, isFocused]);
|
|
286
|
+
|
|
160
287
|
useVideoEvents({
|
|
161
288
|
onLoad: onLoadComplete,
|
|
162
289
|
onBuffer: (e) => onBuffering?.(e.buffering),
|
|
@@ -166,7 +293,12 @@ export const VideoPlayer = forwardRef<VideoManager, VideoPlayerProps>(
|
|
|
166
293
|
return (
|
|
167
294
|
<View style={[styles.container, style]} {...rest}>
|
|
168
295
|
<VideoSurface surfaceId={id} style={styles.surface} />
|
|
169
|
-
{
|
|
296
|
+
{thumbnail && loading ? (
|
|
297
|
+
<View style={styles.surface} pointerEvents="none">
|
|
298
|
+
{thumbnail()}
|
|
299
|
+
</View>
|
|
300
|
+
) : null}
|
|
301
|
+
{controls ? <VideoControls live={live} liveIcon={liveIcon} /> : null}
|
|
170
302
|
</View>
|
|
171
303
|
);
|
|
172
304
|
}
|
package/src/components/icons.tsx
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import Svg, {
|
|
1
|
+
import Svg, {
|
|
2
|
+
ClipPath,
|
|
3
|
+
Defs,
|
|
4
|
+
G,
|
|
5
|
+
Line,
|
|
6
|
+
Path,
|
|
7
|
+
Polygon,
|
|
8
|
+
Rect,
|
|
9
|
+
} from 'react-native-svg';
|
|
2
10
|
|
|
3
11
|
interface IconProps {
|
|
4
12
|
size: number;
|
|
@@ -23,6 +31,32 @@ export function PauseIcon({ size, color }: IconProps) {
|
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
export function CloseIcon({ size, color }: IconProps) {
|
|
34
|
+
return (
|
|
35
|
+
<Svg width={size || 20} height={size || 21} viewBox="0 0 20 21" fill="none">
|
|
36
|
+
<G clipPath="url(#clip0_25_6005)">
|
|
37
|
+
<Path
|
|
38
|
+
d="M12.5 16.3069L6.25 10.0569L12.5 3.80688"
|
|
39
|
+
stroke={color || '#000000'}
|
|
40
|
+
strokeWidth={1.25}
|
|
41
|
+
strokeLinecap="round"
|
|
42
|
+
strokeLinejoin="round"
|
|
43
|
+
/>
|
|
44
|
+
</G>
|
|
45
|
+
<Defs>
|
|
46
|
+
<ClipPath id="clip0_25_6005">
|
|
47
|
+
<Rect
|
|
48
|
+
width={size || 20}
|
|
49
|
+
height={size || 21}
|
|
50
|
+
fill="white"
|
|
51
|
+
transform="translate(0 0.0568848)"
|
|
52
|
+
/>
|
|
53
|
+
</ClipPath>
|
|
54
|
+
</Defs>
|
|
55
|
+
</Svg>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function BackIcon({ size, color }: IconProps) {
|
|
26
60
|
return (
|
|
27
61
|
<Svg
|
|
28
62
|
width={size}
|