react-native-video-provider 0.2.4 → 0.2.5
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 +72 -3
- package/android/build.gradle +25 -0
- package/lib/module/components/VideoFeed.js +20 -0
- package/lib/module/components/VideoFeed.js.map +1 -1
- package/lib/module/components/VideoPlayer.js +33 -2
- package/lib/module/components/VideoPlayer.js.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 +14 -0
- package/lib/typescript/src/components/VideoPlayer.d.ts.map +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.js +21 -7
- package/src/components/VideoFeed.tsx +30 -0
- package/src/components/VideoPlayer.tsx +54 -2
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
|
|
|
@@ -131,7 +141,66 @@ player.attach('feed'); // render here
|
|
|
131
141
|
```
|
|
132
142
|
|
|
133
143
|
`<VideoPlayer>` is the convenience wrapper that does `setSource` + `attach` +
|
|
134
|
-
optional controls in one component.
|
|
144
|
+
optional controls in one component. Handy props:
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
<VideoPlayer
|
|
148
|
+
source={video}
|
|
149
|
+
autoplay muted repeat // playback flags
|
|
150
|
+
resizeMode="contain" // contain | cover | stretch
|
|
151
|
+
controls // built-in chrome (SVG icons)
|
|
152
|
+
onLoadComplete={(m) => {}} // duration/dimensions ready
|
|
153
|
+
onBuffering={(b) => {}}
|
|
154
|
+
onError={(e) => {}}
|
|
155
|
+
ref={playerRef} // → the VideoManager (playerRef.current.seek(…))
|
|
156
|
+
/>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
For a live stream (no known duration), the built-in controls automatically
|
|
160
|
+
hide the seek bar and show just mute + fullscreen.
|
|
161
|
+
|
|
162
|
+
## Video feed (single engine, only the focused one plays)
|
|
163
|
+
|
|
164
|
+
`<VideoFeed>` is a TikTok/Reels-style vertical feed. It renders many videos in
|
|
165
|
+
a FlatList but plays **only the one scrolled into focus** — on the same single
|
|
166
|
+
engine, so memory and CPU stay flat no matter how long the feed. Each item is
|
|
167
|
+
just a surface; scrolling hands the one player off to the focused item.
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
import { VideoFeed } from 'react-native-video-provider';
|
|
171
|
+
|
|
172
|
+
<VideoFeed
|
|
173
|
+
data={videos} // [{ id, uri, title? }, …] — each needs a stable id
|
|
174
|
+
renderOverlay={({ item, focused }) => (
|
|
175
|
+
<Caption title={item.title} paused={!focused} />
|
|
176
|
+
)}
|
|
177
|
+
/>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Any extra `FlatList` prop passes through (`onEndReached` for infinite scroll,
|
|
181
|
+
`ListHeaderComponent`, …).
|
|
182
|
+
|
|
183
|
+
## Pausing on focus loss
|
|
184
|
+
|
|
185
|
+
Both `<VideoPlayer>` and `<VideoFeed>` pause automatically when the **app is
|
|
186
|
+
backgrounded** (opt out with `pauseOnFocusLost={false}` for background audio).
|
|
187
|
+
|
|
188
|
+
For **screen navigation** (navigating to another screen while the app stays
|
|
189
|
+
foregrounded), pass your navigation library's focus flag — React Navigation
|
|
190
|
+
keeps screens mounted, so there is no other reliable signal:
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
import { useIsFocused } from '@react-navigation/native';
|
|
194
|
+
|
|
195
|
+
function Screen() {
|
|
196
|
+
const isFocused = useIsFocused();
|
|
197
|
+
return <VideoPlayer source={video} isFocused={isFocused} />;
|
|
198
|
+
// VideoFeed takes the same prop.
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`false` pauses; returning to `true` resumes (reclaiming the engine if another
|
|
203
|
+
video took it while you were away).
|
|
135
204
|
|
|
136
205
|
## State & events
|
|
137
206
|
|
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
|
+
}
|
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { forwardRef, useEffect, useImperativeHandle } from 'react';
|
|
3
|
+
import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
|
|
4
4
|
import { AppState, StyleSheet, View } from 'react-native';
|
|
5
5
|
import { useVideoManager } from "../provider/VideoContext.js";
|
|
6
6
|
import { useVideoEvents } from "../hooks/useVideoEvents.js";
|
|
@@ -31,6 +31,7 @@ export const VideoPlayer = /*#__PURE__*/forwardRef(({
|
|
|
31
31
|
orientation,
|
|
32
32
|
fullscreenOrientation,
|
|
33
33
|
pauseOnFocusLost = true,
|
|
34
|
+
isFocused,
|
|
34
35
|
onLoadComplete,
|
|
35
36
|
onBuffering,
|
|
36
37
|
onError,
|
|
@@ -39,10 +40,17 @@ export const VideoPlayer = /*#__PURE__*/forwardRef(({
|
|
|
39
40
|
}, ref) => {
|
|
40
41
|
const manager = useVideoManager();
|
|
41
42
|
const id = surfaceId ?? `player:${source.id}`;
|
|
43
|
+
|
|
44
|
+
// Read the latest source without retriggering effects on every render
|
|
45
|
+
// (source is usually a fresh object literal each render).
|
|
46
|
+
const sourceRef = useRef(source);
|
|
47
|
+
sourceRef.current = source;
|
|
42
48
|
useImperativeHandle(ref, () => manager, [manager]);
|
|
43
49
|
useEffect(() => {
|
|
50
|
+
// Don't autoplay a player that mounts already unfocused (isFocused
|
|
51
|
+
// false), else it'd flash play → pause on the next screen.
|
|
44
52
|
manager.setSource(source, {
|
|
45
|
-
autoplay,
|
|
53
|
+
autoplay: autoplay && isFocused !== false,
|
|
46
54
|
surfaceId: id
|
|
47
55
|
});
|
|
48
56
|
// Attach on mount / when the video identity changes. Other source
|
|
@@ -97,6 +105,29 @@ export const VideoPlayer = /*#__PURE__*/forwardRef(({
|
|
|
97
105
|
});
|
|
98
106
|
return () => sub.remove();
|
|
99
107
|
}, [manager, id, pauseOnFocusLost]);
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (isFocused === undefined) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const state = manager.store.getState();
|
|
113
|
+
if (isFocused) {
|
|
114
|
+
// Regained screen focus. If the engine is still ours, just resume;
|
|
115
|
+
// otherwise it moved to another video while we were blurred — reclaim
|
|
116
|
+
// it (same-id handoff means no reload if it never actually left).
|
|
117
|
+
if (state.surfaceId === id && state.currentVideo?.id === sourceRef.current.id) {
|
|
118
|
+
manager.play();
|
|
119
|
+
} else {
|
|
120
|
+
manager.setSource(sourceRef.current, {
|
|
121
|
+
autoplay: true,
|
|
122
|
+
surfaceId: id
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
} else if (state.surfaceId === id) {
|
|
126
|
+
// Lost screen focus while playing our video — pause. Guarded so we
|
|
127
|
+
// never pause a video that has already handed off elsewhere.
|
|
128
|
+
manager.pause();
|
|
129
|
+
}
|
|
130
|
+
}, [manager, id, isFocused]);
|
|
100
131
|
useVideoEvents({
|
|
101
132
|
onLoad: onLoadComplete,
|
|
102
133
|
onBuffer: e => onBuffering?.(e.buffering),
|
|
@@ -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","sub","addEventListener","next","store","getState","pause","remove","onLoad","onBuffer","e","buffering","styles","container","children","surface","displayName","create","backgroundColor","overflow","position","top","left","right","bottom"],"sourceRoot":"../../../src","sources":["components/VideoPlayer.tsx"],"mappings":";;AAAA,SAASA,UAAU,EAAEC,SAAS,EAAEC,mBAAmB,QAAQ,OAAO;
|
|
1
|
+
{"version":3,"names":["forwardRef","useEffect","useImperativeHandle","useRef","AppState","StyleSheet","View","useVideoManager","useVideoEvents","VideoControls","VideoSurface","jsx","_jsx","jsxs","_jsxs","VideoPlayer","source","autoplay","surfaceId","controls","resizeMode","repeat","muted","orientation","fullscreenOrientation","pauseOnFocusLost","isFocused","onLoadComplete","onBuffering","onError","style","rest","ref","manager","id","sourceRef","current","setSource","setResizeMode","undefined","setRepeat","mute","unmute","setOrientation","setFullscreenOrientation","sub","addEventListener","next","store","getState","pause","remove","state","currentVideo","play","onLoad","onBuffer","e","buffering","styles","container","children","surface","displayName","create","backgroundColor","overflow","position","top","left","right","bottom"],"sourceRoot":"../../../src","sources":["components/VideoPlayer.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -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"}
|
|
@@ -39,6 +39,20 @@ export interface VideoPlayerProps extends ViewProps {
|
|
|
39
39
|
* Only the focused player acts, so a video attached elsewhere is untouched.
|
|
40
40
|
*/
|
|
41
41
|
pauseOnFocusLost?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Screen-focus flag from your navigation library — e.g. React Navigation's
|
|
44
|
+
* `useIsFocused()`. `false` pauses this player; returning to `true` reclaims
|
|
45
|
+
* the engine and resumes this video. This is the only reliable way to pause
|
|
46
|
+
* on screen navigation (React Navigation keeps screens mounted and the app
|
|
47
|
+
* stays foregrounded, so unmount/AppState never fire). Leave undefined if
|
|
48
|
+
* you don't use navigation.
|
|
49
|
+
*
|
|
50
|
+
* ```tsx
|
|
51
|
+
* const isFocused = useIsFocused(); // @react-navigation/native
|
|
52
|
+
* <VideoPlayer source={video} isFocused={isFocused} />
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
isFocused?: boolean;
|
|
42
56
|
/** Fires once metadata (duration, dimensions) is available. */
|
|
43
57
|
onLoadComplete?: (info: VideoEventMap['onLoad']) => void;
|
|
44
58
|
/** Fires whenever buffering starts or stops. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoPlayer.d.ts","sourceRoot":"","sources":["../../../../src/components/VideoPlayer.tsx"],"names":[],"mappings":"AACA,OAAO,EAA8B,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAsB,CAAC;AAGpD,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;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,eAAe,CAAC;IACxC;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,+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,
|
|
1
|
+
{"version":3,"file":"VideoPlayer.d.ts","sourceRoot":"","sources":["../../../../src/components/VideoPlayer.tsx"],"names":[],"mappings":"AACA,OAAO,EAA8B,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAsB,CAAC;AAGpD,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;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,eAAe,CAAC;IACxC;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;;;;;;OAYG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,+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,2GA0IvB,CAAC"}
|
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.5",
|
|
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 +
|
|
@@ -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,4 +1,4 @@
|
|
|
1
|
-
import { forwardRef, useEffect, useImperativeHandle } from 'react';
|
|
1
|
+
import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
|
|
2
2
|
import { AppState, StyleSheet, View, type ViewProps } from 'react-native';
|
|
3
3
|
import { VideoManager } from '../core/VideoManager';
|
|
4
4
|
import { useVideoManager } from '../provider/VideoContext';
|
|
@@ -50,6 +50,20 @@ export interface VideoPlayerProps extends ViewProps {
|
|
|
50
50
|
* Only the focused player acts, so a video attached elsewhere is untouched.
|
|
51
51
|
*/
|
|
52
52
|
pauseOnFocusLost?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Screen-focus flag from your navigation library — e.g. React Navigation's
|
|
55
|
+
* `useIsFocused()`. `false` pauses this player; returning to `true` reclaims
|
|
56
|
+
* the engine and resumes this video. This is the only reliable way to pause
|
|
57
|
+
* on screen navigation (React Navigation keeps screens mounted and the app
|
|
58
|
+
* stays foregrounded, so unmount/AppState never fire). Leave undefined if
|
|
59
|
+
* you don't use navigation.
|
|
60
|
+
*
|
|
61
|
+
* ```tsx
|
|
62
|
+
* const isFocused = useIsFocused(); // @react-navigation/native
|
|
63
|
+
* <VideoPlayer source={video} isFocused={isFocused} />
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
isFocused?: boolean;
|
|
53
67
|
/** Fires once metadata (duration, dimensions) is available. */
|
|
54
68
|
onLoadComplete?: (info: VideoEventMap['onLoad']) => void;
|
|
55
69
|
/** Fires whenever buffering starts or stops. */
|
|
@@ -83,6 +97,7 @@ export const VideoPlayer = forwardRef<VideoManager, VideoPlayerProps>(
|
|
|
83
97
|
orientation,
|
|
84
98
|
fullscreenOrientation,
|
|
85
99
|
pauseOnFocusLost = true,
|
|
100
|
+
isFocused,
|
|
86
101
|
onLoadComplete,
|
|
87
102
|
onBuffering,
|
|
88
103
|
onError,
|
|
@@ -94,10 +109,20 @@ export const VideoPlayer = forwardRef<VideoManager, VideoPlayerProps>(
|
|
|
94
109
|
const manager = useVideoManager();
|
|
95
110
|
const id = surfaceId ?? `player:${source.id}`;
|
|
96
111
|
|
|
112
|
+
// Read the latest source without retriggering effects on every render
|
|
113
|
+
// (source is usually a fresh object literal each render).
|
|
114
|
+
const sourceRef = useRef(source);
|
|
115
|
+
sourceRef.current = source;
|
|
116
|
+
|
|
97
117
|
useImperativeHandle(ref, () => manager, [manager]);
|
|
98
118
|
|
|
99
119
|
useEffect(() => {
|
|
100
|
-
|
|
120
|
+
// Don't autoplay a player that mounts already unfocused (isFocused
|
|
121
|
+
// false), else it'd flash play → pause on the next screen.
|
|
122
|
+
manager.setSource(source, {
|
|
123
|
+
autoplay: autoplay && isFocused !== false,
|
|
124
|
+
surfaceId: id,
|
|
125
|
+
});
|
|
101
126
|
// Attach on mount / when the video identity changes. Other source
|
|
102
127
|
// fields (title, headers) don't retrigger: identity is source.id.
|
|
103
128
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -157,6 +182,33 @@ export const VideoPlayer = forwardRef<VideoManager, VideoPlayerProps>(
|
|
|
157
182
|
return () => sub.remove();
|
|
158
183
|
}, [manager, id, pauseOnFocusLost]);
|
|
159
184
|
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
if (isFocused === undefined) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const state = manager.store.getState();
|
|
190
|
+
if (isFocused) {
|
|
191
|
+
// Regained screen focus. If the engine is still ours, just resume;
|
|
192
|
+
// otherwise it moved to another video while we were blurred — reclaim
|
|
193
|
+
// it (same-id handoff means no reload if it never actually left).
|
|
194
|
+
if (
|
|
195
|
+
state.surfaceId === id &&
|
|
196
|
+
state.currentVideo?.id === sourceRef.current.id
|
|
197
|
+
) {
|
|
198
|
+
manager.play();
|
|
199
|
+
} else {
|
|
200
|
+
manager.setSource(sourceRef.current, {
|
|
201
|
+
autoplay: true,
|
|
202
|
+
surfaceId: id,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
} else if (state.surfaceId === id) {
|
|
206
|
+
// Lost screen focus while playing our video — pause. Guarded so we
|
|
207
|
+
// never pause a video that has already handed off elsewhere.
|
|
208
|
+
manager.pause();
|
|
209
|
+
}
|
|
210
|
+
}, [manager, id, isFocused]);
|
|
211
|
+
|
|
160
212
|
useVideoEvents({
|
|
161
213
|
onLoad: onLoadComplete,
|
|
162
214
|
onBuffer: (e) => onBuffering?.(e.buffering),
|