@sendbird/uikit-react-native 3.10.2 → 3.11.0
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 +2 -2
- 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/createPlayerService.native.js +191 -114
- package/lib/commonjs/platform/createPlayerService.native.js.map +1 -1
- package/lib/commonjs/platform/createRecorderService.expo.js +3 -0
- 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/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/createPlayerService.native.js +191 -114
- package/lib/module/platform/createPlayerService.native.js.map +1 -1
- package/lib/module/platform/createRecorderService.expo.js +3 -0
- 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/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/createPlayerService.native.d.ts +5 -3
- 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/version.d.ts +1 -1
- package/package.json +18 -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/createPlayerService.native.tsx +237 -113
- package/src/platform/createRecorderService.expo.tsx +4 -0
- package/src/platform/createRecorderService.native.tsx +240 -118
- package/src/platform/types.ts +5 -0
- package/src/version.ts +1 -1
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
165
|
-
|
|
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();
|
|
@@ -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;
|
|
@@ -152,6 +152,10 @@ const createExpoRecorderService = ({ avModule }: Modules): RecorderServiceInterf
|
|
|
152
152
|
this._recorder = new avModule.Audio.Recording();
|
|
153
153
|
this.setState('idle');
|
|
154
154
|
};
|
|
155
|
+
|
|
156
|
+
public convertRecordPath = (uri: string): string => {
|
|
157
|
+
return uri;
|
|
158
|
+
};
|
|
155
159
|
}
|
|
156
160
|
|
|
157
161
|
return new VoiceRecorder();
|