From 5fda76bb15641ba675667db03f67508e07bc5b60 Mon Sep 17 00:00:00 2001 From: Pantelis Stampoulis Date: Thu, 30 May 2024 13:58:59 +0300 Subject: [PATCH] AudioRecorder improvements --- .../android/util/audio/AudioRecorder.kt | 100 ++++++++++++------ 1 file changed, 69 insertions(+), 31 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/util/audio/AudioRecorder.kt b/WordPress/src/main/java/org/wordpress/android/util/audio/AudioRecorder.kt index d80ae2801819..321328d1c3a7 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/audio/AudioRecorder.kt +++ b/WordPress/src/main/java/org/wordpress/android/util/audio/AudioRecorder.kt @@ -4,6 +4,7 @@ import android.Manifest import android.content.Context import android.content.pm.PackageManager import android.media.MediaRecorder +import android.os.Build import android.util.Log import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -53,31 +54,40 @@ class AudioRecorder( private val _isPaused = MutableStateFlow(false) val isPaused: StateFlow = _isPaused - @Suppress("DEPRECATION") override fun startRecording(onRecordingFinished: (String) -> Unit) { this.onRecordingFinished = onRecordingFinished if (applicationContext.checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) { - recorder = MediaRecorder().apply { - setAudioSource(MediaRecorder.AudioSource.MIC) - setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) - setAudioEncoder(MediaRecorder.AudioEncoder.AAC) - setOutputFile(filePath) + try { + recorder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + MediaRecorder(applicationContext) + } else { + MediaRecorder() + }.apply { + setAudioSource(MediaRecorder.AudioSource.MIC) + setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) + setAudioEncoder(MediaRecorder.AudioEncoder.AAC) + setOutputFile(filePath) - try { prepare() start() startRecordingUpdates() _isRecording.value = true _isPaused.value = false - } catch (e: IOException) { - // Use a logging framework like Timber - Log.e("AudioRecorder", "Error starting recording") } + } catch (e: IOException) { + Log.e(TAG, "Error starting recording: ${e.message}") + onRecordingFinished("") + } catch (e: IllegalStateException) { + Log.e(TAG, "Illegal state when starting recording: ${e.message}") + onRecordingFinished("") + } catch (e: SecurityException) { + Log.e(TAG, "Security exception: ${e.message}") + onRecordingFinished("") } } else { - // Handle permission not granted case, e.g., throw an exception or show a message - Log.e("AudioRecorder","Permission to record audio not granted") + Log.e(TAG, "Permission to record audio not granted") + onRecordingFinished("") } } @@ -88,7 +98,7 @@ class AudioRecorder( release() } } catch (e: IllegalStateException) { - Log.e("AudioRecorder", "Error stopping recording") + Log.e(TAG, "Error stopping recording: ${e.message}") } finally { recorder = null stopRecordingUpdates() @@ -100,16 +110,14 @@ class AudioRecorder( } override fun pauseRecording() { - if (recorder != null) { - try { - recorder?.pause() - _isPaused.value = true - stopRecordingUpdates() - } catch (e: IllegalStateException) { - Log.e("AudioRecorder", "Error pausing recording") - } - } else { - Log.e("AudioRecorder","Pause not supported on this device") + try { + recorder?.pause() + _isPaused.value = true + stopRecordingUpdates() + } catch (e: IllegalStateException) { + Log.e(TAG, "Error pausing recording: ${e.message}") + } catch (e: UnsupportedOperationException) { + Log.e(TAG, "Pause not supported on this device: ${e.message}") } } @@ -123,7 +131,7 @@ class AudioRecorder( isPausedRecording = false startRecordingUpdates() } catch (e: IllegalStateException) { - Log.e("AudioRecorder", "Error resuming recording") + Log.e(TAG, "Error resuming recording") } } } @@ -135,33 +143,63 @@ class AudioRecorder( recordingParams = params } + @Suppress("MagicNumber") private fun startRecordingUpdates() { recordingJob = coroutineScope.launch { - var elapsedTime = 0 + var elapsedTimeInSeconds = 0 while (recorder != null) { delay(RECORDING_UPDATE_INTERVAL) - elapsedTime++ + elapsedTimeInSeconds += (RECORDING_UPDATE_INTERVAL / 1000).toInt() val fileSize = File(filePath).length() _recordingUpdates.value = RecordingUpdate( - elapsedTime = elapsedTime, + elapsedTime = elapsedTimeInSeconds, fileSize = fileSize, fileSizeLimitExceeded = fileSize >= recordingParams.maxFileSize, ) - if (fileSize >= recordingParams.maxFileSize - || elapsedTime >= recordingParams.maxDuration) { + if ( maxFileSizeExceeded(fileSize) || maxDurationExceeded(elapsedTimeInSeconds) ) { stopRecording() + break } } } } + /** + * Checks if the recorded file size has exceeded the specified maximum file size. + * + * @param fileSize The current size of the recorded file in bytes. + * @return `true` if the file size has exceeded the maximum file size minus the threshold, `false` otherwise. + * If `recordingParams.maxFileSize` is set to `-1L`, this function always returns `false` indicating + * no limit. + */ + private fun maxFileSizeExceeded(fileSize: Long): Boolean = when { + recordingParams.maxFileSize == -1L -> false + else -> fileSize >= recordingParams.maxFileSize - FILE_SIZE_THRESHOLD + } + + /** + * Checks if the recording duration has exceeded the specified maximum duration. + * + * @param elapsedTimeInSeconds The elapsed recording time in seconds. + * @return `true` if the elapsed time has exceeded the maximum duration minus the threshold, `false` otherwise. + * If `recordingParams.maxDuration` is set to `-1`, this function always returns `false` indicating + * no limit. + */ + private fun maxDurationExceeded(elapsedTimeInSeconds: Int): Boolean = when { + recordingParams.maxDuration == -1 -> false + else -> elapsedTimeInSeconds >= recordingParams.maxDuration - DURATION_THRESHOLD + } + private fun stopRecordingUpdates() { recordingJob?.cancel() } companion object { - private const val RECORDING_UPDATE_INTERVAL = 1000L - private const val RESUME_DELAY = 500L + private const val TAG = "AudioRecorder" + private const val RECORDING_UPDATE_INTERVAL = 1000L // in milliseconds + private const val RESUME_DELAY = 500L // in milliseconds + private const val FILE_SIZE_THRESHOLD = 100000L + private const val DURATION_THRESHOLD = 1 } }