@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,155 +1,279 @@
1
1
  import { Platform } from 'react-native';
2
- import type * as RNAudioRecorder from 'react-native-audio-recorder-player';
2
+ import type * as LegacyModule from 'react-native-audio-recorder-player';
3
+ import type * as NitroSoundOrLegacyV4Module from 'react-native-nitro-sound';
3
4
  import * as Permissions from 'react-native-permissions';
4
5
 
5
6
  import { Logger, matchesOneOf, sleep } from '@sendbird/uikit-utils';
6
7
 
8
+ import { AudioRecorderModule } from './createRecorderService.native';
7
9
  import type { PlayerServiceInterface, Unsubscribe } from './types';
8
10
 
11
+ export type AudioPlayerModule = typeof LegacyModule | typeof NitroSoundOrLegacyV4Module;
12
+
9
13
  type Modules = {
10
- audioRecorderModule: typeof RNAudioRecorder;
14
+ audioRecorderModule: AudioPlayerModule;
11
15
  permissionModule: typeof Permissions;
12
16
  };
13
17
  type PlaybackListener = Parameters<PlayerServiceInterface['addPlaybackListener']>[number];
14
18
  type StateListener = Parameters<PlayerServiceInterface['addStateListener']>[number];
15
- const createNativePlayerService = ({ audioRecorderModule, permissionModule }: Modules): PlayerServiceInterface => {
16
- const module = new audioRecorderModule.default();
17
19
 
18
- class VoicePlayer implements PlayerServiceInterface {
19
- uri?: string;
20
- state: PlayerServiceInterface['state'] = 'idle';
20
+ interface PlayBackData {
21
+ currentPosition: number;
22
+ duration: number;
23
+ }
24
+
25
+ interface PlayerModuleAdapter {
26
+ setSubscriptionDuration(duration: number): Promise<void> | void;
27
+ addPlayBackListener(callback: (data: PlayBackData) => void): void;
28
+ removePlayBackListener(): void;
29
+ startPlayer(uri: string): Promise<void>;
30
+ pausePlayer(): Promise<void>;
31
+ resumePlayer(): Promise<void>;
32
+ stopPlayer(): Promise<void>;
33
+ seekToPlayer(time: number): Promise<void>;
34
+ }
35
+
36
+ class AudioRecorderPlayerAdapter implements PlayerModuleAdapter {
37
+ private module: InstanceType<typeof LegacyModule.default>;
38
+
39
+ constructor(audioRecorderModule: typeof LegacyModule) {
40
+ this.module = new audioRecorderModule.default();
41
+ }
42
+
43
+ async setSubscriptionDuration(duration: number): Promise<void> {
44
+ await this.module.setSubscriptionDuration(duration);
45
+ }
46
+
47
+ addPlayBackListener(callback: (data: PlayBackData) => void): void {
48
+ this.module.addPlayBackListener(callback);
49
+ }
50
+
51
+ removePlayBackListener(): void {
52
+ this.module.removePlayBackListener();
53
+ }
54
+
55
+ async startPlayer(uri: string): Promise<void> {
56
+ await this.module.startPlayer(uri);
57
+ }
58
+
59
+ async pausePlayer(): Promise<void> {
60
+ await this.module.pausePlayer();
61
+ }
62
+
63
+ async resumePlayer(): Promise<void> {
64
+ await this.module.resumePlayer();
65
+ }
66
+
67
+ async stopPlayer(): Promise<void> {
68
+ await this.module.stopPlayer();
69
+ }
70
+
71
+ async seekToPlayer(time: number): Promise<void> {
72
+ await this.module.seekToPlayer(time);
73
+ }
74
+ }
75
+
76
+ class NitroSoundOrLegacyV4Adapter implements PlayerModuleAdapter {
77
+ private module;
78
+
79
+ constructor(audioRecorderModule: typeof NitroSoundOrLegacyV4Module) {
80
+ this.module = audioRecorderModule.default;
81
+ }
82
+
83
+ setSubscriptionDuration(duration: number): void {
84
+ try {
85
+ this.module.setSubscriptionDuration(duration);
86
+ } catch (error) {
87
+ Logger.warn('[PlayerService.Native] Failed to set subscription duration', error);
88
+ }
89
+ }
90
+
91
+ addPlayBackListener(callback: (data: PlayBackData) => void): void {
92
+ this.module.addPlayBackListener(callback);
93
+ }
94
+
95
+ removePlayBackListener(): void {
96
+ this.module.removePlayBackListener();
97
+ }
98
+
99
+ async startPlayer(uri: string): Promise<void> {
100
+ await this.module.startPlayer(uri);
101
+ }
102
+
103
+ async pausePlayer(): Promise<void> {
104
+ await this.module.pausePlayer();
105
+ }
106
+
107
+ async resumePlayer(): Promise<void> {
108
+ await this.module.resumePlayer();
109
+ }
110
+
111
+ async stopPlayer(): Promise<void> {
112
+ await this.module.stopPlayer();
113
+ }
114
+
115
+ async seekToPlayer(time: number): Promise<void> {
116
+ await this.module.seekToPlayer(time);
117
+ }
118
+ }
119
+
120
+ class VoicePlayer implements PlayerServiceInterface {
121
+ public uri?: string;
122
+ public state: PlayerServiceInterface['state'] = 'idle';
123
+
124
+ private readonly playbackSubscribers = new Set<PlaybackListener>();
125
+ private readonly stateSubscribers = new Set<StateListener>();
21
126
 
22
- private readonly playbackSubscribers = new Set<PlaybackListener>();
23
- private readonly stateSubscribers = new Set<StateListener>();
127
+ constructor(private readonly adapter: PlayerModuleAdapter, private readonly permissionModule: typeof Permissions) {
128
+ this.initialize();
129
+ }
24
130
 
25
- constructor() {
26
- module.setSubscriptionDuration(0.1).catch((error) => {
131
+ private initialize(): void {
132
+ const setDurationResult = this.adapter.setSubscriptionDuration(0.1);
133
+ if (setDurationResult instanceof Promise) {
134
+ setDurationResult.catch((error) => {
27
135
  Logger.warn('[PlayerService.Native] Failed to set subscription duration', error);
28
136
  });
29
137
  }
138
+ }
30
139
 
31
- private setState = (state: PlayerServiceInterface['state']) => {
32
- this.state = state;
33
- this.stateSubscribers.forEach((callback) => {
34
- callback(state);
35
- });
36
- };
140
+ private setState = (state: PlayerServiceInterface['state']) => {
141
+ this.state = state;
142
+ this.stateSubscribers.forEach((callback) => {
143
+ callback(state);
144
+ });
145
+ };
37
146
 
38
- private setListener = () => {
39
- module.addPlayBackListener(async (data) => {
40
- const stopped = data.currentPosition >= data.duration;
41
-
42
- if (stopped) {
43
- this.stop().catch((error) => {
44
- Logger.warn('[PlayerService.Native] Failed to stop in PlayBackListener', error);
45
- });
46
- }
47
- if (this.state === 'playing') {
48
- this.playbackSubscribers.forEach((callback) => {
49
- callback({ currentTime: data.currentPosition, duration: data.duration, stopped });
50
- });
51
- }
52
- });
53
- };
147
+ private setListener = () => {
148
+ this.adapter.addPlayBackListener(async (data) => {
149
+ const stopped = data.currentPosition >= data.duration;
54
150
 
55
- private removeListener = () => {
56
- module.removePlayBackListener();
57
- };
151
+ if (stopped) {
152
+ this.stop().catch((error) => {
153
+ Logger.warn('[PlayerService.Native] Failed to stop in PlayBackListener', error);
154
+ });
155
+ }
156
+ if (this.state === 'playing') {
157
+ this.playbackSubscribers.forEach((callback) => {
158
+ callback({ currentTime: data.currentPosition, duration: data.duration, stopped });
159
+ });
160
+ }
161
+ });
162
+ };
58
163
 
59
- public requestPermission = async (): Promise<boolean> => {
60
- if (Platform.OS === 'android') {
61
- if (Platform.Version > 32) return true;
164
+ private removeListener = () => {
165
+ this.adapter.removePlayBackListener();
166
+ };
62
167
 
63
- const { READ_EXTERNAL_STORAGE } = permissionModule.PERMISSIONS.ANDROID;
168
+ public requestPermission = async (): Promise<boolean> => {
169
+ if (Platform.OS === 'android') {
170
+ if (Platform.Version > 32) return true;
64
171
 
65
- const status = await permissionModule.check(READ_EXTERNAL_STORAGE);
66
- if (status === 'granted') {
67
- return true;
68
- } else {
69
- const status = await permissionModule.request(READ_EXTERNAL_STORAGE);
70
- return status === 'granted';
71
- }
72
- } else {
172
+ const { READ_EXTERNAL_STORAGE } = this.permissionModule.PERMISSIONS.ANDROID;
173
+
174
+ const status = await this.permissionModule.check(READ_EXTERNAL_STORAGE);
175
+ if (status === 'granted') {
73
176
  return true;
177
+ } else {
178
+ const status = await this.permissionModule.request(READ_EXTERNAL_STORAGE);
179
+ return status === 'granted';
74
180
  }
75
- };
181
+ } else {
182
+ return true;
183
+ }
184
+ };
76
185
 
77
- public addPlaybackListener = (callback: PlaybackListener): Unsubscribe => {
78
- this.playbackSubscribers.add(callback);
79
- return () => {
80
- this.playbackSubscribers.delete(callback);
81
- };
186
+ public addPlaybackListener = (callback: PlaybackListener): Unsubscribe => {
187
+ this.playbackSubscribers.add(callback);
188
+ return () => {
189
+ this.playbackSubscribers.delete(callback);
82
190
  };
191
+ };
83
192
 
84
- public addStateListener = (callback: (state: PlayerServiceInterface['state']) => void): Unsubscribe => {
85
- this.stateSubscribers.add(callback);
86
- return () => {
87
- this.stateSubscribers.delete(callback);
88
- };
193
+ public addStateListener = (callback: (state: PlayerServiceInterface['state']) => void): Unsubscribe => {
194
+ this.stateSubscribers.add(callback);
195
+ return () => {
196
+ this.stateSubscribers.delete(callback);
89
197
  };
198
+ };
90
199
 
91
- public play = async (uri: string): Promise<void> => {
92
- if (matchesOneOf(this.state, ['idle', 'stopped'])) {
93
- try {
94
- this.setState('preparing');
95
- this.uri = uri;
96
- this.setListener();
97
-
98
- // FIXME: Workaround, `module.startPlayer()` caused a significant frame-drop and prevented the 'preparing' UI transition.
99
- await sleep(0);
100
- await module.startPlayer(uri);
101
-
102
- this.setState('playing');
103
- } catch (e) {
104
- this.setState('idle');
105
- this.uri = undefined;
106
- this.removeListener();
107
- throw e;
108
- }
109
- } else if (matchesOneOf(this.state, ['paused']) && this.uri === uri) {
110
- try {
111
- this.setListener();
112
- await module.resumePlayer();
113
- this.setState('playing');
114
- } catch (e) {
115
- this.removeListener();
116
- throw e;
117
- }
118
- }
119
- };
200
+ public play = async (uri: string): Promise<void> => {
201
+ if (matchesOneOf(this.state, ['idle', 'stopped'])) {
202
+ try {
203
+ this.setState('preparing');
204
+ this.uri = uri;
205
+ this.setListener();
206
+
207
+ // FIXME: Workaround, `module.startPlayer()` caused a significant frame-drop and prevented the 'preparing' UI transition.
208
+ await sleep(0);
209
+ await this.adapter.startPlayer(uri);
120
210
 
121
- public pause = async (): Promise<void> => {
122
- if (matchesOneOf(this.state, ['playing'])) {
123
- await module.pausePlayer();
211
+ this.setState('playing');
212
+ } catch (e) {
213
+ this.setState('idle');
214
+ this.uri = undefined;
124
215
  this.removeListener();
125
- this.setState('paused');
216
+ throw e;
126
217
  }
127
- };
128
-
129
- public stop = async (): Promise<void> => {
130
- if (matchesOneOf(this.state, ['preparing', 'playing', 'paused'])) {
131
- await module.stopPlayer();
218
+ } else if (matchesOneOf(this.state, ['paused']) && this.uri === uri) {
219
+ try {
220
+ this.setListener();
221
+ await this.adapter.resumePlayer();
222
+ this.setState('playing');
223
+ } catch (e) {
132
224
  this.removeListener();
133
- this.setState('stopped');
225
+ throw e;
134
226
  }
135
- };
227
+ }
228
+ };
136
229
 
137
- public reset = async (): Promise<void> => {
138
- await this.stop();
139
- this.setState('idle');
140
- this.uri = undefined;
141
- this.playbackSubscribers.clear();
142
- this.stateSubscribers.clear();
143
- };
230
+ public pause = async (): Promise<void> => {
231
+ if (matchesOneOf(this.state, ['playing'])) {
232
+ await this.adapter.pausePlayer();
233
+ this.removeListener();
234
+ this.setState('paused');
235
+ }
236
+ };
144
237
 
145
- public seek = async (time: number): Promise<void> => {
146
- if (matchesOneOf(this.state, ['playing', 'paused'])) {
147
- await module.seekToPlayer(time);
148
- }
149
- };
150
- }
238
+ public stop = async (): Promise<void> => {
239
+ if (matchesOneOf(this.state, ['preparing', 'playing', 'paused'])) {
240
+ await this.adapter.stopPlayer();
241
+ this.removeListener();
242
+ this.setState('stopped');
243
+ }
244
+ };
151
245
 
152
- return new VoicePlayer();
246
+ public reset = async (): Promise<void> => {
247
+ await this.stop();
248
+ this.setState('idle');
249
+ this.uri = undefined;
250
+ this.playbackSubscribers.clear();
251
+ this.stateSubscribers.clear();
252
+ };
253
+
254
+ public seek = async (time: number): Promise<void> => {
255
+ if (matchesOneOf(this.state, ['playing', 'paused'])) {
256
+ await this.adapter.seekToPlayer(time);
257
+ }
258
+ };
259
+ }
260
+
261
+ const createNativePlayerService = (modules: Modules): PlayerServiceInterface => {
262
+ const adapter = isNitroSoundOrLegacyV4Module(modules.audioRecorderModule)
263
+ ? new NitroSoundOrLegacyV4Adapter(modules.audioRecorderModule)
264
+ : new AudioRecorderPlayerAdapter(modules.audioRecorderModule as typeof LegacyModule);
265
+
266
+ return new VoicePlayer(adapter, modules.permissionModule);
153
267
  };
154
268
 
269
+ function isNitroSoundOrLegacyV4Module(module: AudioRecorderModule): module is typeof NitroSoundOrLegacyV4Module {
270
+ const isNitroSound = 'createSound' in module && typeof module.createSound === 'function';
271
+ const isLegacyV4 =
272
+ 'default' in module && 'getHybridObject' in module.default && typeof module.default.getHybridObject === 'function';
273
+ if (isLegacyV4) {
274
+ Logger.warn('react-native-audio-recorder-player is deprecated. Please use react-native-nitro-sound instead.');
275
+ }
276
+ return isNitroSound || isLegacyV4;
277
+ }
278
+
155
279
  export default createNativePlayerService;