@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.
Files changed (22) hide show
  1. package/lib/commonjs/components/MediaPlayer/AudioPlayer/HeadlessAudioPlayer.js +1 -1
  2. package/lib/commonjs/components/MediaPlayer/MediaPlaybackWrapper.js +1 -1
  3. package/lib/commonjs/components/MediaPlayer/MediaPlayerCommon.js +1 -1
  4. package/lib/commonjs/components/MediaPlayer/VideoPlayer/VideoPlayer.js +1 -1
  5. package/lib/typescript/src/components/MediaPlayer/AudioPlayer/HeadlessAudioPlayer.d.ts +1 -3
  6. package/lib/typescript/src/components/MediaPlayer/AudioPlayer/HeadlessAudioPlayer.js +54 -69
  7. package/lib/typescript/src/components/MediaPlayer/AudioPlayer/HeadlessAudioPlayer.js.map +1 -1
  8. package/lib/typescript/src/components/MediaPlayer/MediaPlaybackWrapper.d.ts +2 -3
  9. package/lib/typescript/src/components/MediaPlayer/MediaPlaybackWrapper.js +21 -19
  10. package/lib/typescript/src/components/MediaPlayer/MediaPlaybackWrapper.js.map +1 -1
  11. package/lib/typescript/src/components/MediaPlayer/MediaPlayerCommon.d.ts +4 -5
  12. package/lib/typescript/src/components/MediaPlayer/MediaPlayerCommon.js +26 -3
  13. package/lib/typescript/src/components/MediaPlayer/MediaPlayerCommon.js.map +1 -1
  14. package/lib/typescript/src/components/MediaPlayer/VideoPlayer/VideoPlayer.d.ts +4 -14
  15. package/lib/typescript/src/components/MediaPlayer/VideoPlayer/VideoPlayer.js +62 -125
  16. package/lib/typescript/src/components/MediaPlayer/VideoPlayer/VideoPlayer.js.map +1 -1
  17. package/lib/typescript/tsconfig.tsbuildinfo +1 -1
  18. package/package.json +4 -5
  19. package/src/components/MediaPlayer/AudioPlayer/HeadlessAudioPlayer.tsx +72 -84
  20. package/src/components/MediaPlayer/MediaPlaybackWrapper.tsx +24 -21
  21. package/src/components/MediaPlayer/MediaPlayerCommon.ts +34 -8
  22. 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
- Image,
4
- ImageProps,
5
- ImageResizeMode,
6
- StyleSheet,
7
- View,
8
- } from "react-native";
9
- import {
10
- VideoView as VideoPlayerComponent,
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<ExpoVideoProps, "player" | "nativeControls">;
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 stableSource = useSourceDeepCompareMemoize(
79
- normalizeBase64Source(source, "video")
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 [showPoster, setShowPoster] = React.useState(
93
- usePoster && !!posterSource
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
- React.useEffect(() => {
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
- mappedVideoContentFit = "contain";
77
+ mappedResizeMode = ExpoResizeMode.CONTAIN;
188
78
  break;
189
79
  case "cover":
190
- mappedVideoContentFit = "cover";
80
+ mappedResizeMode = ExpoResizeMode.COVER;
191
81
  break;
192
82
  case "stretch":
193
- mappedVideoContentFit = "fill";
83
+ mappedResizeMode = ExpoResizeMode.STRETCH;
194
84
  break;
195
85
  }
196
86
 
197
- const onFullscreenUpdate = (type: "entered" | "exited") => {
198
- switch (type) {
199
- case "entered":
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 "exited":
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 (videoPlayerRef) {
210
- if (isFullscreen) {
211
- await videoPlayerRef.current?.exitFullscreen();
212
- } else {
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
- playsInSilentMode: playsInSilentModeIOS,
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
- player={player}
160
+ media={videoMediaObject as Playback | undefined}
247
161
  isPlaying={isPlaying}
248
162
  ref={mediaPlaybackWrapperRef}
249
163
  onTogglePlayback={updateAudioMode}
250
164
  >
251
- <View style={[style, styles.container]}>
252
- <VideoPlayerComponent
253
- ref={videoPlayerRef}
254
- player={player}
255
- nativeControls={useNativeControls}
256
- style={sizeStyles}
257
- contentFit={mappedVideoContentFit}
258
- onFullscreenEnter={() => onFullscreenUpdate("entered")}
259
- onFullscreenExit={() => onFullscreenUpdate("exited")}
260
- allowsFullscreen={allowsFullscreen}
261
- {...rest}
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;