@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,155 +1,279 @@
|
|
|
1
1
|
import { Platform } from 'react-native';
|
|
2
|
-
import type * as
|
|
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:
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
23
|
-
|
|
127
|
+
constructor(private readonly adapter: PlayerModuleAdapter, private readonly permissionModule: typeof Permissions) {
|
|
128
|
+
this.initialize();
|
|
129
|
+
}
|
|
24
130
|
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
164
|
+
private removeListener = () => {
|
|
165
|
+
this.adapter.removePlayBackListener();
|
|
166
|
+
};
|
|
62
167
|
|
|
63
|
-
|
|
168
|
+
public requestPermission = async (): Promise<boolean> => {
|
|
169
|
+
if (Platform.OS === 'android') {
|
|
170
|
+
if (Platform.Version > 32) return true;
|
|
64
171
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
211
|
+
this.setState('playing');
|
|
212
|
+
} catch (e) {
|
|
213
|
+
this.setState('idle');
|
|
214
|
+
this.uri = undefined;
|
|
124
215
|
this.removeListener();
|
|
125
|
-
|
|
216
|
+
throw e;
|
|
126
217
|
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
225
|
+
throw e;
|
|
134
226
|
}
|
|
135
|
-
}
|
|
227
|
+
}
|
|
228
|
+
};
|
|
136
229
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
this.
|
|
140
|
-
this.
|
|
141
|
-
this.
|
|
142
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
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;
|