@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.
- package/README.md +9 -7
- package/lib/commonjs/components/ChannelInput/VoiceMessageInput.js +1 -1
- package/lib/commonjs/components/ChannelInput/VoiceMessageInput.js.map +1 -1
- package/lib/commonjs/components/ChannelInput/index.js +2 -25
- package/lib/commonjs/components/ChannelInput/index.js.map +1 -1
- package/lib/commonjs/hooks/useVoiceMessageInput.js +10 -2
- package/lib/commonjs/hooks/useVoiceMessageInput.js.map +1 -1
- package/lib/commonjs/platform/createFileService.native.js +1 -1
- package/lib/commonjs/platform/createFileService.native.js.map +1 -1
- package/lib/commonjs/platform/createMediaService.expo.js +83 -12
- package/lib/commonjs/platform/createMediaService.expo.js.map +1 -1
- package/lib/commonjs/platform/createPlayerService.expo.js +214 -113
- package/lib/commonjs/platform/createPlayerService.expo.js.map +1 -1
- package/lib/commonjs/platform/createPlayerService.native.js +191 -114
- package/lib/commonjs/platform/createPlayerService.native.js.map +1 -1
- package/lib/commonjs/platform/createRecorderService.expo.js +248 -127
- package/lib/commonjs/platform/createRecorderService.expo.js.map +1 -1
- package/lib/commonjs/platform/createRecorderService.native.js +212 -129
- package/lib/commonjs/platform/createRecorderService.native.js.map +1 -1
- package/lib/commonjs/platform/types.js.map +1 -1
- package/lib/commonjs/utils/expoBackwardUtils.js +23 -0
- package/lib/commonjs/utils/expoBackwardUtils.js.map +1 -1
- package/lib/commonjs/utils/expoPermissionGranted.js.map +1 -1
- package/lib/commonjs/version.js +1 -1
- package/lib/commonjs/version.js.map +1 -1
- package/lib/module/components/ChannelInput/VoiceMessageInput.js +1 -1
- package/lib/module/components/ChannelInput/VoiceMessageInput.js.map +1 -1
- package/lib/module/components/ChannelInput/index.js +3 -26
- package/lib/module/components/ChannelInput/index.js.map +1 -1
- package/lib/module/hooks/useVoiceMessageInput.js +10 -2
- package/lib/module/hooks/useVoiceMessageInput.js.map +1 -1
- package/lib/module/platform/createFileService.native.js +1 -1
- package/lib/module/platform/createFileService.native.js.map +1 -1
- package/lib/module/platform/createMediaService.expo.js +82 -13
- package/lib/module/platform/createMediaService.expo.js.map +1 -1
- package/lib/module/platform/createPlayerService.expo.js +214 -113
- package/lib/module/platform/createPlayerService.expo.js.map +1 -1
- package/lib/module/platform/createPlayerService.native.js +191 -114
- package/lib/module/platform/createPlayerService.native.js.map +1 -1
- package/lib/module/platform/createRecorderService.expo.js +249 -128
- package/lib/module/platform/createRecorderService.expo.js.map +1 -1
- package/lib/module/platform/createRecorderService.native.js +212 -129
- package/lib/module/platform/createRecorderService.native.js.map +1 -1
- package/lib/module/platform/types.js.map +1 -1
- package/lib/module/utils/expoBackwardUtils.js +23 -0
- package/lib/module/utils/expoBackwardUtils.js.map +1 -1
- package/lib/module/utils/expoPermissionGranted.js.map +1 -1
- package/lib/module/version.js +1 -1
- package/lib/module/version.js.map +1 -1
- package/lib/typescript/src/containers/SendbirdUIKitContainer.d.ts +1 -1
- package/lib/typescript/src/platform/createMediaService.expo.d.ts +2 -2
- package/lib/typescript/src/platform/createPlayerService.expo.d.ts +2 -2
- package/lib/typescript/src/platform/createPlayerService.native.d.ts +5 -3
- package/lib/typescript/src/platform/createRecorderService.expo.d.ts +2 -2
- package/lib/typescript/src/platform/createRecorderService.native.d.ts +5 -3
- package/lib/typescript/src/platform/types.d.ts +4 -0
- package/lib/typescript/src/utils/expoBackwardUtils.d.ts +10 -0
- package/lib/typescript/src/utils/expoPermissionGranted.d.ts +1 -1
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +29 -5
- package/src/components/ChannelInput/VoiceMessageInput.tsx +1 -1
- package/src/components/ChannelInput/index.tsx +6 -36
- package/src/hooks/useVoiceMessageInput.ts +7 -2
- package/src/platform/createFileService.native.ts +1 -1
- package/src/platform/createMediaService.expo.tsx +87 -9
- package/src/platform/createPlayerService.expo.tsx +242 -109
- package/src/platform/createPlayerService.native.tsx +237 -113
- package/src/platform/createRecorderService.expo.tsx +268 -107
- package/src/platform/createRecorderService.native.tsx +240 -118
- package/src/platform/types.ts +5 -0
- package/src/utils/expoBackwardUtils.ts +29 -0
- package/src/utils/expoPermissionGranted.ts +3 -1
- package/src/version.ts +1 -1
|
@@ -1,148 +1,281 @@
|
|
|
1
|
+
import type * as ExpoAudio from 'expo-audio';
|
|
1
2
|
import type * as ExpoAV from 'expo-av';
|
|
2
3
|
|
|
3
4
|
import { Logger, matchesOneOf } from '@sendbird/uikit-utils';
|
|
4
5
|
|
|
5
|
-
import
|
|
6
|
+
import expoBackwardUtils from '../utils/expoBackwardUtils';
|
|
7
|
+
import type { ExpoAudioModule } from '../utils/expoBackwardUtils';
|
|
6
8
|
import type { PlayerServiceInterface, Unsubscribe } from './types';
|
|
7
9
|
|
|
8
10
|
type Modules = {
|
|
9
|
-
avModule:
|
|
11
|
+
avModule: ExpoAudioModule;
|
|
10
12
|
};
|
|
11
13
|
type PlaybackListener = Parameters<PlayerServiceInterface['addPlaybackListener']>[number];
|
|
12
14
|
type StateListener = Parameters<PlayerServiceInterface['addStateListener']>[number];
|
|
13
|
-
const createExpoPlayerService = ({ avModule }: Modules): PlayerServiceInterface => {
|
|
14
|
-
const sound = new avModule.Audio.Sound();
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
interface AudioPlayerAdapter {
|
|
17
|
+
requestPermission(): Promise<boolean>;
|
|
18
|
+
play(uri: string): Promise<void>;
|
|
19
|
+
pause(): Promise<void>;
|
|
20
|
+
stop(): Promise<void>;
|
|
21
|
+
reset(): Promise<void>;
|
|
22
|
+
seek(time: number): Promise<void>;
|
|
23
|
+
addPlaybackListener(callback: PlaybackListener): Unsubscribe;
|
|
24
|
+
addStateListener(callback: StateListener): Unsubscribe;
|
|
25
|
+
readonly state: PlayerServiceInterface['state'];
|
|
26
|
+
uri?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
abstract class BaseAudioPlayerAdapter implements AudioPlayerAdapter {
|
|
30
|
+
uri?: string;
|
|
31
|
+
state: PlayerServiceInterface['state'] = 'idle';
|
|
32
|
+
|
|
33
|
+
protected readonly playbackSubscribers = new Set<PlaybackListener>();
|
|
34
|
+
protected readonly stateSubscribers = new Set<StateListener>();
|
|
19
35
|
|
|
20
|
-
|
|
21
|
-
|
|
36
|
+
protected setState = (state: PlayerServiceInterface['state']) => {
|
|
37
|
+
this.state = state;
|
|
38
|
+
this.stateSubscribers.forEach((callback) => {
|
|
39
|
+
callback(state);
|
|
40
|
+
});
|
|
41
|
+
};
|
|
22
42
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
43
|
+
public requestPermission = async (): Promise<boolean> => {
|
|
44
|
+
return true;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
public addPlaybackListener = (callback: PlaybackListener): Unsubscribe => {
|
|
48
|
+
this.playbackSubscribers.add(callback);
|
|
49
|
+
return () => {
|
|
50
|
+
this.playbackSubscribers.delete(callback);
|
|
28
51
|
};
|
|
52
|
+
};
|
|
29
53
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
public addStateListener = (callback: StateListener): Unsubscribe => {
|
|
55
|
+
this.stateSubscribers.add(callback);
|
|
56
|
+
return () => {
|
|
57
|
+
this.stateSubscribers.delete(callback);
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
abstract play(uri: string): Promise<void>;
|
|
62
|
+
abstract pause(): Promise<void>;
|
|
63
|
+
abstract stop(): Promise<void>;
|
|
64
|
+
abstract reset(): Promise<void>;
|
|
65
|
+
abstract seek(time: number): Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
class LegacyExpoAVPlayerAdapter extends BaseAudioPlayerAdapter {
|
|
69
|
+
private readonly sound: ExpoAV.Audio.Sound;
|
|
70
|
+
|
|
71
|
+
constructor(avModule: typeof ExpoAV) {
|
|
72
|
+
super();
|
|
73
|
+
this.sound = new avModule.Audio.Sound();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private setListener = () => {
|
|
77
|
+
this.sound.setProgressUpdateIntervalAsync(100).catch((error) => {
|
|
78
|
+
Logger.warn('[PlayerService.Expo] Failed to set progress update interval', error);
|
|
79
|
+
});
|
|
80
|
+
this.sound.setOnPlaybackStatusUpdate((status) => {
|
|
81
|
+
if (status.isLoaded) {
|
|
82
|
+
if (status.didJustFinish) {
|
|
83
|
+
this.stop().catch((error) => {
|
|
84
|
+
Logger.warn('[PlayerService.Expo] Failed to stop in OnPlaybackStatusUpdate', error);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (status.isPlaying) {
|
|
88
|
+
this.playbackSubscribers.forEach((callback) => {
|
|
89
|
+
callback({
|
|
90
|
+
currentTime: status.positionMillis,
|
|
91
|
+
duration: status.durationMillis ?? 0,
|
|
92
|
+
stopped: status.didJustFinish,
|
|
48
93
|
});
|
|
49
|
-
}
|
|
94
|
+
});
|
|
50
95
|
}
|
|
51
|
-
}
|
|
52
|
-
};
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
};
|
|
53
99
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
100
|
+
private removeListener = () => {
|
|
101
|
+
this.sound.setOnPlaybackStatusUpdate(null);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
private prepare = async (uri: string) => {
|
|
105
|
+
this.setState('preparing');
|
|
106
|
+
await this.sound.loadAsync({ uri }, { shouldPlay: false }, true);
|
|
107
|
+
this.uri = uri;
|
|
108
|
+
};
|
|
57
109
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
110
|
+
public play = async (uri: string): Promise<void> => {
|
|
111
|
+
if (matchesOneOf(this.state, ['idle', 'stopped'])) {
|
|
112
|
+
try {
|
|
113
|
+
await this.prepare(uri);
|
|
114
|
+
this.setListener();
|
|
115
|
+
await this.sound.playAsync();
|
|
116
|
+
this.setState('playing');
|
|
117
|
+
} catch (e) {
|
|
118
|
+
this.setState('idle');
|
|
119
|
+
this.uri = undefined;
|
|
120
|
+
this.removeListener();
|
|
121
|
+
throw e;
|
|
65
122
|
}
|
|
66
|
-
}
|
|
123
|
+
} else if (matchesOneOf(this.state, ['paused']) && this.uri === uri) {
|
|
124
|
+
try {
|
|
125
|
+
this.setListener();
|
|
126
|
+
await this.sound.playAsync();
|
|
127
|
+
this.setState('playing');
|
|
128
|
+
} catch (e) {
|
|
129
|
+
this.removeListener();
|
|
130
|
+
throw e;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
67
134
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
135
|
+
public pause = async (): Promise<void> => {
|
|
136
|
+
if (matchesOneOf(this.state, ['playing'])) {
|
|
137
|
+
await this.sound.pauseAsync();
|
|
138
|
+
this.removeListener();
|
|
139
|
+
this.setState('paused');
|
|
140
|
+
}
|
|
141
|
+
};
|
|
74
142
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
143
|
+
public stop = async (): Promise<void> => {
|
|
144
|
+
if (matchesOneOf(this.state, ['playing', 'paused'])) {
|
|
145
|
+
await this.sound.stopAsync();
|
|
146
|
+
await this.sound.unloadAsync();
|
|
147
|
+
this.removeListener();
|
|
148
|
+
this.setState('stopped');
|
|
149
|
+
}
|
|
150
|
+
};
|
|
81
151
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
152
|
+
public reset = async (): Promise<void> => {
|
|
153
|
+
await this.stop();
|
|
154
|
+
this.setState('idle');
|
|
155
|
+
this.uri = undefined;
|
|
156
|
+
this.playbackSubscribers.clear();
|
|
157
|
+
this.stateSubscribers.clear();
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
public seek = async (time: number): Promise<void> => {
|
|
161
|
+
if (matchesOneOf(this.state, ['playing', 'paused'])) {
|
|
162
|
+
await this.sound.playFromPositionAsync(time);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
class ExpoAudioPlayerAdapter extends BaseAudioPlayerAdapter {
|
|
168
|
+
private readonly audioModule: typeof ExpoAudio;
|
|
169
|
+
private player: ExpoAudio.AudioPlayer | null = null;
|
|
170
|
+
|
|
171
|
+
constructor(audioModule: typeof ExpoAudio) {
|
|
172
|
+
super();
|
|
173
|
+
this.audioModule = audioModule;
|
|
174
|
+
}
|
|
87
175
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
this.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
this.uri = undefined;
|
|
98
|
-
this.removeListener();
|
|
99
|
-
throw e;
|
|
176
|
+
private setListener = () => {
|
|
177
|
+
if (!this.player) return;
|
|
178
|
+
|
|
179
|
+
this.player.addListener('playbackStatusUpdate', (status) => {
|
|
180
|
+
if (status.isLoaded) {
|
|
181
|
+
if (status.didJustFinish) {
|
|
182
|
+
this.stop().catch((error) => {
|
|
183
|
+
Logger.warn('[PlayerService.Expo] Failed to stop in playbackStatusUpdate', error);
|
|
184
|
+
});
|
|
100
185
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
186
|
+
if (status.playing) {
|
|
187
|
+
this.playbackSubscribers.forEach((callback) => {
|
|
188
|
+
callback({
|
|
189
|
+
currentTime: status.currentTime,
|
|
190
|
+
duration: status.duration ?? 0,
|
|
191
|
+
stopped: status.didJustFinish,
|
|
192
|
+
});
|
|
193
|
+
});
|
|
109
194
|
}
|
|
110
195
|
}
|
|
111
|
-
};
|
|
196
|
+
});
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
private removeListener = () => {
|
|
200
|
+
if (this.player) {
|
|
201
|
+
this.player.remove();
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
private prepare = async (uri: string) => {
|
|
206
|
+
this.setState('preparing');
|
|
207
|
+
this.player = this.audioModule.createAudioPlayer(uri, { updateInterval: 100 });
|
|
208
|
+
this.uri = uri;
|
|
209
|
+
};
|
|
112
210
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
211
|
+
public play = async (uri: string): Promise<void> => {
|
|
212
|
+
if (matchesOneOf(this.state, ['idle', 'stopped'])) {
|
|
213
|
+
try {
|
|
214
|
+
await this.prepare(uri);
|
|
215
|
+
this.setListener();
|
|
216
|
+
this.player?.play();
|
|
217
|
+
this.setState('playing');
|
|
218
|
+
} catch (e) {
|
|
219
|
+
this.setState('idle');
|
|
220
|
+
this.uri = undefined;
|
|
116
221
|
this.removeListener();
|
|
117
|
-
|
|
222
|
+
throw e;
|
|
118
223
|
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
224
|
+
} else if (matchesOneOf(this.state, ['paused']) && this.uri === uri) {
|
|
225
|
+
try {
|
|
226
|
+
this.setListener();
|
|
227
|
+
this.player?.play();
|
|
228
|
+
this.setState('playing');
|
|
229
|
+
} catch (e) {
|
|
125
230
|
this.removeListener();
|
|
126
|
-
|
|
231
|
+
throw e;
|
|
127
232
|
}
|
|
128
|
-
}
|
|
233
|
+
}
|
|
234
|
+
};
|
|
129
235
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
this.
|
|
133
|
-
this.
|
|
134
|
-
this.
|
|
135
|
-
|
|
136
|
-
|
|
236
|
+
public pause = async (): Promise<void> => {
|
|
237
|
+
if (matchesOneOf(this.state, ['playing'])) {
|
|
238
|
+
this.player?.pause();
|
|
239
|
+
this.removeListener();
|
|
240
|
+
this.setState('paused');
|
|
241
|
+
}
|
|
242
|
+
};
|
|
137
243
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
244
|
+
public stop = async (): Promise<void> => {
|
|
245
|
+
if (matchesOneOf(this.state, ['playing', 'paused'])) {
|
|
246
|
+
this.player?.pause();
|
|
247
|
+
this.removeListener();
|
|
248
|
+
this.setState('stopped');
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
public reset = async (): Promise<void> => {
|
|
253
|
+
await this.stop();
|
|
254
|
+
this.player?.remove();
|
|
255
|
+
this.player = null;
|
|
256
|
+
this.setState('idle');
|
|
257
|
+
this.uri = undefined;
|
|
258
|
+
this.playbackSubscribers.clear();
|
|
259
|
+
this.stateSubscribers.clear();
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
public seek = async (time: number): Promise<void> => {
|
|
263
|
+
if (matchesOneOf(this.state, ['playing', 'paused']) && this.player) {
|
|
264
|
+
this.player.currentTime = time;
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const createExpoPlayerService = ({ avModule }: Modules): PlayerServiceInterface => {
|
|
270
|
+
if (expoBackwardUtils.expoAV.isLegacyAVModule(avModule)) {
|
|
271
|
+
Logger.warn(
|
|
272
|
+
'[PlayerService.Expo] expo-av is deprecated and will be removed in Expo 54. Please migrate to expo-audio.',
|
|
273
|
+
);
|
|
143
274
|
}
|
|
144
275
|
|
|
145
|
-
return
|
|
276
|
+
return expoBackwardUtils.expoAV.isAudioModule(avModule)
|
|
277
|
+
? new ExpoAudioPlayerAdapter(avModule)
|
|
278
|
+
: new LegacyExpoAVPlayerAdapter(avModule);
|
|
146
279
|
};
|
|
147
280
|
|
|
148
281
|
export default createExpoPlayerService;
|