@draftbit/core 54.0.4-6c949a.2 → 54.0.4-8202b6.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 +3 -1
  6. package/lib/typescript/src/components/MediaPlayer/AudioPlayer/HeadlessAudioPlayer.js +69 -54
  7. package/lib/typescript/src/components/MediaPlayer/AudioPlayer/HeadlessAudioPlayer.js.map +1 -1
  8. package/lib/typescript/src/components/MediaPlayer/MediaPlaybackWrapper.d.ts +3 -2
  9. package/lib/typescript/src/components/MediaPlayer/MediaPlaybackWrapper.js +19 -21
  10. package/lib/typescript/src/components/MediaPlayer/MediaPlaybackWrapper.js.map +1 -1
  11. package/lib/typescript/src/components/MediaPlayer/MediaPlayerCommon.d.ts +5 -4
  12. package/lib/typescript/src/components/MediaPlayer/MediaPlayerCommon.js +3 -26
  13. package/lib/typescript/src/components/MediaPlayer/MediaPlayerCommon.js.map +1 -1
  14. package/lib/typescript/src/components/MediaPlayer/VideoPlayer/VideoPlayer.d.ts +14 -4
  15. package/lib/typescript/src/components/MediaPlayer/VideoPlayer/VideoPlayer.js +125 -62
  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 +5 -4
  19. package/src/components/MediaPlayer/AudioPlayer/HeadlessAudioPlayer.tsx +84 -72
  20. package/src/components/MediaPlayer/MediaPlaybackWrapper.tsx +21 -24
  21. package/src/components/MediaPlayer/MediaPlayerCommon.ts +8 -34
  22. package/src/components/MediaPlayer/VideoPlayer/VideoPlayer.tsx +213 -86
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@draftbit/core",
3
- "version": "54.0.4-6c949a.2+6c949af",
3
+ "version": "54.0.4-8202b6.2+8202b6f",
4
4
  "description": "Core (non-native) Components",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "types": "lib/typescript/src/index.d.ts",
@@ -42,7 +42,7 @@
42
42
  "homepage": "https://github.com/draftbit/react-native-jigsaw#readme",
43
43
  "dependencies": {
44
44
  "@draftbit/react-theme-provider": "^2.1.1",
45
- "@draftbit/theme": "^54.0.4-6c949a.2+6c949af",
45
+ "@draftbit/theme": "^54.0.4-8202b6.2+8202b6f",
46
46
  "@emotion/react": "^11.13.5",
47
47
  "@emotion/styled": "^11.13.5",
48
48
  "@expo/vector-icons": "^15.0.3",
@@ -57,8 +57,9 @@
57
57
  "color": "^4.2.3",
58
58
  "date-fns": "^4.1.0",
59
59
  "dateformat": "^5.0.3",
60
- "expo-av": "~16.0.8",
60
+ "expo-audio": "~1.1.1",
61
61
  "expo-image": "~3.0.11",
62
+ "expo-video": "~3.0.16",
62
63
  "lodash.isequal": "^4.5.0",
63
64
  "lodash.isnumber": "^3.0.3",
64
65
  "lodash.omit": "^4.5.0",
@@ -122,5 +123,5 @@
122
123
  ],
123
124
  "testEnvironment": "node"
124
125
  },
125
- "gitHead": "6c949afa2c601852d436883343b61091d025d849"
126
+ "gitHead": "8202b6f4dcb6aba7f4aa81886f193d71ba52b142"
126
127
  }
@@ -1,17 +1,12 @@
1
1
  import * as React from "react";
2
- import {
3
- Audio,
4
- AVPlaybackStatus,
5
- InterruptionModeIOS,
6
- InterruptionModeAndroid,
7
- } from "expo-av";
2
+ import { useAudioPlayer, setAudioModeAsync, AudioStatus } from "expo-audio";
8
3
  import { HeadlessAudioPlayerProps } from "./AudioPlayerCommon";
9
4
  import {
10
- mapToMediaPlayerStatus,
11
5
  normalizeBase64Source,
6
+ useSourceDeepCompareMemoize,
12
7
  useSourceDeepCompareEffect,
13
8
  } from "../MediaPlayerCommon";
14
- import type { MediaPlayerRef } from "../MediaPlayerCommon";
9
+ import type { MediaPlayerRef, MediaPlayerStatus } from "../MediaPlayerCommon";
15
10
  import MediaPlaybackWrapper from "../MediaPlaybackWrapper";
16
11
 
17
12
  /**
@@ -36,38 +31,70 @@ const HeadlessAudioPlayer = React.forwardRef<
36
31
  },
37
32
  ref
38
33
  ) => {
39
- const [currentSound, setCurrentSound] = React.useState<Audio.Sound>();
34
+ const stableSource = useSourceDeepCompareMemoize(
35
+ normalizeBase64Source(source, "audio")
36
+ );
37
+ const player = useAudioPlayer(stableSource);
38
+
40
39
  const [isPlaying, setIsPlaying] = React.useState(false);
41
40
 
42
41
  React.useEffect(() => {
43
- if (
44
- currentSound &&
45
- typeof currentSound?.setIsLoopingAsync === "function"
46
- ) {
47
- currentSound.setIsLoopingAsync(isLooping);
48
- }
49
- }, [currentSound, isLooping]);
42
+ player.loop = isLooping;
43
+ }, [player, isLooping]);
50
44
 
51
45
  React.useEffect(() => {
52
- if (currentSound && typeof currentSound?.setVolumeAsync === "function") {
53
- currentSound.setVolumeAsync(volume);
46
+ player.volume = volume;
47
+ }, [player, volume]);
48
+
49
+ // Emit loading state immediately
50
+ React.useEffect(() => {
51
+ onPlaybackStatusUpdateProp?.({
52
+ isPlaying: false,
53
+ isLoading: true,
54
+ isBuffering: false,
55
+ currentPositionMillis: 0,
56
+ durationMillis: 0,
57
+ bufferedDurationMillis: 0,
58
+ isError: false,
59
+ });
60
+ }, []);
61
+
62
+ React.useEffect(() => {
63
+ const subscription = player.addListener(
64
+ "playbackStatusUpdate",
65
+ (status) => {
66
+ const mappedStatus = mapToMediaPlayerStatus(status);
67
+ onPlaybackStatusUpdateProp?.(mappedStatus);
68
+
69
+ if (status.isLoaded) {
70
+ if (status.didJustFinish && !isLooping) {
71
+ onPlaybackFinish?.();
72
+ }
73
+ setIsPlaying(status.playing);
74
+ }
75
+ }
76
+ );
77
+ return () => subscription.remove();
78
+ }, []);
79
+
80
+ // Replace source when it changes (deep comparison on URI to avoid unnecessary reloads)
81
+ const isFirstSourceRender = React.useRef(true);
82
+ useSourceDeepCompareEffect(() => {
83
+ if (isFirstSourceRender.current) {
84
+ isFirstSourceRender.current = false;
85
+ return;
54
86
  }
55
- }, [currentSound, volume]);
87
+ player.replace(normalizeBase64Source(source, "audio") as any);
88
+ }, [source]);
56
89
 
57
90
  const updateAudioMode = React.useCallback(async () => {
58
91
  try {
59
- await Audio.setAudioModeAsync({
60
- staysActiveInBackground: playsInBackground,
61
- interruptionModeIOS:
62
- interruptionMode === "lower volume"
63
- ? InterruptionModeIOS.DuckOthers
64
- : InterruptionModeIOS.DoNotMix,
65
- interruptionModeAndroid:
66
- interruptionMode === "lower volume"
67
- ? InterruptionModeAndroid.DuckOthers
68
- : InterruptionModeAndroid.DoNotMix,
69
- playsInSilentModeIOS,
70
- playThroughEarpieceAndroid,
92
+ await setAudioModeAsync({
93
+ shouldPlayInBackground: playsInBackground,
94
+ interruptionMode:
95
+ interruptionMode === "lower volume" ? "duckOthers" : "doNotMix",
96
+ playsInSilentMode: playsInSilentModeIOS,
97
+ shouldRouteThroughEarpiece: playThroughEarpieceAndroid,
71
98
  });
72
99
  } catch (e) {
73
100
  if ((e as { code?: string })?.code === "E_AUDIO_AUDIOMODE") {
@@ -88,59 +115,44 @@ const HeadlessAudioPlayer = React.forwardRef<
88
115
  playThroughEarpieceAndroid,
89
116
  ]);
90
117
 
91
- const onPlaybackStatusUpdate = (status: AVPlaybackStatus) => {
92
- const mappedStatus = mapToMediaPlayerStatus(status);
93
- onPlaybackStatusUpdateProp?.(mappedStatus);
94
-
95
- if (status.isLoaded) {
96
- if (status.didJustFinish) {
97
- if (isLooping) {
98
- return;
99
- }
100
- onPlaybackFinish?.();
101
- }
102
- setIsPlaying(status.isPlaying);
103
- }
104
- };
105
-
106
118
  const onTogglePlayback = () => {
107
- //Has to be called everytime a player is played to reconfigure the global Audio config based on each player's configuration
119
+ // Has to be called everytime a player is played to reconfigure the global Audio config based on each player's configuration
108
120
  updateAudioMode();
109
121
  };
110
122
 
111
- const loadAudio = async () => {
112
- onPlaybackStatusUpdateProp?.({
113
- isPlaying: false,
114
- isLoading: true,
115
- isBuffering: false,
116
- currentPositionMillis: 0,
117
- durationMillis: 0,
118
- bufferedDurationMillis: 0,
119
- isError: false,
120
- });
121
-
122
- const finalSource = await normalizeBase64Source(source, "audio");
123
-
124
- const { sound } = await Audio.Sound.createAsync(finalSource);
125
- setCurrentSound(sound);
126
- sound.setOnPlaybackStatusUpdate(onPlaybackStatusUpdate);
127
- };
128
-
129
- useSourceDeepCompareEffect(() => {
130
- loadAudio();
131
-
132
- // Ignore dependency of loadAudio
133
- }, [source]);
134
-
135
123
  return (
136
124
  <MediaPlaybackWrapper
137
125
  ref={ref}
138
126
  isPlaying={isPlaying}
139
- media={currentSound}
127
+ player={player}
140
128
  onTogglePlayback={onTogglePlayback}
141
129
  />
142
130
  );
143
131
  }
144
132
  );
145
133
 
134
+ export function mapToMediaPlayerStatus(status: AudioStatus): MediaPlayerStatus {
135
+ if (status.isLoaded) {
136
+ return {
137
+ isPlaying: status.playing,
138
+ isLoading: false,
139
+ isBuffering: status.isBuffering,
140
+ currentPositionMillis: status.currentTime * 1000,
141
+ durationMillis: status.duration * 1000,
142
+ bufferedDurationMillis: status.duration * 1000,
143
+ isError: false,
144
+ };
145
+ }
146
+
147
+ return {
148
+ isPlaying: false,
149
+ isLoading: true,
150
+ isBuffering: false,
151
+ currentPositionMillis: 0,
152
+ durationMillis: 0,
153
+ bufferedDurationMillis: 0,
154
+ isError: false,
155
+ };
156
+ }
157
+
146
158
  export default HeadlessAudioPlayer;
@@ -1,10 +1,11 @@
1
1
  import * as React from "react";
2
- import type { Playback } from "expo-av/src/AV";
2
+ import type { AudioPlayer } from "expo-audio";
3
+ import type { VideoPlayer } from "expo-video";
3
4
 
4
5
  import type { MediaPlayerRef } from "./MediaPlayerCommon";
5
6
 
6
7
  interface MediaPlaybackWrapperProps {
7
- media?: Playback;
8
+ player?: AudioPlayer | VideoPlayer;
8
9
  isPlaying?: boolean;
9
10
  onTogglePlayback?: () => void;
10
11
  }
@@ -15,42 +16,38 @@ interface MediaPlaybackWrapperProps {
15
16
  const MediaPlaybackWrapper = React.forwardRef<
16
17
  MediaPlayerRef,
17
18
  React.PropsWithChildren<MediaPlaybackWrapperProps>
18
- >(({ media, isPlaying, onTogglePlayback, children }, ref) => {
19
- const togglePlayback = React.useCallback(async () => {
19
+ >(({ player, isPlaying, onTogglePlayback, children }, ref) => {
20
+ const togglePlayback = React.useCallback(() => {
20
21
  onTogglePlayback?.();
21
22
 
22
23
  if (isPlaying) {
23
- await media?.pauseAsync();
24
+ player?.pause();
24
25
  } else {
25
- await media?.playAsync();
26
+ player?.play();
26
27
  }
27
- }, [media, isPlaying, onTogglePlayback]);
28
+ }, [isPlaying, onTogglePlayback]);
28
29
 
29
- const pause = React.useCallback(async () => {
30
+ const pause = React.useCallback(() => {
30
31
  onTogglePlayback?.();
31
- await media?.pauseAsync();
32
- }, [media, onTogglePlayback]);
32
+ player?.pause();
33
+ }, [player, onTogglePlayback]);
33
34
 
34
- const play = React.useCallback(async () => {
35
+ const play = React.useCallback(() => {
35
36
  onTogglePlayback?.();
36
- await media?.playAsync();
37
- }, [media, onTogglePlayback]);
37
+ player?.play();
38
+ }, [player, onTogglePlayback]);
38
39
 
39
40
  const seekToPosition = React.useCallback(
40
- async (positionMillis: number) => {
41
- await media?.setPositionAsync(positionMillis);
41
+ (positionMillis: number) => {
42
+ if (typeof (player as any)?.seekTo === "function") {
43
+ (player as AudioPlayer).seekTo(positionMillis / 1000);
44
+ } else if (player) {
45
+ player.currentTime = positionMillis / 1000;
46
+ }
42
47
  },
43
- [media]
48
+ [player]
44
49
  );
45
50
 
46
- React.useEffect(() => {
47
- return media
48
- ? () => {
49
- media.unloadAsync();
50
- }
51
- : undefined;
52
- }, [media]);
53
-
54
51
  React.useImperativeHandle(
55
52
  ref,
56
53
  () => ({
@@ -1,4 +1,5 @@
1
- import { AVPlaybackSource, AVPlaybackStatus } from "expo-av";
1
+ import { AudioSource } from "expo-audio";
2
+ import { VideoSource } from "expo-video";
2
3
  import { v4 as uuid } from "uuid";
3
4
  import { Platform } from "react-native";
4
5
  import React from "react";
@@ -24,34 +25,7 @@ export interface MediaPlayerRef {
24
25
  export interface MediaPlayerProps {
25
26
  onPlaybackStatusUpdate?: (status: MediaPlayerStatus) => void;
26
27
  onPlaybackFinish?: () => void;
27
- source: AVPlaybackSource;
28
- }
29
-
30
- export function mapToMediaPlayerStatus(
31
- status: AVPlaybackStatus
32
- ): MediaPlayerStatus {
33
- if (status.isLoaded) {
34
- return {
35
- isPlaying: status.isPlaying,
36
- isLoading: false,
37
- isBuffering: status.isBuffering,
38
- currentPositionMillis: status.positionMillis || 0,
39
- durationMillis: status.durationMillis || 0,
40
- bufferedDurationMillis: status.playableDurationMillis || 0,
41
- isError: false,
42
- };
43
- }
44
-
45
- return {
46
- isPlaying: false,
47
- isLoading: false,
48
- isBuffering: false,
49
- currentPositionMillis: 0,
50
- durationMillis: 0,
51
- bufferedDurationMillis: 0,
52
- isError: true,
53
- error: status.error,
54
- };
28
+ source: AudioSource | VideoSource;
55
29
  }
56
30
 
57
31
  const URL_REGEX =
@@ -60,14 +34,14 @@ const URL_REGEX =
60
34
  /**
61
35
  * Base64 strings are not playable on iOS and needs to be saved to a file before playing
62
36
  */
63
- export async function normalizeBase64Source(
64
- source: AVPlaybackSource,
37
+ export function normalizeBase64Source(
38
+ source: AudioSource | VideoSource,
65
39
  type: "audio" | "video"
66
- ): Promise<AVPlaybackSource> {
40
+ ): AudioSource | VideoSource {
67
41
  const uri: string | undefined = (source as any)?.uri;
68
42
 
69
43
  if (Platform.OS === "ios" && uri && !uri.match(URL_REGEX)) {
70
- const { File, Paths } = await import("expo-file-system");
44
+ const { File, Paths } = require("expo-file-system");
71
45
 
72
46
  const defaultMimeType = type === "audio" ? "wav" : "mp4";
73
47
  const mimeType = uri.startsWith(`data:${type}/`)
@@ -100,7 +74,7 @@ function sourceDeepCompareEquals(a: any, b: any) {
100
74
  return a === b;
101
75
  }
102
76
 
103
- function useSourceDeepCompareMemoize(value: any) {
77
+ export function useSourceDeepCompareMemoize(value: any) {
104
78
  const ref = React.useRef<any>(undefined);
105
79
  if (!sourceDeepCompareEquals(value, ref.current)) {
106
80
  ref.current = value;