@sendbird/uikit-react-native 3.10.3 → 3.11.1

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 (73) hide show
  1. package/README.md +9 -7
  2. package/lib/commonjs/components/ChannelInput/VoiceMessageInput.js +1 -1
  3. package/lib/commonjs/components/ChannelInput/VoiceMessageInput.js.map +1 -1
  4. package/lib/commonjs/components/ChannelInput/index.js +2 -25
  5. package/lib/commonjs/components/ChannelInput/index.js.map +1 -1
  6. package/lib/commonjs/hooks/useVoiceMessageInput.js +10 -2
  7. package/lib/commonjs/hooks/useVoiceMessageInput.js.map +1 -1
  8. package/lib/commonjs/platform/createFileService.native.js +1 -1
  9. package/lib/commonjs/platform/createFileService.native.js.map +1 -1
  10. package/lib/commonjs/platform/createMediaService.expo.js +83 -12
  11. package/lib/commonjs/platform/createMediaService.expo.js.map +1 -1
  12. package/lib/commonjs/platform/createPlayerService.expo.js +214 -113
  13. package/lib/commonjs/platform/createPlayerService.expo.js.map +1 -1
  14. package/lib/commonjs/platform/createPlayerService.native.js +191 -114
  15. package/lib/commonjs/platform/createPlayerService.native.js.map +1 -1
  16. package/lib/commonjs/platform/createRecorderService.expo.js +248 -127
  17. package/lib/commonjs/platform/createRecorderService.expo.js.map +1 -1
  18. package/lib/commonjs/platform/createRecorderService.native.js +212 -129
  19. package/lib/commonjs/platform/createRecorderService.native.js.map +1 -1
  20. package/lib/commonjs/platform/types.js.map +1 -1
  21. package/lib/commonjs/utils/expoBackwardUtils.js +23 -0
  22. package/lib/commonjs/utils/expoBackwardUtils.js.map +1 -1
  23. package/lib/commonjs/utils/expoPermissionGranted.js.map +1 -1
  24. package/lib/commonjs/version.js +1 -1
  25. package/lib/commonjs/version.js.map +1 -1
  26. package/lib/module/components/ChannelInput/VoiceMessageInput.js +1 -1
  27. package/lib/module/components/ChannelInput/VoiceMessageInput.js.map +1 -1
  28. package/lib/module/components/ChannelInput/index.js +3 -26
  29. package/lib/module/components/ChannelInput/index.js.map +1 -1
  30. package/lib/module/hooks/useVoiceMessageInput.js +10 -2
  31. package/lib/module/hooks/useVoiceMessageInput.js.map +1 -1
  32. package/lib/module/platform/createFileService.native.js +1 -1
  33. package/lib/module/platform/createFileService.native.js.map +1 -1
  34. package/lib/module/platform/createMediaService.expo.js +82 -13
  35. package/lib/module/platform/createMediaService.expo.js.map +1 -1
  36. package/lib/module/platform/createPlayerService.expo.js +214 -113
  37. package/lib/module/platform/createPlayerService.expo.js.map +1 -1
  38. package/lib/module/platform/createPlayerService.native.js +191 -114
  39. package/lib/module/platform/createPlayerService.native.js.map +1 -1
  40. package/lib/module/platform/createRecorderService.expo.js +249 -128
  41. package/lib/module/platform/createRecorderService.expo.js.map +1 -1
  42. package/lib/module/platform/createRecorderService.native.js +212 -129
  43. package/lib/module/platform/createRecorderService.native.js.map +1 -1
  44. package/lib/module/platform/types.js.map +1 -1
  45. package/lib/module/utils/expoBackwardUtils.js +23 -0
  46. package/lib/module/utils/expoBackwardUtils.js.map +1 -1
  47. package/lib/module/utils/expoPermissionGranted.js.map +1 -1
  48. package/lib/module/version.js +1 -1
  49. package/lib/module/version.js.map +1 -1
  50. package/lib/typescript/src/containers/SendbirdUIKitContainer.d.ts +1 -1
  51. package/lib/typescript/src/platform/createMediaService.expo.d.ts +2 -2
  52. package/lib/typescript/src/platform/createPlayerService.expo.d.ts +2 -2
  53. package/lib/typescript/src/platform/createPlayerService.native.d.ts +5 -3
  54. package/lib/typescript/src/platform/createRecorderService.expo.d.ts +2 -2
  55. package/lib/typescript/src/platform/createRecorderService.native.d.ts +5 -3
  56. package/lib/typescript/src/platform/types.d.ts +4 -0
  57. package/lib/typescript/src/utils/expoBackwardUtils.d.ts +10 -0
  58. package/lib/typescript/src/utils/expoPermissionGranted.d.ts +1 -1
  59. package/lib/typescript/src/version.d.ts +1 -1
  60. package/package.json +29 -5
  61. package/src/components/ChannelInput/VoiceMessageInput.tsx +1 -1
  62. package/src/components/ChannelInput/index.tsx +6 -36
  63. package/src/hooks/useVoiceMessageInput.ts +7 -2
  64. package/src/platform/createFileService.native.ts +1 -1
  65. package/src/platform/createMediaService.expo.tsx +87 -9
  66. package/src/platform/createPlayerService.expo.tsx +242 -109
  67. package/src/platform/createPlayerService.native.tsx +237 -113
  68. package/src/platform/createRecorderService.expo.tsx +268 -107
  69. package/src/platform/createRecorderService.native.tsx +240 -118
  70. package/src/platform/types.ts +5 -0
  71. package/src/utils/expoBackwardUtils.ts +29 -0
  72. package/src/utils/expoPermissionGranted.ts +3 -1
  73. package/src/version.ts +1 -1
@@ -1,7 +1,7 @@
1
- import * as ExpoAV from 'expo-av';
1
+ import type { ExpoAudioModule } from '../utils/expoBackwardUtils';
2
2
  import type { RecorderServiceInterface } from './types';
3
3
  type Modules = {
4
- avModule: typeof ExpoAV;
4
+ avModule: ExpoAudioModule;
5
5
  };
6
6
  declare const createExpoRecorderService: ({ avModule }: Modules) => RecorderServiceInterface;
7
7
  export default createExpoRecorderService;
@@ -1,9 +1,11 @@
1
- import * as RNAudioRecorder from 'react-native-audio-recorder-player';
1
+ import type * as LegacyModule from 'react-native-audio-recorder-player';
2
+ import type * as NitroSoundOrLegacyV4Module from 'react-native-nitro-sound';
2
3
  import * as Permissions from 'react-native-permissions';
3
4
  import type { RecorderServiceInterface } from './types';
5
+ export type AudioRecorderModule = typeof LegacyModule | typeof NitroSoundOrLegacyV4Module;
4
6
  type Modules = {
5
- audioRecorderModule: typeof RNAudioRecorder;
7
+ audioRecorderModule: AudioRecorderModule;
6
8
  permissionModule: typeof Permissions;
7
9
  };
8
- declare const createNativeRecorderService: ({ audioRecorderModule, permissionModule }: Modules) => RecorderServiceInterface;
10
+ declare const createNativeRecorderService: (modules: Modules) => RecorderServiceInterface;
9
11
  export default createNativeRecorderService;
@@ -191,4 +191,8 @@ export interface RecorderServiceInterface {
191
191
  * [*] to [idle]
192
192
  * */
193
193
  reset(): Promise<void>;
194
+ /**
195
+ * Get the record path processed for the specific platform and adapter.
196
+ * */
197
+ convertRecordPath(uri: string): string;
194
198
  }
@@ -1,6 +1,9 @@
1
+ import type * as ExpoAudio from 'expo-audio';
2
+ import type * as ExpoAV from 'expo-av';
1
3
  import type * as ExpoDocumentPicker from 'expo-document-picker';
2
4
  import type * as ExpoFs from 'expo-file-system';
3
5
  import type * as ExpoImagePicker from 'expo-image-picker';
6
+ import type * as ExpoVideo from 'expo-video';
4
7
  import type { FilePickerResponse } from '../platform/types';
5
8
  declare const expoBackwardUtils: {
6
9
  imagePicker: {
@@ -11,6 +14,13 @@ declare const expoBackwardUtils: {
11
14
  isCanceled(result: ExpoDocumentPicker.DocumentPickerResult): boolean;
12
15
  toFilePickerResponses(result: ExpoDocumentPicker.DocumentPickerResult): Promise<FilePickerResponse[]>;
13
16
  };
17
+ expoAV: {
18
+ isLegacyAVModule(module: ExpoAudioModule | ExpoVideoModule): module is typeof ExpoAV;
19
+ isAudioModule(module: ExpoAudioModule): module is typeof ExpoAudio;
20
+ isVideoModule(module: ExpoVideoModule): module is typeof ExpoVideo;
21
+ };
14
22
  toFileSize(info: ExpoFs.FileInfo): number;
15
23
  };
24
+ export type ExpoAudioModule = typeof ExpoAV | typeof ExpoAudio;
25
+ export type ExpoVideoModule = typeof ExpoAV | typeof ExpoVideo;
16
26
  export default expoBackwardUtils;
@@ -7,7 +7,7 @@ export interface ExpoPermissionResponse {
7
7
  export interface ExpoMediaLibraryPermissionResponse extends ExpoPermissionResponse {
8
8
  accessPrivileges?: 'all' | 'limited' | 'none';
9
9
  }
10
- export interface ExpoPushPermissionResponse extends ExpoPermissionResponse, NotificationPermissionsStatus {
10
+ export interface ExpoPushPermissionResponse extends Omit<ExpoPermissionResponse, 'status'>, NotificationPermissionsStatus {
11
11
  }
12
12
  declare const expoPermissionGranted: (stats: Array<ExpoMediaLibraryPermissionResponse | ExpoPushPermissionResponse | ExpoPermissionResponse>, limitedCallback?: () => void) => boolean;
13
13
  export default expoPermissionGranted;
@@ -1,2 +1,2 @@
1
- declare const VERSION = "3.10.3";
1
+ declare const VERSION = "3.11.1";
2
2
  export default VERSION;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendbird/uikit-react-native",
3
- "version": "3.10.3",
3
+ "version": "3.11.1",
4
4
  "description": "Sendbird UIKit for React Native: A feature-rich and customizable chat UI kit with messaging, channel management, and user authentication.",
5
5
  "keywords": [
6
6
  "sendbird",
@@ -60,10 +60,10 @@
60
60
  },
61
61
  "dependencies": {
62
62
  "@openspacelabs/react-native-zoomable-view": "^2.1.5",
63
- "@sendbird/uikit-chat-hooks": "3.10.3",
64
- "@sendbird/uikit-react-native-foundation": "3.10.3",
63
+ "@sendbird/uikit-chat-hooks": "3.11.1",
64
+ "@sendbird/uikit-react-native-foundation": "3.11.1",
65
65
  "@sendbird/uikit-tools": "0.0.15",
66
- "@sendbird/uikit-utils": "3.10.3"
66
+ "@sendbird/uikit-utils": "3.11.1"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@bam.tech/react-native-image-resizer": "^3.0.4",
@@ -77,6 +77,8 @@
77
77
  "@types/react": "*",
78
78
  "@types/react-native": "*",
79
79
  "date-fns": ">=2.28.0",
80
+ "expo": "^54.0.12",
81
+ "expo-audio": "^1.0.13",
80
82
  "expo-av": "^13.2.1",
81
83
  "expo-clipboard": "^4.1.2",
82
84
  "expo-document-picker": "^11.5.3",
@@ -85,6 +87,7 @@
85
87
  "expo-image-picker": "^14.1.1",
86
88
  "expo-media-library": "^16.0.0",
87
89
  "expo-notifications": "^0.18.1",
90
+ "expo-video": "^3.0.11",
88
91
  "expo-video-thumbnails": "^7.2.1",
89
92
  "glob": "^7.2.0",
90
93
  "inquirer": "^8.2.0",
@@ -98,6 +101,8 @@
98
101
  "react-native-file-access": "^3.1.0",
99
102
  "react-native-image-picker": "^7.1.2",
100
103
  "react-native-mmkv": "^2.12.2",
104
+ "react-native-nitro-modules": "^0.29.4",
105
+ "react-native-nitro-sound": "^0.2.0",
101
106
  "react-native-permissions": "^3.10.1",
102
107
  "react-native-safe-area-context": "^4.10.8",
103
108
  "react-native-video": "^6.3.0",
@@ -115,6 +120,7 @@
115
120
  "@sendbird/react-native-scrollview-enhancer": "*",
116
121
  "@sendbird/uikit-tools": ">=0.0.10",
117
122
  "date-fns": ">=2.28.0",
123
+ "expo-audio": ">=1.0.0",
118
124
  "expo-av": ">=12.0.4",
119
125
  "expo-clipboard": ">=2.1.1",
120
126
  "expo-document-picker": ">=10.1.3",
@@ -123,6 +129,7 @@
123
129
  "expo-image-picker": ">=12.0.2",
124
130
  "expo-media-library": ">=16.0.0",
125
131
  "expo-notifications": ">=0.14.1",
132
+ "expo-video": ">=3.0.0",
126
133
  "expo-video-thumbnails": ">=6.4.0",
127
134
  "react": ">=17.0.2",
128
135
  "react-native": ">=0.65.0",
@@ -132,6 +139,8 @@
132
139
  "react-native-file-access": ">=2.4.3",
133
140
  "react-native-image-picker": ">=4.7.1",
134
141
  "react-native-mmkv": ">=2.0.0",
142
+ "react-native-nitro-modules": "*",
143
+ "react-native-nitro-sound": ">=0.2.0",
135
144
  "react-native-permissions": ">=3.6.0",
136
145
  "react-native-safe-area-context": ">=3.3.2",
137
146
  "react-native-video": ">=5.2.0"
@@ -161,6 +170,12 @@
161
170
  "expo-av": {
162
171
  "optional": true
163
172
  },
173
+ "expo-audio": {
174
+ "optional": true
175
+ },
176
+ "expo-video": {
177
+ "optional": true
178
+ },
164
179
  "expo-clipboard": {
165
180
  "optional": true
166
181
  },
@@ -202,6 +217,15 @@
202
217
  },
203
218
  "react-native-video": {
204
219
  "optional": true
220
+ },
221
+ "react-native-audio-recorder-player": {
222
+ "optional": true
223
+ },
224
+ "react-native-nitro-modules": {
225
+ "optional": true
226
+ },
227
+ "react-native-nitro-sound": {
228
+ "optional": true
205
229
  }
206
230
  },
207
231
  "react-native-builder-bob": {
@@ -218,5 +242,5 @@
218
242
  ]
219
243
  ]
220
244
  },
221
- "gitHead": "2f85a74e995069845ec6876d1339a66b299b642b"
245
+ "gitHead": "16f1f54e8c442492e79a9591ae32203d5db55c2b"
222
246
  }
@@ -75,7 +75,7 @@ const VoiceMessageInput = ({ onClose, onSend }: VoiceMessageInputProps) => {
75
75
  break;
76
76
  }
77
77
  } catch (error) {
78
- Logger.warn('Failed to run voice message action.', state);
78
+ Logger.warn('Failed to run voice message action.', state, error);
79
79
  }
80
80
  };
81
81
  const renderActionIcon = () => {
@@ -1,6 +1,5 @@
1
1
  import React, { useEffect, useMemo, useState } from 'react';
2
2
  import {
3
- Keyboard,
4
3
  KeyboardAvoidingView,
5
4
  Platform,
6
5
  StyleProp,
@@ -95,12 +94,7 @@ export type ChannelInputProps = {
95
94
  };
96
95
 
97
96
  const AUTO_FOCUS = Platform.select({ ios: false, android: true, default: false });
98
- const isAndroidApi35Plus = Platform.OS === 'android' && Platform.Version >= 35;
99
- const KEYBOARD_AVOID_VIEW_BEHAVIOR = Platform.select({
100
- ios: 'padding' as const,
101
- android: isAndroidApi35Plus ? ('padding' as const) : undefined,
102
- default: undefined,
103
- });
97
+ const KEYBOARD_AVOID_VIEW_BEHAVIOR = Platform.select({ ios: 'padding' as const, default: undefined });
104
98
 
105
99
  // FIXME(iOS): Dynamic style does not work properly when typing the CJK. (https://github.com/facebook/react-native/issues/26107)
106
100
  // To workaround temporarily, change the key for re-mount the component.
@@ -115,16 +109,6 @@ const ChannelInput = (props: ChannelInputProps) => {
115
109
 
116
110
  const safeArea = useSafeAreaPadding(['top', 'left', 'right', 'bottom']);
117
111
 
118
- // Android API 35+ keyboard avoidance handling
119
- /**
120
- * Android API 35+ introduced edge-to-edge layouts, which changed how keyboard avoidance should be handled.
121
- * For API 35+, the system manages insets automatically, so we use the provided keyboardAvoidOffset directly.
122
- * For older Android versions, we manually subtract the safe area bottom padding to avoid overlapping with system UI.
123
- * See: https://developer.android.com/develop/ui/views/layout/edge-to-edge
124
- */
125
- const keyboardVerticalOffset = isAndroidApi35Plus
126
- ? keyboardAvoidOffset
127
- : -safeArea.paddingBottom + keyboardAvoidOffset;
128
112
  const { colors, typography } = useUIKitTheme();
129
113
  const { sbOptions, mentionManager } = useSendbirdChat();
130
114
 
@@ -161,30 +145,16 @@ const ChannelInput = (props: ChannelInputProps) => {
161
145
  onChangeText(replace(text, searchStringRange.start, searchStringRange.end, mentionedMessageText), { user, range });
162
146
  };
163
147
 
164
- const [keyboardShown, setKeyboardShown] = useState(false);
165
-
166
- useEffect(() => {
167
- const keyboardDidShow = () => setKeyboardShown(true);
168
- const keyboardDidHide = () => setKeyboardShown(false);
169
-
170
- const showSubscription = Keyboard.addListener('keyboardDidShow', keyboardDidShow);
171
- const hideSubscription = Keyboard.addListener('keyboardDidHide', keyboardDidHide);
172
-
173
- return () => {
174
- showSubscription.remove();
175
- hideSubscription.remove();
176
- };
177
- }, []);
178
-
179
- const shouldShowSafeAreaBottom = !isAndroidApi35Plus || (isAndroidApi35Plus && !keyboardShown);
180
-
181
148
  if (!props.shouldRenderInput) {
182
149
  return <SafeAreaBottom height={safeArea.paddingBottom} />;
183
150
  }
184
151
 
185
152
  return (
186
153
  <>
187
- <KeyboardAvoidingView keyboardVerticalOffset={keyboardVerticalOffset} behavior={KEYBOARD_AVOID_VIEW_BEHAVIOR}>
154
+ <KeyboardAvoidingView
155
+ keyboardVerticalOffset={-safeArea.paddingBottom + keyboardAvoidOffset}
156
+ behavior={KEYBOARD_AVOID_VIEW_BEHAVIOR}
157
+ >
188
158
  <View
189
159
  style={{
190
160
  paddingStart: safeArea.paddingStart,
@@ -224,7 +194,7 @@ const ChannelInput = (props: ChannelInputProps) => {
224
194
  />
225
195
  )}
226
196
  </View>
227
- {shouldShowSafeAreaBottom && <SafeAreaBottom height={safeArea.paddingBottom} />}
197
+ <SafeAreaBottom height={safeArea.paddingBottom} />
228
198
  </View>
229
199
  </KeyboardAvoidingView>
230
200
  {mentionAvailable && props.SuggestedMentionList && (
@@ -161,8 +161,10 @@ const useVoiceMessageInput = ({ onSend, onClose }: Props): VoiceMessageInputResu
161
161
  setVoiceMessageRecordingPath({ recordFilePath: recorderService.uri, uri: recorderService.uri });
162
162
  }
163
163
  } else {
164
- setVoiceMessageRecordingPath(fileService.createRecordFilePath(recorderService.options.extension));
165
- await recorderService.record(getVoiceMessageRecordingPath().recordFilePath);
164
+ const recordFilePath = fileService.createRecordFilePath(recorderService.options.extension);
165
+ const convertedRecordPath = recorderService.convertRecordPath(recordFilePath.recordFilePath);
166
+ setVoiceMessageRecordingPath({ ...recordFilePath, recordFilePath: convertedRecordPath });
167
+ await recorderService.record(convertedRecordPath);
166
168
  }
167
169
  }
168
170
  },
@@ -225,6 +227,9 @@ const useVoiceMessageInput = ({ onSend, onClose }: Props): VoiceMessageInputResu
225
227
  matchesOneOf(status, ['recording', 'recording_completed', 'playing', 'playing_paused']) &&
226
228
  recordingPath.current
227
229
  ) {
230
+ if (status === 'recording') {
231
+ await recorderService.stop();
232
+ }
228
233
  const voiceFile = getVoiceMessageFileObject(recordingPath.current.uri, recorderService.options.extension);
229
234
  onSend(voiceFile, Math.floor(recordingTime.currentTime));
230
235
  await clear();
@@ -242,7 +242,7 @@ const createNativeFileService = ({
242
242
  return Platform.select({
243
243
  ios: {
244
244
  uri: path,
245
- recordFilePath: filename,
245
+ recordFilePath: path,
246
246
  },
247
247
  android: {
248
248
  uri: path.startsWith('file://') ? path : 'file://' + path,
@@ -1,34 +1,112 @@
1
1
  import type * as ExpoAV from 'expo-av';
2
2
  import type * as ExpoFS from 'expo-file-system';
3
3
  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';
4
7
  import type * as ExpoVideoThumbnail from 'expo-video-thumbnails';
5
- import React from 'react';
8
+ import React, { useEffect } from 'react';
6
9
 
7
- import { getDownscaleSize } from '@sendbird/uikit-utils';
10
+ import { Logger, getDownscaleSize } from '@sendbird/uikit-utils';
8
11
 
9
12
  import SBUUtils from '../libs/SBUUtils';
10
13
  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';
12
16
 
13
17
  type Modules = {
14
- avModule: typeof ExpoAV;
18
+ avModule: ExpoVideoModule;
15
19
  thumbnailModule: typeof ExpoVideoThumbnail;
16
20
  imageManipulator: typeof ExpoImageManipulator;
17
21
  fsModule: typeof ExpoFS;
18
22
  };
19
23
 
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
+
20
92
  const createExpoMediaService = ({
21
93
  avModule,
22
94
  thumbnailModule,
23
95
  imageManipulator,
24
96
  fsModule,
25
97
  }: 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
+
26
108
  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,
32
110
  async getVideoThumbnail({ url, quality, timeMills }) {
33
111
  try {
34
112
  const { uri } = await thumbnailModule.getThumbnailAsync(url, { quality, time: timeMills });