Skip to content

Commit 2645b78

Browse files
authored
Merge pull request #265 from sendbird/feat/migrate-expo-av-to-expo-video-audio
[CLNP-7591] feat: add support for expo-video and expo-audio for Expo SDK 54
2 parents 220fe84 + 485cc67 commit 2645b78

File tree

9 files changed

+2307
-310
lines changed

9 files changed

+2307
-310
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
runs-on: ubuntu-latest
1818
strategy:
1919
matrix:
20-
node-version: [18.x]
20+
node-version: [20.x]
2121
steps:
2222
- uses: actions/checkout@v4
2323
- name: Use Node.js ${{ matrix.node-version }}
@@ -47,7 +47,7 @@ jobs:
4747
runs-on: ubuntu-latest
4848
strategy:
4949
matrix:
50-
node-version: [18.x]
50+
node-version: [20.x]
5151
steps:
5252
- uses: actions/checkout@v4
5353
- name: Use Node.js ${{ matrix.node-version }}
@@ -69,7 +69,7 @@ jobs:
6969
runs-on: ubuntu-latest
7070
strategy:
7171
matrix:
72-
node-version: [18.x]
72+
node-version: [20.x]
7373
steps:
7474
- uses: actions/checkout@v4
7575
- name: Use Node.js ${{ matrix.node-version }}
@@ -91,7 +91,7 @@ jobs:
9191
runs-on: ubuntu-latest
9292
strategy:
9393
matrix:
94-
node-version: [18.x]
94+
node-version: [20.x]
9595
steps:
9696
- uses: actions/checkout@v4
9797
- name: Use Node.js ${{ matrix.node-version }}
@@ -119,7 +119,7 @@ jobs:
119119
runs-on: ubuntu-latest
120120
strategy:
121121
matrix:
122-
node-version: [18.x]
122+
node-version: [20.x]
123123
steps:
124124
- uses: actions/checkout@v4
125125
- name: Use Node.js ${{ matrix.node-version }}

docs-validation/1_introduction/NativeModules.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ import * as ExpoFS from 'expo-file-system';
6868
import * as ExpoImagePicker from 'expo-image-picker';
6969
import * as ExpoMediaLibrary from 'expo-media-library';
7070
import * as ExpoNotifications from 'expo-notifications';
71-
import * as ExpoAV from 'expo-av';
71+
import * as ExpoAudio from 'expo-audio';
72+
import * as ExpoVideo from 'expo-video';
7273
import * as ExpoVideoThumbnail from 'expo-video-thumbnails';
7374
import * as ExpoImageManipulator from 'expo-image-manipulator';
7475

@@ -82,16 +83,16 @@ const expoPlatformServices = {
8283
documentPickerModule: ExpoDocumentPicker,
8384
}),
8485
media: createExpoMediaService({
85-
avModule: ExpoAV,
86+
avModule: ExpoVideo,
8687
thumbnailModule: ExpoVideoThumbnail,
8788
imageManipulator: ExpoImageManipulator,
8889
fsModule: ExpoFS,
8990
}),
9091
player: createExpoPlayerService({
91-
avModule: ExpoAV,
92+
avModule: ExpoAudio,
9293
}),
9394
recorder: createExpoRecorderService({
94-
avModule: ExpoAV,
95+
avModule: ExpoAudio,
9596
}),
9697
};
9798
/** ------------------ **/

packages/uikit-react-native/package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,10 @@
7777
"@types/react": "*",
7878
"@types/react-native": "*",
7979
"date-fns": ">=2.28.0",
80+
"expo": "^54.0.12",
8081
"expo-av": "^13.2.1",
82+
"expo-audio": "^1.0.13",
83+
"expo-video": "^3.0.11",
8184
"expo-clipboard": "^4.1.2",
8285
"expo-document-picker": "^11.5.3",
8386
"expo-file-system": "^15.2.2",
@@ -118,6 +121,8 @@
118121
"@sendbird/uikit-tools": ">=0.0.10",
119122
"date-fns": ">=2.28.0",
120123
"expo-av": ">=12.0.4",
124+
"expo-audio": ">=1.0.0",
125+
"expo-video": ">=3.0.0",
121126
"expo-clipboard": ">=2.1.1",
122127
"expo-document-picker": ">=10.1.3",
123128
"expo-file-system": ">=13.1.4",
@@ -165,6 +170,12 @@
165170
"expo-av": {
166171
"optional": true
167172
},
173+
"expo-audio": {
174+
"optional": true
175+
},
176+
"expo-video": {
177+
"optional": true
178+
},
168179
"expo-clipboard": {
169180
"optional": true
170181
},

packages/uikit-react-native/src/platform/createMediaService.expo.tsx

Lines changed: 87 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,112 @@
11
import type * as ExpoAV from 'expo-av';
22
import type * as ExpoFS from 'expo-file-system';
33
import type * as ExpoImageManipulator from 'expo-image-manipulator';
4+
import type { EventSubscription } from 'expo-modules-core';
5+
import type * as ExpoVideo from 'expo-video';
6+
import type { StatusChangeEventPayload } from 'expo-video';
47
import type * as ExpoVideoThumbnail from 'expo-video-thumbnails';
5-
import React from 'react';
8+
import React, { useEffect } from 'react';
69

7-
import { getDownscaleSize } from '@sendbird/uikit-utils';
10+
import { Logger, getDownscaleSize } from '@sendbird/uikit-utils';
811

912
import SBUUtils from '../libs/SBUUtils';
1013
import expoBackwardUtils from '../utils/expoBackwardUtils';
11-
import type { MediaServiceInterface } from './types';
14+
import type { ExpoVideoModule } from '../utils/expoBackwardUtils';
15+
import type { MediaServiceInterface, VideoProps } from './types';
1216

1317
type Modules = {
14-
avModule: typeof ExpoAV;
18+
avModule: ExpoVideoModule;
1519
thumbnailModule: typeof ExpoVideoThumbnail;
1620
imageManipulator: typeof ExpoImageManipulator;
1721
fsModule: typeof ExpoFS;
1822
};
1923

24+
interface VideoModuleAdapter {
25+
VideoComponent: React.ComponentType<VideoProps>;
26+
}
27+
28+
class LegacyExpoAVVideoAdapter implements VideoModuleAdapter {
29+
private readonly avModule: typeof ExpoAV;
30+
constructor(avModule: typeof ExpoAV) {
31+
this.avModule = avModule;
32+
}
33+
34+
VideoComponent = ({ source, resizeMode, onLoad, ...props }: VideoProps) => {
35+
// FIXME: type error https://github.com/expo/expo/issues/17101
36+
// @ts-ignore
37+
return <this.avModule.Video {...props} source={source} resizeMode={resizeMode} onLoad={onLoad} useNativeControls />;
38+
};
39+
}
40+
41+
class ExpoVideoAdapter implements VideoModuleAdapter {
42+
constructor(private readonly _videoModule: typeof ExpoVideo) {}
43+
44+
VideoComponent = ({ source, resizeMode, onLoad, ...props }: VideoProps) => {
45+
const player = this._videoModule.useVideoPlayer(source);
46+
47+
useEffect(() => {
48+
if (onLoad && player) {
49+
let subscription: EventSubscription | null = null;
50+
try {
51+
subscription = player.addListener('statusChange', (eventData: StatusChangeEventPayload) => {
52+
const { status, error } = eventData;
53+
if (status === 'readyToPlay' && !error) {
54+
onLoad();
55+
}
56+
});
57+
} catch (error) {
58+
const timeout = setTimeout(() => onLoad(), 300);
59+
return () => clearTimeout(timeout);
60+
}
61+
62+
return () => {
63+
if (subscription) {
64+
subscription.remove();
65+
}
66+
};
67+
}
68+
return undefined;
69+
}, [onLoad, player]);
70+
71+
const getContentFit = (mode: typeof resizeMode): 'cover' | 'contain' | 'fill' => {
72+
switch (mode) {
73+
case 'cover':
74+
return 'cover';
75+
case 'contain':
76+
return 'contain';
77+
case 'stretch':
78+
return 'fill';
79+
default:
80+
return 'contain';
81+
}
82+
};
83+
84+
return React.createElement(this._videoModule.VideoView, {
85+
...props,
86+
player,
87+
contentFit: getContentFit(resizeMode),
88+
});
89+
};
90+
}
91+
2092
const createExpoMediaService = ({
2193
avModule,
2294
thumbnailModule,
2395
imageManipulator,
2496
fsModule,
2597
}: Modules): MediaServiceInterface => {
98+
if (expoBackwardUtils.expoAV.isLegacyAVModule(avModule)) {
99+
Logger.warn(
100+
'[MediaService.Expo] expo-av is deprecated and will be removed in Expo 54. Please migrate to expo-video.',
101+
);
102+
}
103+
104+
const videoAdapter = expoBackwardUtils.expoAV.isVideoModule(avModule)
105+
? new ExpoVideoAdapter(avModule)
106+
: new LegacyExpoAVVideoAdapter(avModule);
107+
26108
return {
27-
VideoComponent({ source, resizeMode, onLoad, ...props }) {
28-
// FIXME: type error https://github.com/expo/expo/issues/17101
29-
// @ts-ignore
30-
return <avModule.Video {...props} source={source} resizeMode={resizeMode} onLoad={onLoad} useNativeControls />;
31-
},
109+
VideoComponent: videoAdapter.VideoComponent,
32110
async getVideoThumbnail({ url, quality, timeMills }) {
33111
try {
34112
const { uri } = await thumbnailModule.getThumbnailAsync(url, { quality, time: timeMills });

0 commit comments

Comments
 (0)