1 |
|
2 |
|
3 | import { NativeModules } from 'react-native';
|
4 |
|
5 | import {
|
6 | _DEFAULT_PROGRESS_UPDATE_INTERVAL_MILLIS,
|
7 | _COMMON_AV_PLAYBACK_METHODS,
|
8 | _getURIAndFullInitialStatusForLoadAsync,
|
9 | _throwErrorIfValuesOutOfBoundsInStatus,
|
10 | _getUnloadedStatus,
|
11 | type PlaybackSource,
|
12 | type PlaybackStatus,
|
13 | type PlaybackStatusToSet,
|
14 | } from './AV';
|
15 |
|
16 | export type RecordingOptions = {
|
17 | android: {
|
18 | extension: string,
|
19 | outputFormat: number,
|
20 | audioEncoder: number,
|
21 | sampleRate?: number,
|
22 | numberOfChannels?: number,
|
23 | bitRate?: number,
|
24 | maxFileSize?: number,
|
25 | },
|
26 | ios: {
|
27 | extension: string,
|
28 | outputFormat?: string | number,
|
29 | audioQuality: number,
|
30 | sampleRate: number,
|
31 | numberOfChannels: number,
|
32 | bitRate: number,
|
33 | bitRateStrategy?: number,
|
34 | bitDepthHint?: number,
|
35 | linearPCMBitDepth?: number,
|
36 | linearPCMIsBigEndian?: boolean,
|
37 | linearPCMIsFloat?: boolean,
|
38 | },
|
39 | };
|
40 |
|
41 | export const RECORDING_OPTION_ANDROID_OUTPUT_FORMAT_DEFAULT = 0;
|
42 | export const RECORDING_OPTION_ANDROID_OUTPUT_FORMAT_THREE_GPP = 1;
|
43 | export const RECORDING_OPTION_ANDROID_OUTPUT_FORMAT_MPEG_4 = 2;
|
44 | export const RECORDING_OPTION_ANDROID_OUTPUT_FORMAT_AMR_NB = 3;
|
45 | export const RECORDING_OPTION_ANDROID_OUTPUT_FORMAT_AMR_WB = 4;
|
46 | export const RECORDING_OPTION_ANDROID_OUTPUT_FORMAT_AAC_ADIF = 5;
|
47 | export const RECORDING_OPTION_ANDROID_OUTPUT_FORMAT_AAC_ADTS = 6;
|
48 | export const RECORDING_OPTION_ANDROID_OUTPUT_FORMAT_RTP_AVP = 7;
|
49 | export const RECORDING_OPTION_ANDROID_OUTPUT_FORMAT_MPEG2TS = 8;
|
50 | export const RECORDING_OPTION_ANDROID_OUTPUT_FORMAT_WEBM = 9;
|
51 |
|
52 | export const RECORDING_OPTION_ANDROID_AUDIO_ENCODER_DEFAULT = 0;
|
53 | export const RECORDING_OPTION_ANDROID_AUDIO_ENCODER_AMR_NB = 1;
|
54 | export const RECORDING_OPTION_ANDROID_AUDIO_ENCODER_AMR_WB = 2;
|
55 | export const RECORDING_OPTION_ANDROID_AUDIO_ENCODER_AAC = 3;
|
56 | export const RECORDING_OPTION_ANDROID_AUDIO_ENCODER_HE_AAC = 4;
|
57 | export const RECORDING_OPTION_ANDROID_AUDIO_ENCODER_AAC_ELD = 5;
|
58 | export const RECORDING_OPTION_ANDROID_AUDIO_ENCODER_VORBIS = 6;
|
59 |
|
60 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_LINEARPCM = 'lpcm';
|
61 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_AC3 = 'ac-3';
|
62 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_60958AC3 = 'cac3';
|
63 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_APPLEIMA4 = 'ima4';
|
64 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_MPEG4AAC = 'aac ';
|
65 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_MPEG4CELP = 'celp';
|
66 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_MPEG4HVXC = 'hvxc';
|
67 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_MPEG4TWINVQ = 'twvq';
|
68 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_MACE3 = 'MAC3';
|
69 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_MACE6 = 'MAC6';
|
70 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_ULAW = 'ulaw';
|
71 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_ALAW = 'alaw';
|
72 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_QDESIGN = 'QDMC';
|
73 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_QDESIGN2 = 'QDM2';
|
74 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_QUALCOMM = 'Qclp';
|
75 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_MPEGLAYER1 = '.mp1';
|
76 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_MPEGLAYER2 = '.mp2';
|
77 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_MPEGLAYER3 = '.mp3';
|
78 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_APPLELOSSLESS = 'alac';
|
79 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_MPEG4AAC_HE = 'aach';
|
80 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_MPEG4AAC_LD = 'aacl';
|
81 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_MPEG4AAC_ELD = 'aace';
|
82 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_MPEG4AAC_ELD_SBR = 'aacf';
|
83 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_MPEG4AAC_ELD_V2 = 'aacg';
|
84 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_MPEG4AAC_HE_V2 = 'aacp';
|
85 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_MPEG4AAC_SPATIAL = 'aacs';
|
86 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_AMR = 'samr';
|
87 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_AMR_WB = 'sawb';
|
88 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_AUDIBLE = 'AUDB';
|
89 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_ILBC = 'ilbc';
|
90 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_DVIINTELIMA = 0x6d730011;
|
91 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_MICROSOFTGSM = 0x6d730031;
|
92 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_AES3 = 'aes3';
|
93 | export const RECORDING_OPTION_IOS_OUTPUT_FORMAT_ENHANCEDAC3 = 'ec-3';
|
94 |
|
95 | export const RECORDING_OPTION_IOS_AUDIO_QUALITY_MIN = 0;
|
96 | export const RECORDING_OPTION_IOS_AUDIO_QUALITY_LOW = 0x20;
|
97 | export const RECORDING_OPTION_IOS_AUDIO_QUALITY_MEDIUM = 0x40;
|
98 | export const RECORDING_OPTION_IOS_AUDIO_QUALITY_HIGH = 0x60;
|
99 | export const RECORDING_OPTION_IOS_AUDIO_QUALITY_MAX = 0x7f;
|
100 |
|
101 | export const RECORDING_OPTION_IOS_BIT_RATE_STRATEGY_CONSTANT = 0;
|
102 | export const RECORDING_OPTION_IOS_BIT_RATE_STRATEGY_LONG_TERM_AVERAGE = 1;
|
103 | export const RECORDING_OPTION_IOS_BIT_RATE_STRATEGY_VARIABLE_CONSTRAINED = 2;
|
104 | export const RECORDING_OPTION_IOS_BIT_RATE_STRATEGY_VARIABLE = 3;
|
105 |
|
106 |
|
107 |
|
108 | export const RECORDING_OPTIONS_PRESET_HIGH_QUALITY: RecordingOptions = {
|
109 | android: {
|
110 | extension: '.m4a',
|
111 | outputFormat: RECORDING_OPTION_ANDROID_OUTPUT_FORMAT_MPEG_4,
|
112 | audioEncoder: RECORDING_OPTION_ANDROID_AUDIO_ENCODER_AAC,
|
113 | sampleRate: 44100,
|
114 | numberOfChannels: 2,
|
115 | bitRate: 128000,
|
116 | },
|
117 | ios: {
|
118 | extension: '.caf',
|
119 | audioQuality: RECORDING_OPTION_IOS_AUDIO_QUALITY_MAX,
|
120 | sampleRate: 44100,
|
121 | numberOfChannels: 2,
|
122 | bitRate: 128000,
|
123 | linearPCMBitDepth: 16,
|
124 | linearPCMIsBigEndian: false,
|
125 | linearPCMIsFloat: false,
|
126 | },
|
127 | };
|
128 |
|
129 | export const RECORDING_OPTIONS_PRESET_LOW_QUALITY: RecordingOptions = {
|
130 | android: {
|
131 | extension: '.3gp',
|
132 | outputFormat: RECORDING_OPTION_ANDROID_OUTPUT_FORMAT_THREE_GPP,
|
133 | audioEncoder: RECORDING_OPTION_ANDROID_AUDIO_ENCODER_AMR_NB,
|
134 | sampleRate: 44100,
|
135 | numberOfChannels: 2,
|
136 | bitRate: 128000,
|
137 | },
|
138 | ios: {
|
139 | extension: '.caf',
|
140 | audioQuality: RECORDING_OPTION_IOS_AUDIO_QUALITY_MIN,
|
141 | sampleRate: 44100,
|
142 | numberOfChannels: 2,
|
143 | bitRate: 128000,
|
144 | linearPCMBitDepth: 16,
|
145 | linearPCMIsBigEndian: false,
|
146 | linearPCMIsFloat: false,
|
147 | },
|
148 | };
|
149 |
|
150 |
|
151 | export type RecordingStatus =
|
152 | | {
|
153 | canRecord: false,
|
154 | isDoneRecording: false,
|
155 | }
|
156 | | {
|
157 | canRecord: true,
|
158 | isRecording: boolean,
|
159 | durationMillis: number,
|
160 | }
|
161 | | {
|
162 | canRecord: false,
|
163 | isDoneRecording: true,
|
164 | durationMillis: number,
|
165 | };
|
166 |
|
167 | export type AudioMode = {
|
168 | allowsRecordingIOS: boolean,
|
169 | interruptionModeIOS: number,
|
170 | playsInSilentModeIOS: boolean,
|
171 | interruptionModeAndroid: boolean,
|
172 | shouldDuckAndroid: boolean,
|
173 | };
|
174 |
|
175 | export const INTERRUPTION_MODE_IOS_MIX_WITH_OTHERS = 0;
|
176 | export const INTERRUPTION_MODE_IOS_DO_NOT_MIX = 1;
|
177 | export const INTERRUPTION_MODE_IOS_DUCK_OTHERS = 2;
|
178 |
|
179 | export const INTERRUPTION_MODE_ANDROID_DO_NOT_MIX = 1;
|
180 | export const INTERRUPTION_MODE_ANDROID_DUCK_OTHERS = 2;
|
181 |
|
182 | let _enabled: boolean = true;
|
183 | let _recorderExists: boolean = false;
|
184 | const _DISABLED_ERROR: Error = new Error(
|
185 | 'Cannot complete operation because audio is not enabled.'
|
186 | );
|
187 |
|
188 |
|
189 | const _isValueValid = (value: any, validValues: Array<any>): boolean => {
|
190 | return validValues.filter(validValue => validValue === value).length > 0;
|
191 | };
|
192 |
|
193 |
|
194 | const _findMissingKeys = (
|
195 | object: Object,
|
196 | requiredKeys: Array<any>
|
197 | ): Array<any> => {
|
198 | return requiredKeys.filter(requiredKey => !(requiredKey in object));
|
199 | };
|
200 |
|
201 | export async function setIsEnabledAsync(value: boolean): Promise<void> {
|
202 | _enabled = value;
|
203 | await NativeModules.ExponentAV.setAudioIsEnabled(value);
|
204 |
|
205 |
|
206 |
|
207 |
|
208 | }
|
209 |
|
210 | export async function setAudioModeAsync(mode: AudioMode): Promise<void> {
|
211 | const missingKeys = _findMissingKeys(mode, [
|
212 | 'allowsRecordingIOS',
|
213 | 'interruptionModeIOS',
|
214 | 'playsInSilentModeIOS',
|
215 | 'interruptionModeAndroid',
|
216 | 'shouldDuckAndroid',
|
217 | ]);
|
218 | if (missingKeys.length > 0) {
|
219 | throw new Error(
|
220 | `Audio mode attempted to be set without the required keys: ${JSON.stringify(
|
221 | missingKeys
|
222 | )}`
|
223 | );
|
224 | }
|
225 | if (
|
226 | !_isValueValid(mode.interruptionModeIOS, [
|
227 | INTERRUPTION_MODE_IOS_MIX_WITH_OTHERS,
|
228 | INTERRUPTION_MODE_IOS_DO_NOT_MIX,
|
229 | INTERRUPTION_MODE_IOS_DUCK_OTHERS,
|
230 | ])
|
231 | ) {
|
232 | throw new Error(`"interruptionModeIOS" was set to an invalid value.`);
|
233 | }
|
234 | if (
|
235 | !_isValueValid(mode.interruptionModeAndroid, [
|
236 | INTERRUPTION_MODE_ANDROID_DO_NOT_MIX,
|
237 | INTERRUPTION_MODE_ANDROID_DUCK_OTHERS,
|
238 | ])
|
239 | ) {
|
240 | throw new Error(`"interruptionModeAndroid" was set to an invalid value.`);
|
241 | }
|
242 | if (
|
243 | typeof mode.allowsRecordingIOS !== 'boolean' ||
|
244 | typeof mode.playsInSilentModeIOS !== 'boolean' ||
|
245 | typeof mode.shouldDuckAndroid !== 'boolean'
|
246 | ) {
|
247 | throw new Error(
|
248 | '"allowsRecordingIOS", "playsInSilentModeIOS", and "shouldDuckAndroid" must be booleans.'
|
249 | );
|
250 | }
|
251 | await NativeModules.ExponentAV.setAudioMode(mode);
|
252 | }
|
253 |
|
254 | export class Sound {
|
255 | _loaded: boolean;
|
256 | _loading: boolean;
|
257 | _key: number;
|
258 | _onPlaybackStatusUpdate: ?(status: PlaybackStatus) => void;
|
259 |
|
260 | constructor() {
|
261 | this._loaded = false;
|
262 | this._loading = false;
|
263 | this._key = -1;
|
264 | this._onPlaybackStatusUpdate = null;
|
265 | }
|
266 |
|
267 | static create = async (
|
268 | source: PlaybackSource,
|
269 | initialStatus: PlaybackStatusToSet = {},
|
270 | onPlaybackStatusUpdate: ?(status: PlaybackStatus) => void = null,
|
271 | downloadFirst: boolean = true
|
272 | ): Promise<{ sound: Sound, status: PlaybackStatus }> => {
|
273 | const sound: Sound = new Sound();
|
274 | sound.setOnPlaybackStatusUpdate(onPlaybackStatusUpdate);
|
275 | const status: PlaybackStatus = await sound.loadAsync(
|
276 | source,
|
277 | initialStatus,
|
278 | downloadFirst
|
279 | );
|
280 | return { sound, status };
|
281 | };
|
282 |
|
283 |
|
284 |
|
285 | _callOnPlaybackStatusUpdateForNewStatus(status: PlaybackStatus) {
|
286 | if (this._onPlaybackStatusUpdate != null) {
|
287 | this._onPlaybackStatusUpdate(status);
|
288 | }
|
289 | }
|
290 |
|
291 | async _performOperationAndHandleStatusAsync(
|
292 | operation: () => Promise<PlaybackStatus>
|
293 | ): Promise<PlaybackStatus> {
|
294 | if (!_enabled) {
|
295 | throw _DISABLED_ERROR;
|
296 | }
|
297 | if (this._loaded) {
|
298 | const status = await operation();
|
299 | this._callOnPlaybackStatusUpdateForNewStatus(status);
|
300 | return status;
|
301 | } else {
|
302 | throw new Error('Cannot complete operation because sound is not loaded.');
|
303 | }
|
304 | }
|
305 |
|
306 | _internalStatusUpdateCallback = (status: PlaybackStatus) => {
|
307 | this._callOnPlaybackStatusUpdateForNewStatus(status);
|
308 | this._setInternalStatusUpdateCallback();
|
309 | };
|
310 |
|
311 |
|
312 | _setInternalStatusUpdateCallback() {
|
313 | if (this._loaded) {
|
314 | NativeModules.ExponentAV.setStatusUpdateCallbackForSound(
|
315 | this._key,
|
316 | this._internalStatusUpdateCallback
|
317 | );
|
318 | }
|
319 | }
|
320 |
|
321 | _errorCallback = (error: string) => {
|
322 | this._loaded = false;
|
323 | this._key = -1;
|
324 | this._callOnPlaybackStatusUpdateForNewStatus(_getUnloadedStatus(error));
|
325 | };
|
326 |
|
327 |
|
328 |
|
329 |
|
330 |
|
331 |
|
332 | getStatusAsync = async (): Promise<PlaybackStatus> => {
|
333 | if (this._loaded) {
|
334 | return this._performOperationAndHandleStatusAsync(() =>
|
335 | NativeModules.ExponentAV.getStatusForSound(this._key)
|
336 | );
|
337 | }
|
338 | const status: PlaybackStatus = _getUnloadedStatus();
|
339 | this._callOnPlaybackStatusUpdateForNewStatus(status);
|
340 | return status;
|
341 | };
|
342 |
|
343 | setOnPlaybackStatusUpdate(
|
344 | onPlaybackStatusUpdate: ?(status: PlaybackStatus) => void
|
345 | ) {
|
346 | this._onPlaybackStatusUpdate = onPlaybackStatusUpdate;
|
347 | this.getStatusAsync();
|
348 | }
|
349 |
|
350 |
|
351 | setCallback = (callback: ?(status: PlaybackStatus) => void) => {
|
352 | console.warn(
|
353 | `'Sound.setCallback()' is deprecated and will be removed in SDK21. Use 'Sound.setOnPlaybackStatusUpdate()' instead.`
|
354 | );
|
355 | this.setOnPlaybackStatusUpdate(callback);
|
356 | };
|
357 |
|
358 |
|
359 |
|
360 | async loadAsync(
|
361 | source: PlaybackSource,
|
362 | initialStatus: PlaybackStatusToSet = {},
|
363 | downloadFirst: boolean = true
|
364 | ): Promise<PlaybackStatus> {
|
365 | if (!_enabled) {
|
366 | throw _DISABLED_ERROR;
|
367 | }
|
368 | if (this.loading) {
|
369 | throw new Error('The Sound is already loading.');
|
370 | }
|
371 | if (!this._loaded) {
|
372 | this._loading = true;
|
373 |
|
374 | const {
|
375 | uri,
|
376 | fullInitialStatus,
|
377 | } = await _getURIAndFullInitialStatusForLoadAsync(
|
378 | source,
|
379 | initialStatus,
|
380 | downloadFirst
|
381 | );
|
382 |
|
383 |
|
384 | return new Promise(
|
385 | function(resolve, reject) {
|
386 | const loadSuccess = (key: number, status: PlaybackStatus) => {
|
387 | this._key = key;
|
388 | this._loaded = true;
|
389 | this._loading = false;
|
390 | NativeModules.ExponentAV.setErrorCallbackForSound(
|
391 | this._key,
|
392 | this._errorCallback
|
393 | );
|
394 | this._setInternalStatusUpdateCallback();
|
395 | this._callOnPlaybackStatusUpdateForNewStatus(status);
|
396 | resolve(status);
|
397 | };
|
398 | const loadError = (error: string) => {
|
399 | this._loading = false;
|
400 | reject(new Error(error));
|
401 | };
|
402 | NativeModules.ExponentAV.loadForSound(
|
403 | uri,
|
404 | fullInitialStatus,
|
405 | loadSuccess,
|
406 | loadError
|
407 | );
|
408 | }.bind(this)
|
409 | );
|
410 | } else {
|
411 | throw new Error('The Sound is already loaded.');
|
412 | }
|
413 | }
|
414 |
|
415 | async unloadAsync(): Promise<PlaybackStatus> {
|
416 | if (this._loaded) {
|
417 | this._loaded = false;
|
418 | const key = this._key;
|
419 | this._key = -1;
|
420 | const status = await NativeModules.ExponentAV.unloadForSound(key);
|
421 | this._callOnPlaybackStatusUpdateForNewStatus(status);
|
422 | return status;
|
423 | } else {
|
424 | return this.getStatusAsync();
|
425 | }
|
426 | }
|
427 |
|
428 |
|
429 |
|
430 | async setStatusAsync(status: PlaybackStatusToSet): Promise<PlaybackStatus> {
|
431 | _throwErrorIfValuesOutOfBoundsInStatus(status);
|
432 | return this._performOperationAndHandleStatusAsync(() =>
|
433 | NativeModules.ExponentAV.setStatusForSound(this._key, status)
|
434 | );
|
435 | }
|
436 |
|
437 |
|
438 | playAsync: () => Promise<PlaybackStatus>;
|
439 | playFromPositionAsync: (positionMillis: number) => Promise<PlaybackStatus>;
|
440 | pauseAsync: () => Promise<PlaybackStatus>;
|
441 | stopAsync: () => Promise<PlaybackStatus>;
|
442 | setPositionAsync: (positionMillis: number) => Promise<PlaybackStatus>;
|
443 | setRateAsync: (
|
444 | rate: number,
|
445 | shouldCorrectPitch: boolean
|
446 | ) => Promise<PlaybackStatus>;
|
447 | setVolumeAsync: (volume: number) => Promise<PlaybackStatus>;
|
448 | setIsMutedAsync: (isMuted: boolean) => Promise<PlaybackStatus>;
|
449 | setIsLoopingAsync: (isLooping: boolean) => Promise<PlaybackStatus>;
|
450 | setProgressUpdateIntervalAsync: (
|
451 | progressUpdateIntervalMillis: number
|
452 | ) => Promise<PlaybackStatus>;
|
453 | }
|
454 |
|
455 | Object.assign(Sound.prototype, _COMMON_AV_PLAYBACK_METHODS);
|
456 |
|
457 | export class Recording {
|
458 | _canRecord: boolean;
|
459 | _isDoneRecording: boolean;
|
460 | _finalDurationMillis: number;
|
461 | _uri: ?string;
|
462 | _onRecordingStatusUpdate: ?(status: RecordingStatus) => void;
|
463 | _progressUpdateTimeoutVariable: ?number;
|
464 | _progressUpdateIntervalMillis: number;
|
465 | _options: ?RecordingOptions;
|
466 |
|
467 | constructor() {
|
468 | this._canRecord = false;
|
469 | this._isDoneRecording = false;
|
470 | this._finalDurationMillis = 0;
|
471 | this._uri = null;
|
472 | this._progressUpdateTimeoutVariable = null;
|
473 | this._progressUpdateIntervalMillis = _DEFAULT_PROGRESS_UPDATE_INTERVAL_MILLIS;
|
474 | this._options = null;
|
475 | }
|
476 |
|
477 |
|
478 |
|
479 | _cleanupForUnloadedRecorder = async (finalStatus: RecordingStatus) => {
|
480 | this._canRecord = false;
|
481 | this._isDoneRecording = true;
|
482 |
|
483 | this._finalDurationMillis = finalStatus.durationMillis;
|
484 | _recorderExists = false;
|
485 | if (NativeModules.ExponentAV.setUnloadedCallbackForAndroidRecording) {
|
486 | NativeModules.ExponentAV.setUnloadedCallbackForAndroidRecording(null);
|
487 | }
|
488 | this._disablePolling();
|
489 | return await this.getStatusAsync();
|
490 | };
|
491 |
|
492 | _pollingLoop = async () => {
|
493 | if (_enabled && this._canRecord && this._onRecordingStatusUpdate != null) {
|
494 | this._progressUpdateTimeoutVariable = setTimeout(
|
495 | this._pollingLoop,
|
496 | this._progressUpdateIntervalMillis
|
497 | );
|
498 | try {
|
499 | await this.getStatusAsync();
|
500 | } catch (error) {
|
501 | this._disablePolling();
|
502 | }
|
503 | }
|
504 | };
|
505 |
|
506 | _disablePolling() {
|
507 | if (this._progressUpdateTimeoutVariable != null) {
|
508 | clearTimeout(this._progressUpdateTimeoutVariable);
|
509 | this._progressUpdateTimeoutVariable = null;
|
510 | }
|
511 | }
|
512 |
|
513 | _enablePollingIfNecessaryAndPossible() {
|
514 | if (_enabled && this._canRecord && this._onRecordingStatusUpdate != null) {
|
515 | this._disablePolling();
|
516 | this._pollingLoop();
|
517 | }
|
518 | }
|
519 |
|
520 | _callOnRecordingStatusUpdateForNewStatus(status: RecordingStatus) {
|
521 | if (this._onRecordingStatusUpdate != null) {
|
522 | this._onRecordingStatusUpdate(status);
|
523 | }
|
524 | }
|
525 |
|
526 | async _performOperationAndHandleStatusAsync(
|
527 | operation: () => Promise<RecordingStatus>
|
528 | ): Promise<RecordingStatus> {
|
529 | if (!_enabled) {
|
530 | throw _DISABLED_ERROR;
|
531 | }
|
532 | if (this._canRecord) {
|
533 | const status = await operation();
|
534 | this._callOnRecordingStatusUpdateForNewStatus(status);
|
535 | return status;
|
536 | } else {
|
537 | throw new Error(
|
538 | 'Cannot complete operation because this recorder is not ready to record.'
|
539 | );
|
540 | }
|
541 | }
|
542 |
|
543 |
|
544 |
|
545 |
|
546 |
|
547 | getStatusAsync = async (): Promise<RecordingStatus> => {
|
548 |
|
549 | if (this._canRecord) {
|
550 | return this._performOperationAndHandleStatusAsync(() =>
|
551 | NativeModules.ExponentAV.getAudioRecordingStatus()
|
552 | );
|
553 | }
|
554 | const status: RecordingStatus = this._isDoneRecording
|
555 | ? {
|
556 | canRecord: false,
|
557 | isDoneRecording: true,
|
558 | durationMillis: this._finalDurationMillis,
|
559 | }
|
560 | : {
|
561 | canRecord: false,
|
562 | isDoneRecording: false,
|
563 | };
|
564 | this._callOnRecordingStatusUpdateForNewStatus(status);
|
565 | return status;
|
566 | };
|
567 |
|
568 | setOnRecordingStatusUpdate(
|
569 | onRecordingStatusUpdate: ?(status: RecordingStatus) => void
|
570 | ) {
|
571 | this._onRecordingStatusUpdate = onRecordingStatusUpdate;
|
572 | if (onRecordingStatusUpdate == null) {
|
573 | this._disablePolling();
|
574 | } else {
|
575 | this._enablePollingIfNecessaryAndPossible();
|
576 | }
|
577 | this.getStatusAsync();
|
578 | }
|
579 |
|
580 |
|
581 | setCallback(callback: ?(status: RecordingStatus) => void) {
|
582 |
|
583 | console.warn(
|
584 | `'Recording.setCallback()' is deprecated and will be removed in SDK21. Use 'Recording.setOnRecordingStatusUpdate()' instead.`
|
585 | );
|
586 | this.setOnRecordingStatusUpdate(callback);
|
587 | }
|
588 |
|
589 | setProgressUpdateInterval(progressUpdateIntervalMillis: number) {
|
590 | this._progressUpdateIntervalMillis = progressUpdateIntervalMillis;
|
591 | this.getStatusAsync();
|
592 | }
|
593 |
|
594 |
|
595 |
|
596 | async prepareToRecordAsync(
|
597 | options: RecordingOptions = RECORDING_OPTIONS_PRESET_LOW_QUALITY
|
598 | ): Promise<RecordingStatus> {
|
599 | if (!_enabled) {
|
600 | throw _DISABLED_ERROR;
|
601 | }
|
602 |
|
603 | if (_recorderExists) {
|
604 | throw new Error(
|
605 | 'Only one Recording object can be prepared at a given time.'
|
606 | );
|
607 | }
|
608 |
|
609 | if (this._isDoneRecording) {
|
610 | throw new Error(
|
611 | 'This Recording object is done recording; you must make a new one.'
|
612 | );
|
613 | }
|
614 |
|
615 | if (!options || !options.android || !options.ios) {
|
616 | throw new Error(
|
617 | 'You must provide recording options for android and ios in order to prepare to record.'
|
618 | );
|
619 | }
|
620 |
|
621 | const extensionRegex = /^\.\w+$/;
|
622 | if (
|
623 | !options.android.extension ||
|
624 | !options.ios.extension ||
|
625 | !extensionRegex.test(options.android.extension) ||
|
626 | !extensionRegex.test(options.ios.extension)
|
627 | ) {
|
628 | throw new Error(
|
629 | `Your file extensions must match ${extensionRegex.toString()}.`
|
630 | );
|
631 | }
|
632 |
|
633 | if (!this._canRecord) {
|
634 | if (NativeModules.ExponentAV.setUnloadedCallbackForAndroidRecording) {
|
635 | NativeModules.ExponentAV.setUnloadedCallbackForAndroidRecording(
|
636 | this._cleanupForUnloadedRecorder
|
637 | );
|
638 | }
|
639 |
|
640 | const {
|
641 | uri,
|
642 | status,
|
643 | }: {
|
644 | uri: string,
|
645 | status: Object,
|
646 | } = await NativeModules.ExponentAV.prepareAudioRecorder(options);
|
647 | _recorderExists = true;
|
648 | this._uri = uri;
|
649 | this._options = options;
|
650 | this._canRecord = true;
|
651 | this._callOnRecordingStatusUpdateForNewStatus(status);
|
652 | this._enablePollingIfNecessaryAndPossible();
|
653 | return status;
|
654 | } else {
|
655 | throw new Error('This Recording object is already prepared to record.');
|
656 | }
|
657 | }
|
658 |
|
659 | async startAsync(): Promise<RecordingStatus> {
|
660 | return this._performOperationAndHandleStatusAsync(() =>
|
661 | NativeModules.ExponentAV.startAudioRecording()
|
662 | );
|
663 | }
|
664 |
|
665 | async pauseAsync(): Promise<RecordingStatus> {
|
666 | return this._performOperationAndHandleStatusAsync(() =>
|
667 | NativeModules.ExponentAV.pauseAudioRecording()
|
668 | );
|
669 | }
|
670 |
|
671 | async stopAndUnloadAsync(): Promise<RecordingStatus> {
|
672 | if (!this._canRecord) {
|
673 | if (this._isDoneRecording) {
|
674 | throw new Error(
|
675 | 'Cannot unload a Recording that has already been unloaded.'
|
676 | );
|
677 | } else {
|
678 | throw new Error(
|
679 | 'Cannot unload a Recording that has not been prepared.'
|
680 | );
|
681 | }
|
682 | }
|
683 |
|
684 |
|
685 | const finalStatus: Object = await NativeModules.ExponentAV.stopAudioRecording();
|
686 | await NativeModules.ExponentAV.unloadAudioRecorder();
|
687 | return this._cleanupForUnloadedRecorder(finalStatus);
|
688 | }
|
689 |
|
690 |
|
691 |
|
692 | getURI(): ?string {
|
693 | return this._uri;
|
694 | }
|
695 |
|
696 | async createNewLoadedSound(
|
697 | initialStatus: PlaybackStatusToSet = {},
|
698 | onPlaybackStatusUpdate: ?(status: PlaybackStatus) => void = null
|
699 | ): Promise<{ sound: Sound, status: PlaybackStatus }> {
|
700 | if (this._uri == null || !this._isDoneRecording) {
|
701 | throw new Error(
|
702 | 'Cannot create sound when the Recording has not finished!'
|
703 | );
|
704 | }
|
705 | return Sound.create(
|
706 |
|
707 | { uri: this._uri },
|
708 | initialStatus,
|
709 | onPlaybackStatusUpdate,
|
710 | false
|
711 | );
|
712 | }
|
713 | }
|