diff --git a/docs/pages/versions/unversioned/sdk/audio.md b/docs/pages/versions/unversioned/sdk/audio.md index 09ab71b570a69..6082db4026cc9 100644 --- a/docs/pages/versions/unversioned/sdk/audio.md +++ b/docs/pages/versions/unversioned/sdk/audio.md @@ -338,6 +338,8 @@ try { Stops the recording and deallocates the recorder from memory. This reverts the `Recording` instance to an unprepared state, and another `Recording` instance must be created in order to record again. This method can only be called if the `Recording` has been prepared. + NOTE: On Android this method may fail with `E_AUDIO_NODATA` when called too soon after `startAsync` and no audio data has been recorded yet. In that case the recorded file will be invalid and should be discarded. + #### Returns A `Promise` that is fulfilled when recording has stopped, or rejects if recording could not be stopped. The promise is resolved with the `status` of the recording (see `getStatusAsync()` for details). diff --git a/packages/expo-av/android/src/main/java/expo/modules/av/AVManager.java b/packages/expo-av/android/src/main/java/expo/modules/av/AVManager.java index 4e2494190f1e3..1fe61966b4d1a 100644 --- a/packages/expo-av/android/src/main/java/expo/modules/av/AVManager.java +++ b/packages/expo-av/android/src/main/java/expo/modules/av/AVManager.java @@ -693,7 +693,13 @@ public void stopAudioRecording(final Promise promise) { try { mAudioRecorder.stop(); } catch (final RuntimeException e) { - promise.reject("E_AUDIO_RECORDINGSTOP", "Stop encountered an error: recording not stopped", e); + mAudioRecorderIsPaused = false; + if (!mAudioRecorderIsRecording) { + promise.reject("E_AUDIO_RECORDINGSTOP", "Stop encountered an error: recording not started", e); + } else { + mAudioRecorderIsRecording = false; + promise.reject("E_AUDIO_NODATA", "Stop encountered an error: no valid audio data has been received", e); + } return; } diff --git a/packages/expo-av/src/Audio/Recording.ts b/packages/expo-av/src/Audio/Recording.ts index d5ac56d2c6c46..717cae0d227e8 100644 --- a/packages/expo-av/src/Audio/Recording.ts +++ b/packages/expo-av/src/Audio/Recording.ts @@ -179,11 +179,10 @@ export class Recording { // Internal methods - _cleanupForUnloadedRecorder = async (finalStatus: RecordingStatus) => { + _cleanupForUnloadedRecorder = async (finalStatus?: RecordingStatus) => { this._canRecord = false; this._isDoneRecording = true; - // $FlowFixMe(greg): durationMillis is not always defined - this._finalDurationMillis = finalStatus.durationMillis; + this._finalDurationMillis = finalStatus?.durationMillis || 0; _recorderExists = false; if (this._subscription) { this._subscription.remove(); @@ -354,9 +353,18 @@ export class Recording { } // We perform a separate native API call so that the state of the Recording can be updated with // the final duration of the recording. (We cast stopStatus as Object to appease Flow) - const finalStatus = await ExponentAV.stopAudioRecording(); + let stopResult: RecordingStatus | undefined; + let stopError: Error | undefined; + try { + stopResult = await ExponentAV.stopAudioRecording(); + } catch (err) { + stopError = err; + } + + // Clean-up and return status await ExponentAV.unloadAudioRecorder(); - return this._cleanupForUnloadedRecorder(finalStatus); + const status = await this._cleanupForUnloadedRecorder(stopResult); + return stopError ? Promise.reject(stopError) : status; } // Read API