mirror of
https://github.com/Myzel394/Alibi.git
synced 2025-06-18 23:05:26 +02:00
feat: Add recording error handling
This commit is contained in:
parent
7620a065b4
commit
66b1d8a88d
@ -336,6 +336,9 @@ data class AudioRecorderSettings(
|
|||||||
7 to "OPUS",
|
7 to "OPUS",
|
||||||
)
|
)
|
||||||
val ENCODER_SUPPORTED_OUTPUT_FORMATS_MAP: Map<Int, Array<Int>> = mutableMapOf(
|
val ENCODER_SUPPORTED_OUTPUT_FORMATS_MAP: Map<Int, Array<Int>> = mutableMapOf(
|
||||||
|
MediaRecorder.AudioEncoder.DEFAULT to arrayOf(
|
||||||
|
MediaRecorder.OutputFormat.DEFAULT,
|
||||||
|
),
|
||||||
MediaRecorder.AudioEncoder.AAC to arrayOf(
|
MediaRecorder.AudioEncoder.AAC to arrayOf(
|
||||||
MediaRecorder.OutputFormat.THREE_GPP,
|
MediaRecorder.OutputFormat.THREE_GPP,
|
||||||
MediaRecorder.OutputFormat.MPEG_4,
|
MediaRecorder.OutputFormat.MPEG_4,
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
package app.myzel394.alibi.services
|
package app.myzel394.alibi.services
|
||||||
|
|
||||||
import android.media.MediaRecorder
|
import android.media.MediaRecorder
|
||||||
|
import android.media.MediaRecorder.OnErrorListener
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import java.lang.IllegalStateException
|
||||||
|
|
||||||
class AudioRecorderService: IntervalRecorderService() {
|
class AudioRecorderService: IntervalRecorderService() {
|
||||||
var amplitudesAmount = 1000
|
var amplitudesAmount = 1000
|
||||||
|
|
||||||
var recorder: MediaRecorder? = null
|
var recorder: MediaRecorder? = null
|
||||||
private set
|
private set
|
||||||
|
var onError: () -> Unit = {}
|
||||||
|
|
||||||
val filePath: String
|
val filePath: String
|
||||||
get() = "$folder/$counter.${settings!!.fileExtension}"
|
get() = "$folder/$counter.${settings!!.fileExtension}"
|
||||||
@ -24,6 +27,9 @@ class AudioRecorderService: IntervalRecorderService() {
|
|||||||
setAudioEncoder(settings!!.encoder)
|
setAudioEncoder(settings!!.encoder)
|
||||||
setAudioEncodingBitRate(settings!!.bitRate)
|
setAudioEncodingBitRate(settings!!.bitRate)
|
||||||
setAudioSamplingRate(settings!!.samplingRate)
|
setAudioSamplingRate(settings!!.samplingRate)
|
||||||
|
setOnErrorListener(OnErrorListener { _, _, _ ->
|
||||||
|
onError()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,8 +51,12 @@ class AudioRecorderService: IntervalRecorderService() {
|
|||||||
|
|
||||||
resetRecorder()
|
resetRecorder()
|
||||||
|
|
||||||
newRecorder.start()
|
try {
|
||||||
recorder = newRecorder
|
recorder = newRecorder
|
||||||
|
newRecorder.start()
|
||||||
|
} catch (error: RuntimeException) {
|
||||||
|
onError()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pause() {
|
override fun pause() {
|
||||||
|
@ -131,6 +131,7 @@ abstract class RecorderService: Service() {
|
|||||||
onStateChange?.invoke(newState)
|
onStateChange?.invoke(newState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Must be immediately called after creating the service!
|
||||||
fun startRecording() {
|
fun startRecording() {
|
||||||
recordingStart = LocalDateTime.now()
|
recordingStart = LocalDateTime.now()
|
||||||
|
|
||||||
@ -141,12 +142,6 @@ abstract class RecorderService: Service() {
|
|||||||
changeState(RecorderState.RECORDING)
|
changeState(RecorderState.RECORDING)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate() {
|
|
||||||
super.onCreate()
|
|
||||||
|
|
||||||
startRecording()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ import androidx.compose.material3.LinearProgressIndicator
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@ -200,7 +201,9 @@ fun RecordingStatus(
|
|||||||
contentDescription = label
|
contentDescription = label
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
audioRecorder.stopRecording(context)
|
runCatching {
|
||||||
|
audioRecorder.stopRecording(context)
|
||||||
|
}
|
||||||
audioRecorder.onRecordingSave()
|
audioRecorder.onRecordingSave()
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
@ -4,6 +4,7 @@ import android.content.ComponentName
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
|
import android.media.MediaRecorder
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@ -44,6 +45,7 @@ class AudioRecorderModel: ViewModel() {
|
|||||||
private set
|
private set
|
||||||
|
|
||||||
var onRecordingSave: () -> Unit = {}
|
var onRecordingSave: () -> Unit = {}
|
||||||
|
var onError: () -> Unit = {}
|
||||||
|
|
||||||
private val connection = object : ServiceConnection {
|
private val connection = object : ServiceConnection {
|
||||||
override fun onServiceConnected(className: ComponentName, service: IBinder) {
|
override fun onServiceConnected(className: ComponentName, service: IBinder) {
|
||||||
@ -58,10 +60,17 @@ class AudioRecorderModel: ViewModel() {
|
|||||||
amplitudes = amps
|
amplitudes = amps
|
||||||
onAmplitudeChange()
|
onAmplitudeChange()
|
||||||
}
|
}
|
||||||
|
recorder.onError = {
|
||||||
|
recorderService!!.createLastRecording()
|
||||||
|
onError()
|
||||||
|
}
|
||||||
|
}.also {
|
||||||
|
it.startRecording()
|
||||||
|
|
||||||
|
recorderState = it.state
|
||||||
|
recordingTime = it.recordingTime
|
||||||
|
amplitudes = it.amplitudes
|
||||||
}
|
}
|
||||||
recorderState = recorderService!!.state
|
|
||||||
recordingTime = recorderService!!.recordingTime
|
|
||||||
amplitudes = recorderService!!.amplitudes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onServiceDisconnected(arg0: ComponentName) {
|
override fun onServiceDisconnected(arg0: ComponentName) {
|
||||||
|
@ -10,7 +10,10 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Memory
|
import androidx.compose.material.icons.filled.Memory
|
||||||
import androidx.compose.material.icons.filled.Settings
|
import androidx.compose.material.icons.filled.Settings
|
||||||
|
import androidx.compose.material.icons.filled.Warning
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
@ -45,13 +48,15 @@ fun AudioRecorder(
|
|||||||
navController: NavController,
|
navController: NavController,
|
||||||
audioRecorder: AudioRecorderModel,
|
audioRecorder: AudioRecorderModel,
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
val settings = rememberSettings()
|
val settings = rememberSettings()
|
||||||
val saveFile = rememberFileSaverDialog(settings.audioRecorderSettings.getMimeType())
|
val saveFile = rememberFileSaverDialog(settings.audioRecorderSettings.getMimeType())
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
var isProcessingAudio by remember { mutableStateOf(false) }
|
var isProcessingAudio by remember { mutableStateOf(false) }
|
||||||
|
var showRecorderError by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
audioRecorder.onRecordingSave = {
|
audioRecorder.onRecordingSave = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
isProcessingAudio = true
|
isProcessingAudio = true
|
||||||
@ -67,6 +72,16 @@ fun AudioRecorder(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
audioRecorder.onError = {
|
||||||
|
// No need to save last recording as it's done automatically on error
|
||||||
|
audioRecorder.stopRecording(context, saveAsLastRecording = false)
|
||||||
|
showRecorderError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
onDispose {
|
||||||
|
audioRecorder.onRecordingSave = {}
|
||||||
|
audioRecorder.onError = {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isProcessingAudio)
|
if (isProcessingAudio)
|
||||||
@ -96,6 +111,40 @@ fun AudioRecorder(
|
|||||||
},
|
},
|
||||||
confirmButton = {}
|
confirmButton = {}
|
||||||
)
|
)
|
||||||
|
if (showRecorderError)
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showRecorderError = false },
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Warning,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(stringResource(R.string.ui_audioRecorder_error_recording_title))
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(stringResource(R.string.ui_audioRecorder_error_recording_description))
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
Button(
|
||||||
|
onClick = { showRecorderError = false },
|
||||||
|
colors = ButtonDefaults.textButtonColors(),
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.dialog_close_cancel_label))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
audioRecorder.onRecordingSave()
|
||||||
|
},
|
||||||
|
colors = ButtonDefaults.textButtonColors(),
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.ui_audioRecorder_action_save_label))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
|
@ -60,4 +60,6 @@
|
|||||||
<string name="ui_settings_value_auto_label">Auto</string>
|
<string name="ui_settings_value_auto_label">Auto</string>
|
||||||
<string name="ui_audioRecorder_state_paused_title">Recording paused</string>
|
<string name="ui_audioRecorder_state_paused_title">Recording paused</string>
|
||||||
<string name="ui_audioRecorder_state_paused_description">Audio Recording has been paused</string>
|
<string name="ui_audioRecorder_state_paused_description">Audio Recording has been paused</string>
|
||||||
|
<string name="ui_audioRecorder_error_recording_title">An error occured</string>
|
||||||
|
<string name="ui_audioRecorder_error_recording_description">Alibi encountered an error during recording. Would you like to try saving the recording?</string>
|
||||||
</resources>
|
</resources>
|
Loading…
x
Reference in New Issue
Block a user