@@ -4,6 +4,7 @@ import android.Manifest
44import android.content.Context
55import android.content.pm.PackageManager
66import android.media.MediaRecorder
7+ import android.os.Build
78import android.util.Log
89import kotlinx.coroutines.CoroutineScope
910import kotlinx.coroutines.Dispatchers
@@ -17,17 +18,15 @@ import kotlinx.coroutines.flow.asStateFlow
1718import kotlinx.coroutines.launch
1819import java.io.File
1920import java.io.IOException
21+ import org.wordpress.android.util.audio.IAudioRecorder.AudioRecorderResult
22+ import org.wordpress.android.util.audio.IAudioRecorder.AudioRecorderResult.Success
23+ import org.wordpress.android.util.audio.IAudioRecorder.AudioRecorderResult.Error
2024
2125class AudioRecorder (
22- private val applicationContext : Context
26+ private val applicationContext : Context ,
27+ private val recordingStrategy : RecordingStrategy
2328) : IAudioRecorder {
24- // default recording params
25- private var recordingParams: RecordingParams = RecordingParams (
26- maxDuration = 60 * 5 , // 5 minutes
27- maxFileSize = 1000000L * 25 // 25MB
28- )
29-
30- private var onRecordingFinished: (String ) -> Unit = {}
29+ private var onRecordingFinished: (AudioRecorderResult ) -> Unit = {}
3130
3231 private val storeInMemory = true
3332 private val filePath by lazy {
@@ -54,30 +53,44 @@ class AudioRecorder(
5453 val isPaused: StateFlow <Boolean > = _isPaused
5554
5655 @Suppress(" DEPRECATION" )
57- override fun startRecording (onRecordingFinished : (String ) -> Unit ) {
56+ override fun startRecording (onRecordingFinished : (AudioRecorderResult ) -> Unit ) {
5857 this .onRecordingFinished = onRecordingFinished
5958 if (applicationContext.checkSelfPermission(Manifest .permission.RECORD_AUDIO )
6059 == PackageManager .PERMISSION_GRANTED ) {
61- recorder = MediaRecorder ().apply {
62- setAudioSource(MediaRecorder .AudioSource .MIC )
63- setOutputFormat(MediaRecorder .OutputFormat .MPEG_4 )
64- setAudioEncoder(MediaRecorder .AudioEncoder .AAC )
65- setOutputFile(filePath)
60+ try {
61+ recorder = if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .S ) {
62+ MediaRecorder (applicationContext)
63+ } else {
64+ MediaRecorder ()
65+ }.apply {
66+ setAudioSource(MediaRecorder .AudioSource .MIC )
67+ setOutputFormat(MediaRecorder .OutputFormat .MPEG_4 )
68+ setAudioEncoder(MediaRecorder .AudioEncoder .AAC )
69+ setOutputFile(filePath)
6670
67- try {
6871 prepare()
6972 start()
7073 startRecordingUpdates()
7174 _isRecording .value = true
7275 _isPaused .value = false
73- } catch (e: IOException ) {
74- // Use a logging framework like Timber
75- Log .e(" AudioRecorder" , " Error starting recording" )
7676 }
77+ } catch (e: IOException ) {
78+ val errorMessage = " Error preparing MediaRecorder: ${e.message} "
79+ Log .e(TAG , errorMessage)
80+ onRecordingFinished(Error (errorMessage))
81+ } catch (e: IllegalStateException ) {
82+ val errorMessage = " Illegal state when starting recording: ${e.message} "
83+ Log .e(TAG , errorMessage)
84+ onRecordingFinished(Error (errorMessage))
85+ } catch (e: SecurityException ) {
86+ val errorMessage = " Security exception when starting recording: ${e.message} "
87+ Log .e(TAG , errorMessage)
88+ onRecordingFinished(Error (errorMessage))
7789 }
7890 } else {
79- // Handle permission not granted case, e.g., throw an exception or show a message
80- Log .e(" AudioRecorder" ," Permission to record audio not granted" )
91+ val errorMessage = " Permission to record audio not granted"
92+ Log .e(TAG , errorMessage)
93+ onRecordingFinished(Error (errorMessage))
8194 }
8295 }
8396
@@ -88,28 +101,26 @@ class AudioRecorder(
88101 release()
89102 }
90103 } catch (e: IllegalStateException ) {
91- Log .e(" AudioRecorder " , " Error stopping recording" )
104+ Log .e(TAG , " Error stopping recording: ${e.message} " )
92105 } finally {
93106 recorder = null
94107 stopRecordingUpdates()
95108 _isPaused .value = false
96109 _isRecording .value = false
97110 }
98111 // return filePath
99- onRecordingFinished(filePath)
112+ onRecordingFinished(Success ( filePath) )
100113 }
101114
102115 override fun pauseRecording () {
103- if (recorder != null ) {
104- try {
105- recorder?.pause()
106- _isPaused .value = true
107- stopRecordingUpdates()
108- } catch (e: IllegalStateException ) {
109- Log .e(" AudioRecorder" , " Error pausing recording" )
110- }
111- } else {
112- Log .e(" AudioRecorder" ," Pause not supported on this device" )
116+ try {
117+ recorder?.pause()
118+ _isPaused .value = true
119+ stopRecordingUpdates()
120+ } catch (e: IllegalStateException ) {
121+ Log .e(TAG , " Error pausing recording: ${e.message} " )
122+ } catch (e: UnsupportedOperationException ) {
123+ Log .e(TAG , " Pause not supported on this device: ${e.message} " )
113124 }
114125 }
115126
@@ -123,45 +134,71 @@ class AudioRecorder(
123134 isPausedRecording = false
124135 startRecordingUpdates()
125136 } catch (e: IllegalStateException ) {
126- Log .e(" AudioRecorder " , " Error resuming recording" )
137+ Log .e(TAG , " Error resuming recording" )
127138 }
128139 }
129140 }
130141 }
131142
132143 override fun recordingUpdates (): Flow <RecordingUpdate > = recordingUpdates
133144
134- override fun setRecordingParams (params : RecordingParams ) {
135- recordingParams = params
136- }
137-
145+ @Suppress(" MagicNumber" )
138146 private fun startRecordingUpdates () {
139147 recordingJob = coroutineScope.launch {
140- var elapsedTime = 0
148+ var elapsedTimeInSeconds = 0
141149 while (recorder != null ) {
142150 delay(RECORDING_UPDATE_INTERVAL )
143- elapsedTime ++
151+ elapsedTimeInSeconds + = ( RECORDING_UPDATE_INTERVAL / 1000 ).toInt()
144152 val fileSize = File (filePath).length()
145153 _recordingUpdates .value = RecordingUpdate (
146- elapsedTime = elapsedTime ,
154+ elapsedTime = elapsedTimeInSeconds ,
147155 fileSize = fileSize,
148- fileSizeLimitExceeded = fileSize >= recordingParams .maxFileSize,
156+ fileSizeLimitExceeded = fileSize >= recordingStrategy .maxFileSize,
149157 )
150158
151- if (fileSize >= recordingParams.maxFileSize
152- || elapsedTime >= recordingParams.maxDuration) {
159+ if ( maxFileSizeExceeded(fileSize) || maxDurationExceeded(elapsedTimeInSeconds) ) {
153160 stopRecording()
161+ break
154162 }
155163 }
156164 }
157165 }
158166
167+ /* *
168+ * Checks if the recorded file size has exceeded the specified maximum file size.
169+ *
170+ * @param fileSize The current size of the recorded file in bytes.
171+ * @return `true` if the file size has exceeded the maximum file size minus the threshold, `false` otherwise.
172+ * If `recordingParams.maxFileSize` is set to `-1L`, this function always returns `false` indicating
173+ * no limit.
174+ */
175+ private fun maxFileSizeExceeded (fileSize : Long ): Boolean = when {
176+ recordingStrategy.maxFileSize == - 1L -> false
177+ else -> fileSize >= recordingStrategy.maxFileSize - FILE_SIZE_THRESHOLD
178+ }
179+
180+ /* *
181+ * Checks if the recording duration has exceeded the specified maximum duration.
182+ *
183+ * @param elapsedTimeInSeconds The elapsed recording time in seconds.
184+ * @return `true` if the elapsed time has exceeded the maximum duration minus the threshold, `false` otherwise.
185+ * If `recordingParams.maxDuration` is set to `-1`, this function always returns `false` indicating
186+ * no limit.
187+ */
188+ private fun maxDurationExceeded (elapsedTimeInSeconds : Int ): Boolean = when {
189+ recordingStrategy.maxDuration == - 1 -> false
190+ else -> elapsedTimeInSeconds >= recordingStrategy.maxDuration - DURATION_THRESHOLD
191+ }
192+
159193 private fun stopRecordingUpdates () {
160194 recordingJob?.cancel()
161195 }
162196
163197 companion object {
164- private const val RECORDING_UPDATE_INTERVAL = 1000L
165- private const val RESUME_DELAY = 500L
198+ private const val TAG = " AudioRecorder"
199+ private const val RECORDING_UPDATE_INTERVAL = 1000L // in milliseconds
200+ private const val RESUME_DELAY = 500L // in milliseconds
201+ private const val FILE_SIZE_THRESHOLD = 100000L
202+ private const val DURATION_THRESHOLD = 1
166203 }
167204}
0 commit comments