Skip to content

Commit

Permalink
If startRecorder fails, the recorder is left in an invalid, unrecover…
Browse files Browse the repository at this point in the history
…able state (#625)

It's common for either `MediaRecorder.start()` or
`MediaRecorder.prepare()` to fail if the device doesn't support the
requested codec, or there is some problem with the arguments.

Currently, if this happens, the recorder is left in an invalid state:

### `index.ts`
If an error occurs, this._isRecording is still set to `true`, which is
incorrect. This PR will catch an error and reset this boolean to `false`


### `RNAudioRecorderPlayerModule.kt`
If an error occurs, `this.mediaRecorder` will be set to partially or
incorrectly initialized object. The next time `startRecord()` is called,
this stale object will be reused, which caused a secondary (and more
confusing) error.

To fix this, `startRecord` will catch any initialization error and reset
`this.mediaRecorder` to null.
  • Loading branch information
jbaudanza authored Sep 3, 2024
1 parent 978dd9c commit da308fd
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -69,30 +69,47 @@ class RNAudioRecorderPlayerModule(private val reactContext: ReactApplicationCont
audioFileURL = if (((path == "DEFAULT"))) "${reactContext.cacheDir}/sound.${defaultFileExtensions.get(outputFormat)}" else path
_meteringEnabled = meteringEnabled

if (mediaRecorder == null) {
mediaRecorder = MediaRecorder()
if (mediaRecorder != null) {
promise.reject("InvalidState", "startRecorder has already been called.")
return
}

if (audioSet != null) {
mediaRecorder!!.setAudioSource(if (audioSet.hasKey("AudioSourceAndroid")) audioSet.getInt("AudioSourceAndroid") else MediaRecorder.AudioSource.MIC)
mediaRecorder!!.setOutputFormat(outputFormat)
mediaRecorder!!.setAudioEncoder(if (audioSet.hasKey("AudioEncoderAndroid")) audioSet.getInt("AudioEncoderAndroid") else MediaRecorder.AudioEncoder.AAC)
mediaRecorder!!.setAudioSamplingRate(if (audioSet.hasKey("AudioSamplingRateAndroid")) audioSet.getInt("AudioSamplingRateAndroid") else 48000)
mediaRecorder!!.setAudioEncodingBitRate(if (audioSet.hasKey("AudioEncodingBitRateAndroid")) audioSet.getInt("AudioEncodingBitRateAndroid") else 128000)
mediaRecorder!!.setAudioChannels(if (audioSet.hasKey("AudioChannelsAndroid")) audioSet.getInt("AudioChannelsAndroid") else 2)
var newMediaRecorder: MediaRecorder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
MediaRecorder(reactContext)
} else {
mediaRecorder!!.setAudioSource(MediaRecorder.AudioSource.MIC)
mediaRecorder!!.setOutputFormat(outputFormat)
mediaRecorder!!.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
mediaRecorder!!.setAudioEncodingBitRate(128000)
mediaRecorder!!.setAudioSamplingRate(48000)
MediaRecorder()
}
mediaRecorder!!.setOutputFile(audioFileURL)

try {
mediaRecorder!!.prepare()
if (audioSet == null) {
newMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC)
newMediaRecorder.setOutputFormat(outputFormat)
newMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
} else {
newMediaRecorder.setAudioSource(if (audioSet.hasKey("AudioSourceAndroid")) audioSet.getInt("AudioSourceAndroid") else MediaRecorder.AudioSource.MIC)
newMediaRecorder.setOutputFormat(outputFormat)
newMediaRecorder.setAudioEncoder(if (audioSet.hasKey("AudioEncoderAndroid")) audioSet.getInt("AudioEncoderAndroid") else MediaRecorder.AudioEncoder.AAC)

if (audioSet.hasKey("AudioSamplingRateAndroid")) {
newMediaRecorder.setAudioSamplingRate(audioSet.getInt("AudioSamplingRateAndroid"))
}

if (audioSet.hasKey("AudioEncodingBitRateAndroid")) {
newMediaRecorder.setAudioEncodingBitRate(audioSet.getInt("AudioEncodingBitRateAndroid"))
}

if (audioSet.hasKey("AudioChannelsAndroid")) {
newMediaRecorder.setAudioChannels(audioSet.getInt("AudioChannelsAndroid"))
}
}
newMediaRecorder.setOutputFile(audioFileURL)

newMediaRecorder.prepare()
totalPausedRecordTime = 0L
mediaRecorder!!.start()
newMediaRecorder.start()

mediaRecorder = newMediaRecorder

val systemTime = SystemClock.elapsedRealtime()
recorderRunnable = object : Runnable {
override fun run() {
Expand All @@ -118,6 +135,9 @@ class RNAudioRecorderPlayerModule(private val reactContext: ReactApplicationCont
(recorderRunnable as Runnable).run()
promise.resolve("file:///$audioFileURL")
} catch (e: Exception) {
newMediaRecorder.release()
mediaRecorder = null

Log.e(tag, "Exception: ", e)
promise.reject("startRecord", e.message)
}
Expand All @@ -126,7 +146,7 @@ class RNAudioRecorderPlayerModule(private val reactContext: ReactApplicationCont
@ReactMethod
fun resumeRecorder(promise: Promise) {
if (mediaRecorder == null) {
promise.reject("resumeReocrder", "Recorder is null.")
promise.reject("resumeRecorder", "Recorder is null.")
return
}

Expand Down Expand Up @@ -172,7 +192,6 @@ class RNAudioRecorderPlayerModule(private val reactContext: ReactApplicationCont

try {
mediaRecorder!!.stop()
mediaRecorder!!.reset()
mediaRecorder!!.release()
mediaRecorder = null
promise.resolve("file:///$audioFileURL")
Expand Down
15 changes: 10 additions & 5 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,11 +248,16 @@ class AudioRecorderPlayer {
if (!this._isRecording) {
this._isRecording = true;

return RNAudioRecorderPlayer.startRecorder(
uri ?? 'DEFAULT',
audioSets,
meteringEnabled ?? false,
);
try {
return await RNAudioRecorderPlayer.startRecorder(
uri ?? 'DEFAULT',
audioSets,
meteringEnabled ?? false,
);
} catch (error: any) {
this._isRecording = false;
throw error;
}
}

return 'Already recording';
Expand Down

0 comments on commit da308fd

Please sign in to comment.