Skip to content

Commit ea084aa

Browse files
authored
feat: add video playing optional capability through expo-av (#3248)
1 parent 08bf7f4 commit ea084aa

File tree

4 files changed

+132
-50
lines changed

4 files changed

+132
-50
lines changed
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
let AudioComponent;
2+
let VideoComponent;
23
let RecordingObject;
4+
35
try {
46
const audioVideoPackage = require('expo-av');
57
AudioComponent = audioVideoPackage.Audio;
8+
VideoComponent = audioVideoPackage.Video;
69
RecordingObject = audioVideoPackage.RecordingObject;
710
} catch (e) {
811
// do nothing
912
}
1013

11-
if (!AudioComponent) {
14+
if (!AudioComponent || !VideoComponent) {
1215
console.log(
1316
'Audio Video library is currently not installed. To allow in-app audio playback, install the "expo-av" package.',
1417
);
1518
}
1619

17-
export { AudioComponent, RecordingObject };
20+
export { AudioComponent, RecordingObject, VideoComponent };

package/expo-package/src/optionalDependencies/Video.tsx

Lines changed: 89 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useEffect } from 'react';
22

33
import { useEventListener } from 'expo';
44

5-
import { AudioComponent } from './AudioVideo';
5+
import { AudioComponent, VideoComponent as ExpoAVVideoComponent } from './AudioVideo';
66

77
let videoPackage;
88

@@ -18,56 +18,98 @@ if (!videoPackage) {
1818
);
1919
}
2020

21-
const VideoComponent = videoPackage ? videoPackage.VideoView : null;
21+
const VideoComponent = videoPackage ? videoPackage.VideoView : ExpoAVVideoComponent;
2222
const useVideoPlayer = videoPackage ? videoPackage.useVideoPlayer : null;
2323

24-
export const Video = videoPackage
25-
? ({ onLoadStart, onLoad, onEnd, onProgress, onBuffer, resizeMode, style, uri, videoRef }) => {
26-
const player = useVideoPlayer(uri, (player) => {
27-
player.timeUpdateEventInterval = 0.5;
28-
videoRef.current = player;
29-
});
24+
let Video = null;
3025

31-
useEventListener(player, 'statusChange', ({ status, oldStatus }) => {
32-
if ((!oldStatus || oldStatus === 'idle') && status === 'loading') {
33-
onLoadStart();
34-
} else if ((oldStatus === 'loading' || oldStatus === 'idle') && status === 'readyToPlay') {
35-
onLoad({ duration: player.duration });
36-
onBuffer({ buffering: false });
37-
} else if (oldStatus === 'readyToPlay' && status === 'loading') {
38-
onBuffer({ buffering: true });
39-
}
40-
});
26+
// expo-video
27+
if (videoPackage) {
28+
Video = ({
29+
onLoadStart,
30+
onLoad,
31+
onEnd,
32+
onProgress,
33+
onBuffer,
34+
resizeMode,
35+
style,
36+
uri,
37+
videoRef,
38+
}) => {
39+
const player = useVideoPlayer(uri, (player) => {
40+
player.timeUpdateEventInterval = 0.5;
41+
videoRef.current = player;
42+
});
43+
44+
useEventListener(player, 'statusChange', ({ status, oldStatus }) => {
45+
if ((!oldStatus || oldStatus === 'idle') && status === 'loading') {
46+
onLoadStart();
47+
} else if ((oldStatus === 'loading' || oldStatus === 'idle') && status === 'readyToPlay') {
48+
onLoad({ duration: player.duration });
49+
onBuffer({ buffering: false });
50+
} else if (oldStatus === 'readyToPlay' && status === 'loading') {
51+
onBuffer({ buffering: true });
52+
}
53+
});
4154

42-
useEventListener(player, 'playToEnd', () => {
43-
player.replay();
44-
onEnd();
45-
});
55+
useEventListener(player, 'playToEnd', () => {
56+
player.replay();
57+
onEnd();
58+
});
4659

47-
useEventListener(player, 'timeUpdate', ({ currentTime }) =>
48-
onProgress({ currentTime, seekableDuration: player.duration }),
49-
);
60+
useEventListener(player, 'timeUpdate', ({ currentTime }) =>
61+
onProgress({ currentTime, seekableDuration: player.duration }),
62+
);
63+
64+
// This is done so that the audio of the video is not muted when the phone is in silent mode for iOS.
65+
useEffect(() => {
66+
const initializeSound = async () => {
67+
if (AudioComponent) {
68+
await AudioComponent.setAudioModeAsync({
69+
playsInSilentModeIOS: true,
70+
});
71+
}
72+
};
73+
initializeSound();
74+
}, []);
5075

51-
// This is done so that the audio of the video is not muted when the phone is in silent mode for iOS.
52-
useEffect(() => {
53-
const initializeSound = async () => {
54-
if (AudioComponent) {
55-
await AudioComponent.setAudioModeAsync({
56-
playsInSilentModeIOS: true,
57-
});
58-
}
59-
};
60-
initializeSound();
61-
}, []);
76+
return (
77+
<VideoComponent
78+
allowsFullscreen
79+
contentFit={resizeMode}
80+
nativeControls={false}
81+
player={player}
82+
style={[style]}
83+
/>
84+
);
85+
};
86+
}
87+
// expo-av
88+
else if (ExpoAVVideoComponent) {
89+
Video = ({ onPlaybackStatusUpdate, paused, resizeMode, style, uri, videoRef }) => {
90+
// This is done so that the audio of the video is not muted when the phone is in silent mode for iOS.
91+
useEffect(() => {
92+
const initializeSound = async () => {
93+
await AudioComponent.setAudioModeAsync({
94+
playsInSilentModeIOS: true,
95+
});
96+
};
97+
initializeSound();
98+
}, []);
99+
100+
return (
101+
<VideoComponent
102+
onPlaybackStatusUpdate={onPlaybackStatusUpdate}
103+
ref={videoRef}
104+
resizeMode={resizeMode}
105+
shouldPlay={!paused}
106+
source={{
107+
uri,
108+
}}
109+
style={[style]}
110+
/>
111+
);
112+
};
113+
}
62114

63-
return (
64-
<VideoComponent
65-
allowsFullscreen
66-
contentFit={resizeMode}
67-
nativeControls={false}
68-
player={player}
69-
style={[style]}
70-
/>
71-
);
72-
}
73-
: null;
115+
export { Video };

package/src/components/ImageGallery/components/AnimatedGalleryVideo.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Animated, { SharedValue } from 'react-native-reanimated';
66
import {
77
isVideoPlayerAvailable,
88
NativeHandlers,
9+
PlaybackStatus,
910
VideoPayloadData,
1011
VideoProgressData,
1112
VideoType,
@@ -97,6 +98,37 @@ export const AnimatedGalleryVideo = React.memo(
9798
}
9899
};
99100

101+
const onPlayBackStatusUpdate = (playbackStatus: PlaybackStatus) => {
102+
if (!playbackStatus.isLoaded) {
103+
// Update your UI for the unloaded state
104+
setOpacity(1);
105+
if (playbackStatus.error) {
106+
console.error(`Encountered a fatal error during playback: ${playbackStatus.error}`);
107+
}
108+
} else {
109+
// Update your UI for the loaded state
110+
setOpacity(0);
111+
handleLoad(attachmentId, playbackStatus.durationMillis);
112+
if (playbackStatus.isPlaying) {
113+
// Update your UI for the playing state
114+
handleProgress(
115+
attachmentId,
116+
playbackStatus.positionMillis / playbackStatus.durationMillis,
117+
);
118+
}
119+
120+
if (playbackStatus.isBuffering) {
121+
// Update your UI for the buffering state
122+
setOpacity(1);
123+
}
124+
125+
if (playbackStatus.didJustFinish && !playbackStatus.isLooping) {
126+
// The player has just finished playing and will stop. Maybe you want to play something else?
127+
handleEnd();
128+
}
129+
}
130+
};
131+
100132
const animatedStyles = useAnimatedGalleryStyle({
101133
index,
102134
offsetScale,
@@ -130,6 +162,7 @@ export const AnimatedGalleryVideo = React.memo(
130162
onEnd={onEnd}
131163
onLoad={onLoad}
132164
onLoadStart={onLoadStart}
165+
onPlaybackStatusUpdate={onPlayBackStatusUpdate}
133166
onProgress={onProgress}
134167
paused={paused}
135168
repeat={repeat}

package/src/components/ImageGallery/components/ImageGalleryVideoControl.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ export const ImageGalleryVideoControl = React.memo(
6363
// Note: Not particularly sure why this was ever added, but
6464
// will keep it for now for backwards compatibility.
6565
if (progress === 1) {
66-
// For expo CLI
66+
// For expo CLI, expo-av
67+
if (videoRef.current?.setPositionAsync) {
68+
await videoRef.current.setPositionAsync(0);
69+
}
70+
// For expo CLI, expo-video
6771
if (videoRef.current?.replay) {
6872
await videoRef.current.replay();
6973
}

0 commit comments

Comments
 (0)