@draftbit/core 54.0.4-11e01d.2 → 54.0.4-6c949a.2
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/lib/commonjs/components/MediaPlayer/AudioPlayer/HeadlessAudioPlayer.js +1 -1
- package/lib/commonjs/components/MediaPlayer/MediaPlaybackWrapper.js +1 -1
- package/lib/commonjs/components/MediaPlayer/MediaPlayerCommon.js +1 -1
- package/lib/commonjs/components/MediaPlayer/VideoPlayer/VideoPlayer.js +1 -1
- package/lib/typescript/src/components/MediaPlayer/AudioPlayer/HeadlessAudioPlayer.d.ts +1 -3
- package/lib/typescript/src/components/MediaPlayer/AudioPlayer/HeadlessAudioPlayer.js +54 -69
- package/lib/typescript/src/components/MediaPlayer/AudioPlayer/HeadlessAudioPlayer.js.map +1 -1
- package/lib/typescript/src/components/MediaPlayer/MediaPlaybackWrapper.d.ts +2 -3
- package/lib/typescript/src/components/MediaPlayer/MediaPlaybackWrapper.js +21 -19
- package/lib/typescript/src/components/MediaPlayer/MediaPlaybackWrapper.js.map +1 -1
- package/lib/typescript/src/components/MediaPlayer/MediaPlayerCommon.d.ts +4 -5
- package/lib/typescript/src/components/MediaPlayer/MediaPlayerCommon.js +26 -3
- package/lib/typescript/src/components/MediaPlayer/MediaPlayerCommon.js.map +1 -1
- package/lib/typescript/src/components/MediaPlayer/VideoPlayer/VideoPlayer.d.ts +4 -14
- package/lib/typescript/src/components/MediaPlayer/VideoPlayer/VideoPlayer.js +62 -125
- package/lib/typescript/src/components/MediaPlayer/VideoPlayer/VideoPlayer.js.map +1 -1
- package/lib/typescript/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -5
- package/src/components/MediaPlayer/AudioPlayer/HeadlessAudioPlayer.tsx +72 -84
- package/src/components/MediaPlayer/MediaPlaybackWrapper.tsx +24 -21
- package/src/components/MediaPlayer/MediaPlayerCommon.ts +34 -8
- package/src/components/MediaPlayer/VideoPlayer/VideoPlayer.tsx +86 -213
|
@@ -1,230 +1,136 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { ImageResizeMode, Platform } from "react-native";
|
|
2
3
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
VideoViewProps as ExpoVideoProps,
|
|
12
|
-
VideoContentFit,
|
|
13
|
-
useVideoPlayer,
|
|
14
|
-
TimeUpdateEventPayload,
|
|
15
|
-
VideoPlayer as VideoPlayerType,
|
|
16
|
-
VideoSource,
|
|
17
|
-
} from "expo-video";
|
|
18
|
-
import { setAudioModeAsync } from "expo-audio";
|
|
4
|
+
Video as VideoPlayerComponent,
|
|
5
|
+
VideoProps as ExpoVideoProps,
|
|
6
|
+
ResizeMode as ExpoResizeMode,
|
|
7
|
+
AVPlaybackStatus,
|
|
8
|
+
VideoFullscreenUpdate,
|
|
9
|
+
AVPlaybackSource,
|
|
10
|
+
Audio,
|
|
11
|
+
} from "expo-av";
|
|
19
12
|
import { extractSizeStyles } from "../../../utilities";
|
|
20
13
|
import MediaPlaybackWrapper from "../MediaPlaybackWrapper";
|
|
14
|
+
import type { Playback } from "expo-av/src/AV";
|
|
21
15
|
import {
|
|
16
|
+
mapToMediaPlayerStatus,
|
|
22
17
|
normalizeBase64Source,
|
|
23
18
|
useSourceDeepCompareEffect,
|
|
24
|
-
useSourceDeepCompareMemoize,
|
|
25
|
-
} from "../MediaPlayerCommon";
|
|
26
|
-
import type {
|
|
27
|
-
MediaPlayerRef,
|
|
28
|
-
MediaPlayerProps,
|
|
29
|
-
MediaPlayerStatus,
|
|
30
19
|
} from "../MediaPlayerCommon";
|
|
20
|
+
import type { MediaPlayerRef, MediaPlayerProps } from "../MediaPlayerCommon";
|
|
31
21
|
|
|
32
22
|
type ResizeMode = "contain" | "cover" | "stretch";
|
|
33
|
-
type ExpoVideoPropsOmitted = Omit<
|
|
23
|
+
type ExpoVideoPropsOmitted = Omit<
|
|
24
|
+
ExpoVideoProps,
|
|
25
|
+
"videoStyle" | "resizeMode" | "onPlaybackStatusUpdate" | "source"
|
|
26
|
+
>;
|
|
34
27
|
|
|
35
28
|
interface VideoPlayerProps extends ExpoVideoPropsOmitted, MediaPlayerProps {
|
|
36
29
|
resizeMode?: ResizeMode;
|
|
37
30
|
posterResizeMode?: ImageResizeMode;
|
|
38
|
-
posterSource?: ImageProps["source"];
|
|
39
|
-
usePoster?: boolean;
|
|
40
31
|
playsInSilentModeIOS?: boolean;
|
|
41
|
-
isMuted?: boolean;
|
|
42
|
-
useNativeControls?: boolean;
|
|
43
|
-
shouldPlay?: boolean;
|
|
44
|
-
isLooping?: boolean;
|
|
45
|
-
positionMillis?: number;
|
|
46
|
-
rate?: number;
|
|
47
|
-
volume?: number;
|
|
48
32
|
}
|
|
49
33
|
|
|
50
34
|
export interface VideoPlayerRef extends MediaPlayerRef {
|
|
51
35
|
toggleFullscreen: () => void;
|
|
52
36
|
}
|
|
53
37
|
|
|
38
|
+
// Setting playsInSilentModeIOS prop directly on Video component is unreliable,
|
|
39
|
+
// so we need to set the audio mode globally before playing.
|
|
40
|
+
// See:
|
|
41
|
+
// https://github.com/expo/expo/issues/7485
|
|
42
|
+
// https://stackoverflow.com/questions/57371543/how-to-fix-video-play-but-dont-have-sound-on-ios-with-expo
|
|
43
|
+
const triggerAudio = async (ref: React.RefObject<MediaPlayerRef | null>) => {
|
|
44
|
+
if (ref && ref?.current && Platform.OS === "ios") {
|
|
45
|
+
await Audio.setAudioModeAsync({ playsInSilentModeIOS: true });
|
|
46
|
+
ref.current.play();
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
54
50
|
const VideoPlayer = React.forwardRef<VideoPlayerRef, VideoPlayerProps>(
|
|
55
51
|
(
|
|
56
52
|
{
|
|
57
53
|
style,
|
|
58
54
|
resizeMode = "contain",
|
|
59
55
|
posterResizeMode = "cover",
|
|
60
|
-
posterSource,
|
|
61
|
-
usePoster = false,
|
|
62
56
|
onPlaybackStatusUpdate: onPlaybackStatusUpdateProp,
|
|
63
57
|
onPlaybackFinish,
|
|
64
58
|
source,
|
|
65
59
|
playsInSilentModeIOS = false,
|
|
66
|
-
isMuted = false,
|
|
67
|
-
useNativeControls = true,
|
|
68
|
-
shouldPlay = false,
|
|
69
|
-
isLooping = false,
|
|
70
|
-
positionMillis,
|
|
71
|
-
allowsFullscreen = true,
|
|
72
|
-
rate = 1,
|
|
73
|
-
volume = 1,
|
|
74
60
|
...rest
|
|
75
61
|
},
|
|
76
62
|
ref
|
|
77
63
|
) => {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
const player = useVideoPlayer(stableSource, (p) => {
|
|
83
|
-
p.loop = isLooping;
|
|
84
|
-
p.muted = isMuted;
|
|
85
|
-
p.volume = volume;
|
|
86
|
-
p.playbackRate = rate;
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
const videoPlayerRef = React.useRef<VideoPlayerComponent>(null);
|
|
64
|
+
const [videoMediaObject, setVideoMediaObject] =
|
|
65
|
+
React.useState<VideoPlayerComponent | null>();
|
|
90
66
|
const [isPlaying, setIsPlaying] = React.useState(false);
|
|
91
67
|
const [isFullscreen, setIsFullscreen] = React.useState(false);
|
|
92
|
-
const [
|
|
93
|
-
|
|
94
|
-
);
|
|
95
|
-
|
|
68
|
+
const [currentSource, setCurrentSource] =
|
|
69
|
+
React.useState<AVPlaybackSource>();
|
|
96
70
|
const mediaPlaybackWrapperRef = React.useRef<MediaPlayerRef>(null);
|
|
97
71
|
|
|
98
72
|
const sizeStyles = extractSizeStyles(style);
|
|
99
73
|
|
|
100
|
-
|
|
101
|
-
player.muted = isMuted;
|
|
102
|
-
}, [player, isMuted]);
|
|
103
|
-
|
|
104
|
-
React.useEffect(() => {
|
|
105
|
-
player.loop = isLooping;
|
|
106
|
-
}, [player, isLooping]);
|
|
107
|
-
|
|
108
|
-
React.useEffect(() => {
|
|
109
|
-
player.volume = volume;
|
|
110
|
-
}, [player, volume]);
|
|
111
|
-
|
|
112
|
-
React.useEffect(() => {
|
|
113
|
-
player.playbackRate = rate;
|
|
114
|
-
}, [player, rate]);
|
|
115
|
-
|
|
116
|
-
// Refs so statusChange can read latest shouldPlay/positionMillis
|
|
117
|
-
const shouldPlayRef = React.useRef(shouldPlay);
|
|
118
|
-
const positionMillisRef = React.useRef(positionMillis);
|
|
119
|
-
shouldPlayRef.current = shouldPlay;
|
|
120
|
-
positionMillisRef.current = positionMillis;
|
|
121
|
-
|
|
122
|
-
const hasAppliedInitialState = React.useRef(false);
|
|
123
|
-
|
|
124
|
-
React.useEffect(() => {
|
|
125
|
-
const timeUpdateSub = player.addListener("timeUpdate", (status) => {
|
|
126
|
-
onPlaybackStatusUpdateProp?.(mapToMediaPlayerStatus(status, player));
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
const playingChangeSub = player.addListener(
|
|
130
|
-
"playingChange",
|
|
131
|
-
({ isPlaying: playing }) => {
|
|
132
|
-
setIsPlaying(playing);
|
|
133
|
-
onPlaybackStatusUpdateProp?.(mapPlayerToMediaPlayerStatus(player));
|
|
134
|
-
}
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
const playToEndSub = player.addListener("playToEnd", () => {
|
|
138
|
-
onPlaybackFinish?.();
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
const statusChangeSub = player.addListener(
|
|
142
|
-
"statusChange",
|
|
143
|
-
({ status, error }) => {
|
|
144
|
-
if (status === "readyToPlay") {
|
|
145
|
-
setShowPoster(false);
|
|
146
|
-
if (!hasAppliedInitialState.current) {
|
|
147
|
-
hasAppliedInitialState.current = true;
|
|
148
|
-
if (positionMillisRef.current) {
|
|
149
|
-
player.currentTime = positionMillisRef.current / 1000;
|
|
150
|
-
}
|
|
151
|
-
if (shouldPlayRef.current) {
|
|
152
|
-
player.play();
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
const mappedStatus = mapPlayerToMediaPlayerStatus(player);
|
|
157
|
-
onPlaybackStatusUpdateProp?.(
|
|
158
|
-
status === "error" && error
|
|
159
|
-
? { ...mappedStatus, isError: true, error: error.message }
|
|
160
|
-
: mappedStatus
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
return () => {
|
|
166
|
-
timeUpdateSub.remove();
|
|
167
|
-
playingChangeSub.remove();
|
|
168
|
-
playToEndSub.remove();
|
|
169
|
-
statusChangeSub.remove();
|
|
170
|
-
};
|
|
171
|
-
}, []);
|
|
172
|
-
|
|
173
|
-
// Replace video source when it changes (deep comparison on URI to avoid unnecessary reloads)
|
|
174
|
-
const isFirstSourceRender = React.useRef(true);
|
|
175
|
-
useSourceDeepCompareEffect(() => {
|
|
176
|
-
if (isFirstSourceRender.current) {
|
|
177
|
-
isFirstSourceRender.current = false;
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
hasAppliedInitialState.current = false;
|
|
181
|
-
player.replace(normalizeBase64Source(source, "video") as VideoSource);
|
|
182
|
-
}, [source]);
|
|
183
|
-
|
|
184
|
-
let mappedVideoContentFit: VideoContentFit;
|
|
74
|
+
let mappedResizeMode;
|
|
185
75
|
switch (resizeMode) {
|
|
186
76
|
case "contain":
|
|
187
|
-
|
|
77
|
+
mappedResizeMode = ExpoResizeMode.CONTAIN;
|
|
188
78
|
break;
|
|
189
79
|
case "cover":
|
|
190
|
-
|
|
80
|
+
mappedResizeMode = ExpoResizeMode.COVER;
|
|
191
81
|
break;
|
|
192
82
|
case "stretch":
|
|
193
|
-
|
|
83
|
+
mappedResizeMode = ExpoResizeMode.STRETCH;
|
|
194
84
|
break;
|
|
195
85
|
}
|
|
196
86
|
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
87
|
+
const onPlaybackStatusUpdate = (status: AVPlaybackStatus) => {
|
|
88
|
+
const mappedStatus = mapToMediaPlayerStatus(status);
|
|
89
|
+
onPlaybackStatusUpdateProp?.(mappedStatus);
|
|
90
|
+
|
|
91
|
+
if (status.isLoaded) {
|
|
92
|
+
if (status.didJustFinish) {
|
|
93
|
+
onPlaybackFinish?.();
|
|
94
|
+
}
|
|
95
|
+
setIsPlaying(status.isPlaying);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const onFullscreenUpdate = (fullscreenUpdate: VideoFullscreenUpdate) => {
|
|
100
|
+
switch (fullscreenUpdate) {
|
|
101
|
+
case VideoFullscreenUpdate.PLAYER_DID_PRESENT:
|
|
102
|
+
case VideoFullscreenUpdate.PLAYER_WILL_PRESENT:
|
|
200
103
|
setIsFullscreen(true);
|
|
201
104
|
break;
|
|
202
|
-
case
|
|
105
|
+
case VideoFullscreenUpdate.PLAYER_DID_DISMISS:
|
|
106
|
+
case VideoFullscreenUpdate.PLAYER_WILL_DISMISS:
|
|
203
107
|
setIsFullscreen(false);
|
|
204
108
|
break;
|
|
205
109
|
}
|
|
206
110
|
};
|
|
207
111
|
|
|
208
112
|
const toggleFullscreen = React.useCallback(async () => {
|
|
209
|
-
if (
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
await videoPlayerRef.current?.enterFullscreen();
|
|
214
|
-
}
|
|
113
|
+
if (isFullscreen) {
|
|
114
|
+
await videoMediaObject?.dismissFullscreenPlayer();
|
|
115
|
+
} else {
|
|
116
|
+
await videoMediaObject?.presentFullscreenPlayer();
|
|
215
117
|
}
|
|
216
|
-
}, [isFullscreen]);
|
|
118
|
+
}, [isFullscreen, videoMediaObject]);
|
|
217
119
|
|
|
218
120
|
const updateAudioMode = React.useCallback(async () => {
|
|
219
121
|
try {
|
|
220
|
-
await setAudioModeAsync({
|
|
221
|
-
|
|
122
|
+
await Audio.setAudioModeAsync({
|
|
123
|
+
playsInSilentModeIOS,
|
|
222
124
|
});
|
|
223
125
|
} catch (e) {
|
|
224
126
|
console.error("Failed to set audio mode. Error details:", e);
|
|
225
127
|
}
|
|
226
128
|
}, [playsInSilentModeIOS]);
|
|
227
129
|
|
|
130
|
+
React.useEffect(() => {
|
|
131
|
+
if (isPlaying) triggerAudio(mediaPlaybackWrapperRef);
|
|
132
|
+
}, [mediaPlaybackWrapperRef, isPlaying]);
|
|
133
|
+
|
|
228
134
|
React.useImperativeHandle(
|
|
229
135
|
ref,
|
|
230
136
|
() => ({
|
|
@@ -241,69 +147,36 @@ const VideoPlayer = React.forwardRef<VideoPlayerRef, VideoPlayerProps>(
|
|
|
241
147
|
[toggleFullscreen, isPlaying]
|
|
242
148
|
);
|
|
243
149
|
|
|
150
|
+
useSourceDeepCompareEffect(() => {
|
|
151
|
+
const updateSource = async () => {
|
|
152
|
+
const finalSource = await normalizeBase64Source(source, "video");
|
|
153
|
+
setCurrentSource(finalSource);
|
|
154
|
+
};
|
|
155
|
+
updateSource();
|
|
156
|
+
}, [source]);
|
|
157
|
+
|
|
244
158
|
return (
|
|
245
159
|
<MediaPlaybackWrapper
|
|
246
|
-
|
|
160
|
+
media={videoMediaObject as Playback | undefined}
|
|
247
161
|
isPlaying={isPlaying}
|
|
248
162
|
ref={mediaPlaybackWrapperRef}
|
|
249
163
|
onTogglePlayback={updateAudioMode}
|
|
250
164
|
>
|
|
251
|
-
<
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
{showPoster && posterSource && (
|
|
264
|
-
<View style={StyleSheet.absoluteFill} pointerEvents="none">
|
|
265
|
-
<Image
|
|
266
|
-
source={posterSource}
|
|
267
|
-
resizeMode={posterResizeMode}
|
|
268
|
-
style={[StyleSheet.absoluteFill, sizeStyles]}
|
|
269
|
-
/>
|
|
270
|
-
</View>
|
|
271
|
-
)}
|
|
272
|
-
</View>
|
|
165
|
+
<VideoPlayerComponent
|
|
166
|
+
// https://docs.expo.dev/versions/latest/sdk/av/#example-video to see why ref is handled this way
|
|
167
|
+
ref={(component) => setVideoMediaObject(component)}
|
|
168
|
+
style={style}
|
|
169
|
+
videoStyle={sizeStyles}
|
|
170
|
+
resizeMode={mappedResizeMode}
|
|
171
|
+
posterStyle={[sizeStyles, { resizeMode: posterResizeMode }]}
|
|
172
|
+
onPlaybackStatusUpdate={onPlaybackStatusUpdate}
|
|
173
|
+
onFullscreenUpdate={(e) => onFullscreenUpdate(e.fullscreenUpdate)}
|
|
174
|
+
source={currentSource}
|
|
175
|
+
{...rest}
|
|
176
|
+
/>
|
|
273
177
|
</MediaPlaybackWrapper>
|
|
274
178
|
);
|
|
275
179
|
}
|
|
276
180
|
);
|
|
277
181
|
|
|
278
|
-
const styles = StyleSheet.create({
|
|
279
|
-
container: {
|
|
280
|
-
overflow: "hidden",
|
|
281
|
-
},
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
function mapPlayerToMediaPlayerStatus(
|
|
285
|
-
player: VideoPlayerType
|
|
286
|
-
): MediaPlayerStatus {
|
|
287
|
-
return {
|
|
288
|
-
isPlaying: player.playing,
|
|
289
|
-
isLoading: player.status === "loading",
|
|
290
|
-
isBuffering: player.status === "loading",
|
|
291
|
-
currentPositionMillis: player.currentTime * 1000,
|
|
292
|
-
durationMillis: player.duration * 1000,
|
|
293
|
-
bufferedDurationMillis: player.bufferedPosition * 1000,
|
|
294
|
-
isError: player.status === "error",
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
export function mapToMediaPlayerStatus(
|
|
299
|
-
status: TimeUpdateEventPayload,
|
|
300
|
-
player: VideoPlayerType
|
|
301
|
-
): MediaPlayerStatus {
|
|
302
|
-
return {
|
|
303
|
-
...mapPlayerToMediaPlayerStatus(player),
|
|
304
|
-
currentPositionMillis: status.currentTime * 1000,
|
|
305
|
-
bufferedDurationMillis: status.bufferedPosition * 1000,
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
|
|
309
182
|
export default VideoPlayer;
|