@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.
- 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 +3 -1
- package/lib/typescript/src/components/MediaPlayer/AudioPlayer/HeadlessAudioPlayer.js +69 -54
- package/lib/typescript/src/components/MediaPlayer/AudioPlayer/HeadlessAudioPlayer.js.map +1 -1
- package/lib/typescript/src/components/MediaPlayer/MediaPlaybackWrapper.d.ts +3 -2
- package/lib/typescript/src/components/MediaPlayer/MediaPlaybackWrapper.js +19 -21
- package/lib/typescript/src/components/MediaPlayer/MediaPlaybackWrapper.js.map +1 -1
- package/lib/typescript/src/components/MediaPlayer/MediaPlayerCommon.d.ts +5 -4
- package/lib/typescript/src/components/MediaPlayer/MediaPlayerCommon.js +3 -26
- package/lib/typescript/src/components/MediaPlayer/MediaPlayerCommon.js.map +1 -1
- package/lib/typescript/src/components/MediaPlayer/VideoPlayer/VideoPlayer.d.ts +14 -4
- package/lib/typescript/src/components/MediaPlayer/VideoPlayer/VideoPlayer.js +125 -62
- package/lib/typescript/src/components/MediaPlayer/VideoPlayer/VideoPlayer.js.map +1 -1
- package/lib/typescript/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -4
- package/src/components/MediaPlayer/AudioPlayer/HeadlessAudioPlayer.tsx +84 -72
- package/src/components/MediaPlayer/MediaPlaybackWrapper.tsx +21 -24
- package/src/components/MediaPlayer/MediaPlayerCommon.ts +8 -34
- 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-
|
|
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-
|
|
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-
|
|
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": "
|
|
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
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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
|
-
|
|
87
|
+
player.replace(normalizeBase64Source(source, "audio") as any);
|
|
88
|
+
}, [source]);
|
|
56
89
|
|
|
57
90
|
const updateAudioMode = React.useCallback(async () => {
|
|
58
91
|
try {
|
|
59
|
-
await
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
interruptionMode === "lower volume"
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
>(({
|
|
19
|
-
const togglePlayback = React.useCallback(
|
|
19
|
+
>(({ player, isPlaying, onTogglePlayback, children }, ref) => {
|
|
20
|
+
const togglePlayback = React.useCallback(() => {
|
|
20
21
|
onTogglePlayback?.();
|
|
21
22
|
|
|
22
23
|
if (isPlaying) {
|
|
23
|
-
|
|
24
|
+
player?.pause();
|
|
24
25
|
} else {
|
|
25
|
-
|
|
26
|
+
player?.play();
|
|
26
27
|
}
|
|
27
|
-
}, [
|
|
28
|
+
}, [isPlaying, onTogglePlayback]);
|
|
28
29
|
|
|
29
|
-
const pause = React.useCallback(
|
|
30
|
+
const pause = React.useCallback(() => {
|
|
30
31
|
onTogglePlayback?.();
|
|
31
|
-
|
|
32
|
-
}, [
|
|
32
|
+
player?.pause();
|
|
33
|
+
}, [player, onTogglePlayback]);
|
|
33
34
|
|
|
34
|
-
const play = React.useCallback(
|
|
35
|
+
const play = React.useCallback(() => {
|
|
35
36
|
onTogglePlayback?.();
|
|
36
|
-
|
|
37
|
-
}, [
|
|
37
|
+
player?.play();
|
|
38
|
+
}, [player, onTogglePlayback]);
|
|
38
39
|
|
|
39
40
|
const seekToPosition = React.useCallback(
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
[
|
|
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 {
|
|
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:
|
|
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
|
|
64
|
-
source:
|
|
37
|
+
export function normalizeBase64Source(
|
|
38
|
+
source: AudioSource | VideoSource,
|
|
65
39
|
type: "audio" | "video"
|
|
66
|
-
):
|
|
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 } =
|
|
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;
|