@cinecrew/cinecrew-player 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.
- package/CineCrewPlayer.podspec +22 -0
- package/LICENSE +21 -0
- package/NOTICE +6 -0
- package/README.md +574 -0
- package/android/build.gradle +48 -0
- package/android/proguard-rules.pro +29 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/yuanzhou/vlc/ReactVlcPlayerPackage.java +28 -0
- package/android/src/main/java/com/yuanzhou/vlc/vlcplayer/ReactVlcPlayerView.java +988 -0
- package/android/src/main/java/com/yuanzhou/vlc/vlcplayer/ReactVlcPlayerViewManager.java +208 -0
- package/android/src/main/java/com/yuanzhou/vlc/vlcplayer/VideoEventEmitter.java +136 -0
- package/android/src/main/res/values/strings.xml +3 -0
- package/app.plugin.cjs +1 -0
- package/assets/cinecrew-player-logo.svg +44 -0
- package/package.json +132 -0
- package/packages/react-native-vlc-media-player/LICENSE +21 -0
- package/packages/react-native-vlc-media-player/VLCPlayer.js +207 -0
- package/packages/react-native-vlc-media-player/expo/android/withGradleTasks.cjs +49 -0
- package/packages/react-native-vlc-media-player/expo/ios/withMobileVlcKit.cjs +28 -0
- package/packages/react-native-vlc-media-player/expo/withVlcMediaPlayer.cjs +10 -0
- package/packages/react-native-vlc-media-player/index.d.ts +354 -0
- package/packages/react-native-vlc-media-player/ios/RCTVLCPlayer/RCTVLCPlayer.h +36 -0
- package/packages/react-native-vlc-media-player/ios/RCTVLCPlayer/RCTVLCPlayer.m +635 -0
- package/packages/react-native-vlc-media-player/ios/RCTVLCPlayer/RCTVLCPlayerManager.h +5 -0
- package/packages/react-native-vlc-media-player/ios/RCTVLCPlayer/RCTVLCPlayerManager.m +100 -0
- package/src/native/InlineLivePlayer.js +321 -0
- package/src/native/MediaPlayerView.js +3093 -0
- package/src/native/customization.js +44 -0
- package/src/native/index.js +107 -0
- package/src/native/media/ElectronVideoPlayer.js +288 -0
- package/src/native/media/EmojiPickerModal.js +30 -0
- package/src/native/media/LiveChatDrawer.js +1177 -0
- package/src/native/media/LiveRecordingOverlay.js +319 -0
- package/src/native/media/WebVideoPlayer.js +320 -0
- package/src/native/media/YouTubeVideoPlayer.native.js +120 -0
- package/src/native/media/YouTubeVideoPlayer.web.js +169 -0
- package/src/native/media/player/AudioOnlyView.js +337 -0
- package/src/native/media/player/CenterControls.js +94 -0
- package/src/native/media/player/ExoVideoFallback.js +329 -0
- package/src/native/media/player/PipOverlay.js +1 -0
- package/src/native/media/player/PlayerBottomBar.js +295 -0
- package/src/native/media/player/PlayerTopBar.js +255 -0
- package/src/native/media/player/VLCBoundary.js +41 -0
- package/src/native/media/player/VerticalIndicator.js +184 -0
- package/src/native/media/player/index.js +9 -0
- package/src/native/media/player/playerConstants.js +159 -0
- package/src/native/media/web/useWebAc3AudioPlayback.native.js +6 -0
- package/src/native/media/web/useWebAc3AudioPlayback.web.js +219 -0
- package/src/native/media/web/useWebHlsPlayback.native.js +3 -0
- package/src/native/media/web/useWebHlsPlayback.web.js +66 -0
- package/src/native/media/web/useWebMediaSession.js +90 -0
- package/src/native/media/web/useWebMpegTsPlayback.native.js +3 -0
- package/src/native/media/web/useWebMpegTsPlayback.web.js +356 -0
- package/src/native/media/web/useWebVideoAspectRatio.js +52 -0
- package/src/native/media/web/webPlaybackErrors.js +4 -0
- package/src/utils/layoutUtils.js +12 -0
- package/src/utils/mediaUtils.js +7 -0
- package/src/utils/recording.js +8 -0
- package/src/utils/runtimePlatform.js +19 -0
- package/src/utils/sourceUtils.js +99 -0
- package/src/utils/youtubeHtml.js +34 -0
- package/src/web/index.js +1129 -0
- package/src/web/styles.css +378 -0
- package/types/index.d.ts +268 -0
- package/vendor/mpegts.js/LICENSE +202 -0
- package/vendor/mpegts.js/mpegts.js +3 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { StyleSheet, View, TouchableOpacity } from 'react-native';
|
|
2
|
+
import { PlayerIcon, usePlayerColors } from '../../customization';
|
|
3
|
+
|
|
4
|
+
export const CenterControls = ({
|
|
5
|
+
visible = true,
|
|
6
|
+
isLive,
|
|
7
|
+
isPlaying,
|
|
8
|
+
onSeekBy,
|
|
9
|
+
onTogglePlayPause,
|
|
10
|
+
}) => {
|
|
11
|
+
const palette = usePlayerColors();
|
|
12
|
+
if (!visible) return null;
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<View style={styles.centerContainer} pointerEvents="box-none">
|
|
16
|
+
<View style={styles.centerRow} pointerEvents="box-none">
|
|
17
|
+
{!isLive && (
|
|
18
|
+
<TouchableOpacity
|
|
19
|
+
style={[styles.pill, { backgroundColor: palette.controlBackground }]}
|
|
20
|
+
onPress={(e) => {
|
|
21
|
+
e.stopPropagation();
|
|
22
|
+
onSeekBy(-10);
|
|
23
|
+
}}
|
|
24
|
+
hitSlop={12}
|
|
25
|
+
>
|
|
26
|
+
<PlayerIcon pack="material" name="replay-10" size={32} color={palette.controlColor} />
|
|
27
|
+
</TouchableOpacity>
|
|
28
|
+
)}
|
|
29
|
+
|
|
30
|
+
<TouchableOpacity
|
|
31
|
+
style={[styles.bigPlayBtn, { backgroundColor: palette.controlColor }]}
|
|
32
|
+
onPress={(e) => {
|
|
33
|
+
e.stopPropagation();
|
|
34
|
+
onTogglePlayPause();
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
<PlayerIcon
|
|
38
|
+
name={isPlaying ? 'pause' : 'play'}
|
|
39
|
+
size={40}
|
|
40
|
+
color={palette.backgroundColor}
|
|
41
|
+
/>
|
|
42
|
+
</TouchableOpacity>
|
|
43
|
+
|
|
44
|
+
{!isLive && (
|
|
45
|
+
<TouchableOpacity
|
|
46
|
+
style={[styles.pill, { backgroundColor: palette.controlBackground }]}
|
|
47
|
+
onPress={(e) => {
|
|
48
|
+
e.stopPropagation();
|
|
49
|
+
onSeekBy(10);
|
|
50
|
+
}}
|
|
51
|
+
hitSlop={12}
|
|
52
|
+
>
|
|
53
|
+
<PlayerIcon pack="material" name="forward-10" size={32} color={palette.controlColor} />
|
|
54
|
+
</TouchableOpacity>
|
|
55
|
+
)}
|
|
56
|
+
</View>
|
|
57
|
+
</View>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const styles = StyleSheet.create({
|
|
62
|
+
centerContainer: {
|
|
63
|
+
flex: 1,
|
|
64
|
+
justifyContent: 'center',
|
|
65
|
+
alignItems: 'center',
|
|
66
|
+
},
|
|
67
|
+
centerRow: {
|
|
68
|
+
flexDirection: 'row',
|
|
69
|
+
alignItems: 'center',
|
|
70
|
+
justifyContent: 'center',
|
|
71
|
+
gap: 28,
|
|
72
|
+
},
|
|
73
|
+
pill: {
|
|
74
|
+
flexDirection: 'row',
|
|
75
|
+
alignItems: 'center',
|
|
76
|
+
gap: 6,
|
|
77
|
+
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
78
|
+
paddingHorizontal: 12,
|
|
79
|
+
paddingVertical: 8,
|
|
80
|
+
borderRadius: 20,
|
|
81
|
+
zIndex: 70,
|
|
82
|
+
elevation: 70,
|
|
83
|
+
},
|
|
84
|
+
bigPlayBtn: {
|
|
85
|
+
width: 68,
|
|
86
|
+
height: 68,
|
|
87
|
+
borderRadius: 34,
|
|
88
|
+
backgroundColor: '#FFF',
|
|
89
|
+
alignItems: 'center',
|
|
90
|
+
justifyContent: 'center',
|
|
91
|
+
zIndex: 70,
|
|
92
|
+
elevation: 70,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useState,
|
|
3
|
+
useEffect,
|
|
4
|
+
useCallback,
|
|
5
|
+
useLayoutEffect,
|
|
6
|
+
useRef,
|
|
7
|
+
forwardRef,
|
|
8
|
+
useImperativeHandle,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import { StyleSheet, View, useWindowDimensions } from 'react-native';
|
|
11
|
+
import { useVideoPlayer, VideoView } from 'expo-video';
|
|
12
|
+
|
|
13
|
+
function sendProgress(player, onProgress) {
|
|
14
|
+
if (typeof onProgress !== 'function') return;
|
|
15
|
+
const duration = Number.isFinite(player.duration) && player.duration > 0 ? player.duration : 0;
|
|
16
|
+
const currentTime = Number.isFinite(player.currentTime) && player.currentTime > 0 ? player.currentTime : 0;
|
|
17
|
+
onProgress({ currentTime: currentTime * 1000, duration: duration * 1000, target: currentTime });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function handleReadyStatus(player, callbacks) {
|
|
21
|
+
callbacks.onPlaying?.();
|
|
22
|
+
callbacks.onBuffering?.(false);
|
|
23
|
+
if (!callbacks.paused) callbacks.withActivePlayer((activePlayer) => activePlayer.play());
|
|
24
|
+
callbacks.onTracksChanged?.({ audioTracks: player.audioTracks ? Array.from(player.audioTracks) : [] });
|
|
25
|
+
sendProgress(player, callbacks.onProgress);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function notifyPlayerStatus(event, callbacks) {
|
|
29
|
+
const { status, error } = event;
|
|
30
|
+
if (status === 'readyToPlay') {
|
|
31
|
+
handleReadyStatus(callbacks.player, callbacks);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (status === 'loading' || status === 'idle') {
|
|
35
|
+
callbacks.onBuffering?.(true);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (status === 'error') callbacks.onError?.({ message: 'Failed to load stream.', error });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Native fallback player (expo-video) used when the VLC native module is not
|
|
43
|
+
* registered — e.g. running inside Expo Go, where react-native-vlc-media-player
|
|
44
|
+
* is not bundled. Mirrors the imperative API + events MediaPlayerView expects.
|
|
45
|
+
*/
|
|
46
|
+
export const ExoVideoFallback = forwardRef(function ExoVideoFallback(
|
|
47
|
+
{
|
|
48
|
+
streamUrl,
|
|
49
|
+
paused,
|
|
50
|
+
muted,
|
|
51
|
+
volume = 100,
|
|
52
|
+
playbackRate = 1,
|
|
53
|
+
videoAspectRatio,
|
|
54
|
+
onProgress,
|
|
55
|
+
onPlaying,
|
|
56
|
+
onBuffering,
|
|
57
|
+
onEnded,
|
|
58
|
+
onError,
|
|
59
|
+
onTracksChanged,
|
|
60
|
+
audioTrack,
|
|
61
|
+
audioOnly = false,
|
|
62
|
+
},
|
|
63
|
+
ref
|
|
64
|
+
) {
|
|
65
|
+
const { width: windowWidth, height: windowHeight } = useWindowDimensions();
|
|
66
|
+
const [contentFit, setContentFit] = useState('contain');
|
|
67
|
+
const [boxStyle, setBoxStyle] = useState(undefined);
|
|
68
|
+
|
|
69
|
+
const effectiveStreamUrl = streamUrl || '';
|
|
70
|
+
const player = useVideoPlayer(effectiveStreamUrl, (p) => {
|
|
71
|
+
p.loop = false;
|
|
72
|
+
p.muted = !!muted;
|
|
73
|
+
p.volume = Math.max(0, Math.min(1, (volume ?? 100) / 100));
|
|
74
|
+
p.playbackRate = playbackRate;
|
|
75
|
+
p.staysActiveInBackground = !!audioOnly;
|
|
76
|
+
p.showNowPlayingNotification = !!audioOnly;
|
|
77
|
+
if (audioOnly) {
|
|
78
|
+
p.audioMixingMode = 'doNotMix';
|
|
79
|
+
}
|
|
80
|
+
// iOS default is 0, which disables timeUpdate events entirely.
|
|
81
|
+
p.timeUpdateEventInterval = 0.5;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// useVideoPlayer owns the native shared object and releases it during its
|
|
85
|
+
// cleanup. Mark the player inactive in the layout cleanup (before passive
|
|
86
|
+
// effects are torn down) so no command can race that release.
|
|
87
|
+
const activePlayerRef = useRef(null);
|
|
88
|
+
useLayoutEffect(() => {
|
|
89
|
+
activePlayerRef.current = player;
|
|
90
|
+
return () => {
|
|
91
|
+
if (activePlayerRef.current === player) {
|
|
92
|
+
activePlayerRef.current = null;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}, [player]);
|
|
96
|
+
|
|
97
|
+
const withActivePlayer = useCallback(
|
|
98
|
+
(operation) => {
|
|
99
|
+
if (activePlayerRef.current !== player) return;
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const result = operation(player);
|
|
103
|
+
// Expo's native implementation is typed as void, but some versions
|
|
104
|
+
// return a rejected promise when a shared object is released during a
|
|
105
|
+
// native transition. Never let that become an unhandled render error.
|
|
106
|
+
if (result && typeof result.catch === 'function') {
|
|
107
|
+
result.catch(() => {});
|
|
108
|
+
}
|
|
109
|
+
} catch {
|
|
110
|
+
// The hook may release the native object between the active check and
|
|
111
|
+
// the command. Teardown is best-effort and must remain idempotent.
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
[player],
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
withActivePlayer((activePlayer) => {
|
|
119
|
+
if (paused) {
|
|
120
|
+
activePlayer.pause();
|
|
121
|
+
} else {
|
|
122
|
+
activePlayer.play();
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}, [paused, withActivePlayer]);
|
|
126
|
+
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
withActivePlayer((activePlayer) => {
|
|
129
|
+
activePlayer.muted = !!muted;
|
|
130
|
+
activePlayer.volume = Math.max(0, Math.min(1, (volume ?? 100) / 100));
|
|
131
|
+
});
|
|
132
|
+
}, [muted, volume, withActivePlayer]);
|
|
133
|
+
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
withActivePlayer((activePlayer) => {
|
|
136
|
+
activePlayer.playbackRate = playbackRate;
|
|
137
|
+
});
|
|
138
|
+
}, [playbackRate, withActivePlayer]);
|
|
139
|
+
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
withActivePlayer((activePlayer) => {
|
|
142
|
+
activePlayer.staysActiveInBackground = !!audioOnly;
|
|
143
|
+
activePlayer.showNowPlayingNotification = !!audioOnly;
|
|
144
|
+
if (audioOnly) {
|
|
145
|
+
activePlayer.audioMixingMode = 'doNotMix';
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}, [audioOnly, withActivePlayer]);
|
|
149
|
+
|
|
150
|
+
// Apply track selections natively
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
if (audioTrack === undefined || audioTrack === null) return;
|
|
153
|
+
|
|
154
|
+
withActivePlayer((activePlayer) => {
|
|
155
|
+
if (activePlayer.audioTracks) {
|
|
156
|
+
activePlayer.audioTracks.forEach((t) => {
|
|
157
|
+
t.selected = t.id === audioTrack;
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}, [audioTrack, withActivePlayer]);
|
|
162
|
+
|
|
163
|
+
const applyAspect = useCallback(
|
|
164
|
+
(aspect) => {
|
|
165
|
+
let fit = 'contain';
|
|
166
|
+
let style;
|
|
167
|
+
|
|
168
|
+
if (aspect === 'FILL_SCREEN' || aspect === 'FILL') {
|
|
169
|
+
fit = 'cover';
|
|
170
|
+
} else if (aspect === 'STRETCH') {
|
|
171
|
+
fit = 'fill';
|
|
172
|
+
} else if (aspect === 'CENTER') {
|
|
173
|
+
fit = 'none';
|
|
174
|
+
} else if (typeof aspect === 'string' && aspect.includes(':')) {
|
|
175
|
+
const parts = aspect.split(':');
|
|
176
|
+
const ratio = Number(parts[0]) / Number(parts[1]);
|
|
177
|
+
if (Number.isFinite(ratio) && ratio > 0 && windowWidth > 0 && windowHeight > 0) {
|
|
178
|
+
const boxHeight = windowWidth / ratio;
|
|
179
|
+
const dims = boxHeight <= windowHeight ? { width: '100%' } : { height: '100%' };
|
|
180
|
+
fit = 'fill';
|
|
181
|
+
style = { aspectRatio: ratio, ...dims };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
setContentFit(fit);
|
|
186
|
+
setBoxStyle(style);
|
|
187
|
+
},
|
|
188
|
+
[windowWidth, windowHeight]
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
applyAspect(videoAspectRatio);
|
|
193
|
+
}, [videoAspectRatio, applyAspect]);
|
|
194
|
+
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
if (activePlayerRef.current !== player) return undefined;
|
|
197
|
+
|
|
198
|
+
let offs;
|
|
199
|
+
try {
|
|
200
|
+
offs = [
|
|
201
|
+
player.addListener('timeUpdate', (event) => {
|
|
202
|
+
if (activePlayerRef.current !== player) return;
|
|
203
|
+
|
|
204
|
+
let cur = 0;
|
|
205
|
+
if (Number.isFinite(event?.currentTime)) {
|
|
206
|
+
cur = event.currentTime;
|
|
207
|
+
} else if (Number.isFinite(player.currentTime)) {
|
|
208
|
+
cur = player.currentTime;
|
|
209
|
+
}
|
|
210
|
+
const dur = Number.isFinite(player.duration) && player.duration > 0 ? player.duration : 0;
|
|
211
|
+
if (typeof onProgress === 'function') {
|
|
212
|
+
onProgress({
|
|
213
|
+
currentTime: cur * 1000,
|
|
214
|
+
duration: dur * 1000,
|
|
215
|
+
target: cur,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}),
|
|
219
|
+
player.addListener('playingChange', ({ isPlaying }) => {
|
|
220
|
+
if (activePlayerRef.current !== player) return;
|
|
221
|
+
if (isPlaying && typeof onBuffering === 'function') onBuffering(false);
|
|
222
|
+
}),
|
|
223
|
+
player.addListener('statusChange', (event) => {
|
|
224
|
+
if (activePlayerRef.current !== player) return;
|
|
225
|
+
notifyPlayerStatus(event, {
|
|
226
|
+
player,
|
|
227
|
+
paused,
|
|
228
|
+
onPlaying,
|
|
229
|
+
onBuffering,
|
|
230
|
+
onTracksChanged,
|
|
231
|
+
onProgress,
|
|
232
|
+
onError,
|
|
233
|
+
withActivePlayer,
|
|
234
|
+
});
|
|
235
|
+
}),
|
|
236
|
+
player.addListener('playToEnd', () => {
|
|
237
|
+
if (activePlayerRef.current === player && typeof onEnded === 'function') onEnded();
|
|
238
|
+
}),
|
|
239
|
+
];
|
|
240
|
+
} catch {
|
|
241
|
+
// The shared object can be released while React is replacing the view.
|
|
242
|
+
// There is nothing left to unsubscribe in that case.
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return () =>
|
|
247
|
+
offs.forEach((off) => {
|
|
248
|
+
try {
|
|
249
|
+
if (typeof off === 'function') {
|
|
250
|
+
off();
|
|
251
|
+
} else if (off && typeof off.remove === 'function') {
|
|
252
|
+
off.remove();
|
|
253
|
+
}
|
|
254
|
+
} catch {
|
|
255
|
+
// Listener cleanup is also best-effort after native release.
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}, [
|
|
259
|
+
player,
|
|
260
|
+
paused,
|
|
261
|
+
onProgress,
|
|
262
|
+
onPlaying,
|
|
263
|
+
onBuffering,
|
|
264
|
+
onEnded,
|
|
265
|
+
onError,
|
|
266
|
+
onTracksChanged,
|
|
267
|
+
withActivePlayer,
|
|
268
|
+
]);
|
|
269
|
+
|
|
270
|
+
// Safety net: keep progress flowing even if the timeUpdate event is
|
|
271
|
+
// unreliable in Expo Go.
|
|
272
|
+
useEffect(() => {
|
|
273
|
+
const id = setInterval(() => {
|
|
274
|
+
if (activePlayerRef.current !== player) return;
|
|
275
|
+
|
|
276
|
+
let cur = 0;
|
|
277
|
+
let dur = 0;
|
|
278
|
+
try {
|
|
279
|
+
cur = Number.isFinite(player.currentTime) ? player.currentTime : 0;
|
|
280
|
+
dur = Number.isFinite(player.duration) && player.duration > 0 ? player.duration : 0;
|
|
281
|
+
} catch {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (typeof onProgress === 'function') {
|
|
285
|
+
onProgress({
|
|
286
|
+
currentTime: cur * 1000,
|
|
287
|
+
duration: dur * 1000,
|
|
288
|
+
target: cur,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}, 500);
|
|
292
|
+
return () => clearInterval(id);
|
|
293
|
+
}, [player, onProgress]);
|
|
294
|
+
|
|
295
|
+
useImperativeHandle(ref, () => ({
|
|
296
|
+
seek(ratio) {
|
|
297
|
+
withActivePlayer((activePlayer) => {
|
|
298
|
+
if (activePlayer.duration && Number.isFinite(activePlayer.duration) && activePlayer.duration > 0) {
|
|
299
|
+
activePlayer.currentTime = Math.max(0, Math.min(1, ratio)) * activePlayer.duration;
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
},
|
|
303
|
+
changeVideoAspectRatio(aspect) {
|
|
304
|
+
applyAspect(aspect);
|
|
305
|
+
},
|
|
306
|
+
}), [applyAspect, withActivePlayer]);
|
|
307
|
+
|
|
308
|
+
return (
|
|
309
|
+
<View style={boxStyle || styles.video}>
|
|
310
|
+
{/* TextureView is required here because the player is resized and
|
|
311
|
+
layered under the in-app PiP overlay. */}
|
|
312
|
+
<VideoView
|
|
313
|
+
player={player}
|
|
314
|
+
style={styles.video}
|
|
315
|
+
contentFit={contentFit}
|
|
316
|
+
surfaceType="textureView"
|
|
317
|
+
nativeControls={false}
|
|
318
|
+
allowsFullscreen={false}
|
|
319
|
+
/>
|
|
320
|
+
</View>
|
|
321
|
+
);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
const styles = StyleSheet.create({
|
|
325
|
+
video: {
|
|
326
|
+
width: '100%',
|
|
327
|
+
height: '100%',
|
|
328
|
+
},
|
|
329
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const PipOverlay = () => null;
|