fix: Properly reset AudioRecorder

This commit is contained in:
Myzel394 2023-08-05 19:07:39 +02:00
parent 1269c6cb00
commit 9a43afbe3e
No known key found for this signature in database
GPG Key ID: 79CC92F37B3E1A2B
4 changed files with 99 additions and 55 deletions

View File

@ -11,9 +11,6 @@ import android.os.Handler
import android.os.IBinder import android.os.IBinder
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
@ -35,7 +32,7 @@ import java.time.ZoneId
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.Date import java.util.Date
import java.util.UUID; import java.util.UUID
const val AMPLITUDE_UPDATE_INTERVAL = 100L const val AMPLITUDE_UPDATE_INTERVAL = 100L
@ -47,26 +44,20 @@ class RecorderService: Service() {
private var mediaRecorder: MediaRecorder? = null private var mediaRecorder: MediaRecorder? = null
private var onError: MediaRecorder.OnErrorListener? = null private var onError: MediaRecorder.OnErrorListener? = null
private var onStateChange: (RecorderState) -> Unit = {}
private var onAmplitudeUpdate: () -> Unit = {} private var onAmplitudeUpdate: () -> Unit = {}
private var counter = 0 private var counter = 0
lateinit var settings: Settings lateinit var settings: Settings
var recordingStart = mutableStateOf<LocalDateTime?>(null)
private set
var fileFolder: String? = null var fileFolder: String? = null
private set private set
var recordingState: RecorderState = RecorderState.IDLE val isRecording = mutableStateOf(false)
private set
val isRecording: Boolean
get() = recordingStart.value != null
val filePaths = mutableListOf<String>() val filePaths = mutableListOf<String>()
val amplitudes = mutableStateListOf<Int>() val amplitudes = mutableStateListOf<Int>()
var originalRecordingStart: LocalDateTime? = null var recordingStart: LocalDateTime? = null
private set private set
override fun onBind(p0: Intent?): IBinder = binder override fun onBind(p0: Intent?): IBinder = binder
@ -90,21 +81,26 @@ class RecorderService: Service() {
scope.cancel() scope.cancel()
} }
fun setOnErrorListener(onError: MediaRecorder.OnErrorListener) {
this.onError = onError
}
fun setOnAmplitudeUpdateListener(onAmplitudeUpdate: () -> Unit) { fun setOnAmplitudeUpdateListener(onAmplitudeUpdate: () -> Unit) {
this.onAmplitudeUpdate = onAmplitudeUpdate this.onAmplitudeUpdate = onAmplitudeUpdate
} }
fun setOnStateChangeListener(onStateChange: (RecorderState) -> Unit) { fun reset() {
this.onStateChange = onStateChange recordingStart = null
counter = 0
amplitudes.clear()
isRecording.value = false
filePaths.forEach {
File(it).delete()
}
filePaths.clear()
} }
fun concatenateFiles(forceConcatenation: Boolean = false): File { fun concatenateFiles(forceConcatenation: Boolean = false): File {
val paths = filePaths.joinToString("|") val paths = filePaths.joinToString("|")
val outputFile = "$fileFolder/${originalRecordingStart!!.format(DateTimeFormatter.ISO_DATE_TIME)}.${settings.fileExtension}" val outputFile = "$fileFolder/${recordingStart!!.format(DateTimeFormatter.ISO_DATE_TIME)}.${settings.fileExtension}"
if (File(outputFile).exists() && !forceConcatenation) { if (File(outputFile).exists() && !forceConcatenation) {
return File(outputFile) return File(outputFile)
@ -132,7 +128,7 @@ class RecorderService: Service() {
} }
private fun updateAmplitude() { private fun updateAmplitude() {
if (!isRecording || mediaRecorder == null) { if (!isRecording.value || mediaRecorder == null) {
return return
} }
@ -144,7 +140,7 @@ class RecorderService: Service() {
} }
private fun startNewRecording() { private fun startNewRecording() {
if (!isRecording) { if (!isRecording.value) {
return return
} }
@ -205,17 +201,15 @@ class RecorderService: Service() {
private fun start() { private fun start() {
amplitudes.clear() reset()
filePaths.clear()
// Create folder // Create folder
File(this.fileFolder!!).mkdirs() File(this.fileFolder!!).mkdirs()
scope.launch { scope.launch {
dataStore.data.collectLatest { preferenceSettings -> dataStore.data.collectLatest { preferenceSettings ->
settings = Settings.from(preferenceSettings.audioRecorderSettings) settings = Settings.from(preferenceSettings.audioRecorderSettings)
recordingState = RecorderState.RECORDING recordingStart = LocalDateTime.now()
recordingStart.value = LocalDateTime.now() isRecording.value = true
originalRecordingStart = recordingStart.value
showNotification() showNotification()
startNewRecording() startNewRecording()
@ -225,22 +219,20 @@ class RecorderService: Service() {
} }
private fun stop() { private fun stop() {
recordingState = RecorderState.IDLE
mediaRecorder?.apply { mediaRecorder?.apply {
runCatching { runCatching {
stop() stop()
release() release()
} }
} }
recordingStart.value = null isRecording.value = false
stopForeground(STOP_FOREGROUND_REMOVE) stopForeground(STOP_FOREGROUND_REMOVE)
stopSelf() stopSelf()
} }
private fun showNotification() { private fun showNotification() {
if (recordingStart.value == null) { if (!isRecording.value) {
return return
} }
@ -254,7 +246,7 @@ class RecorderService: Service() {
.setOnlyAlertOnce(true) .setOnlyAlertOnce(true)
.setUsesChronometer(true) .setUsesChronometer(true)
.setChronometerCountDown(false) .setChronometerCountDown(false)
.setWhen(Date.from(recordingStart.value!!.atZone(ZoneId.systemDefault()).toInstant()).time) .setWhen(Date.from(recordingStart!!.atZone(ZoneId.systemDefault()).toInstant()).time)
.setShowWhen(true) .setShowWhen(true)
.build() .build()
@ -267,10 +259,10 @@ class RecorderService: Service() {
// To avoid int overflow, we'll use the number of seconds since 2023-01-01 01:01:01 // To avoid int overflow, we'll use the number of seconds since 2023-01-01 01:01:01
private fun getNotificationId(): Int { private fun getNotificationId(): Int {
val offset = ZoneId.of("UTC").rules.getOffset(recordingStart.value) val offset = ZoneId.of("UTC").rules.getOffset(recordingStart!!)
return ( return (
recordingStart.value!!.toEpochSecond(offset) - recordingStart!!.toEpochSecond(offset) -
LocalDateTime.of(2023, 1, 1, 1, 1).toEpochSecond(offset) LocalDateTime.of(2023, 1, 1, 1, 1).toEpochSecond(offset)
).toInt() ).toInt()
} }
@ -286,12 +278,6 @@ class RecorderService: Service() {
STOP, STOP,
} }
enum class RecorderState {
IDLE,
RECORDING,
PAUSED,
}
companion object { companion object {
fun getRandomFileFolder(context: Context): String { fun getRandomFileFolder(context: Context): String {
// uuid // uuid
@ -302,7 +288,7 @@ class RecorderService: Service() {
fun startService(context: Context, connection: ServiceConnection?) { fun startService(context: Context, connection: ServiceConnection?) {
Intent(context, RecorderService::class.java).also { intent -> Intent(context, RecorderService::class.java).also { intent ->
intent.action = RecorderService.Actions.START.toString() intent.action = Actions.START.toString()
ContextCompat.startForegroundService(context, intent) ContextCompat.startForegroundService(context, intent)
@ -314,7 +300,7 @@ class RecorderService: Service() {
fun stopService(context: Context) { fun stopService(context: Context) {
Intent(context, RecorderService::class.java).also { intent -> Intent(context, RecorderService::class.java).also { intent ->
intent.action = RecorderService.Actions.STOP.toString() intent.action = Actions.STOP.toString()
context.startService(intent) context.startService(intent)
} }

View File

@ -15,7 +15,9 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Cancel import androidx.compose.material.icons.filled.Cancel
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Save import androidx.compose.material.icons.filled.Save
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@ -55,7 +57,7 @@ fun RecordingStatus(
var now by remember { mutableStateOf(LocalDateTime.now()) } var now by remember { mutableStateOf(LocalDateTime.now()) }
val start = service.recordingStart.value!! val start = service.recordingStart!!
val duration = now.toEpochSecond(ZoneId.systemDefault().rules.getOffset(now)) - start.toEpochSecond(ZoneId.systemDefault().rules.getOffset(start)) val duration = now.toEpochSecond(ZoneId.systemDefault().rules.getOffset(now)) - start.toEpochSecond(ZoneId.systemDefault().rules.getOffset(start))
val progress = duration / (service.settings.maxDuration / 1000f) val progress = duration / (service.settings.maxDuration / 1000f)
@ -81,7 +83,7 @@ fun RecordingStatus(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
) { ) {
val distance = Duration.between(service.recordingStart.value, now).toMillis() val distance = Duration.between(service.recordingStart, now).toMillis()
Pulsating { Pulsating {
Box( Box(
@ -104,18 +106,81 @@ fun RecordingStatus(
.width(300.dp) .width(300.dp)
) )
Spacer(modifier = Modifier.height(32.dp)) Spacer(modifier = Modifier.height(32.dp))
var showDeleteDialog by remember { mutableStateOf(false) }
if (showDeleteDialog) {
AlertDialog(
onDismissRequest = {
showDeleteDialog = false
},
title = {
Text("Delete Recording?")
},
text = {
Text("Are you sure you want to delete this recording?")
},
icon = {
Icon(
Icons.Default.Delete,
contentDescription = null,
)
},
confirmButton = {
Button(
modifier = Modifier
.semantics {
contentDescription = "Confirm Recording Deletion"
},
onClick = {
showDeleteDialog = false
RecorderService.stopService(context)
service.reset()
},
) {
Icon(
Icons.Default.Delete,
contentDescription = null,
modifier = Modifier.size(ButtonDefaults.IconSize),
)
Spacer(modifier = Modifier.width(ButtonDefaults.IconSpacing))
Text("Delete")
}
},
dismissButton = {
Button(
modifier = Modifier
.semantics {
contentDescription = "Cancel Recording Deletion"
},
onClick = {
showDeleteDialog = false
},
colors = ButtonDefaults.textButtonColors(),
) {
Icon(
Icons.Default.Cancel,
contentDescription = null,
modifier = Modifier.size(ButtonDefaults.IconSize),
)
Spacer(modifier = Modifier.width(ButtonDefaults.IconSpacing))
Text("Cancel")
}
}
)
}
Button( Button(
modifier = Modifier modifier = Modifier
.semantics { .semantics {
contentDescription = "Delete Recording" contentDescription = "Delete Recording"
}, },
onClick = { onClick = {
RecorderService.stopService(context) showDeleteDialog = true
}, },
colors = ButtonDefaults.textButtonColors(), colors = ButtonDefaults.textButtonColors(),
) { ) {
Icon( Icon(
Icons.Default.Cancel, Icons.Default.Delete,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(ButtonDefaults.IconSize), modifier = Modifier.size(ButtonDefaults.IconSize),
) )

View File

@ -1,9 +1,7 @@
package app.myzel394.locationtest.ui.components.AudioRecorder.atoms package app.myzel394.locationtest.ui.components.AudioRecorder.atoms
import android.content.ServiceConnection import android.content.ServiceConnection
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@ -25,8 +23,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.semantics
@ -83,7 +79,7 @@ fun StartRecording(
) )
} }
} }
if (service?.originalRecordingStart != null) if (service?.recordingStart != null)
Column( Column(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
@ -112,7 +108,7 @@ fun StartRecording(
.size(ButtonDefaults.IconSize), .size(ButtonDefaults.IconSize),
) )
Spacer(modifier = Modifier.width(ButtonDefaults.IconSpacing)) Spacer(modifier = Modifier.width(ButtonDefaults.IconSpacing))
Text("Save Recording from ${service.originalRecordingStart!!.format(DateTimeFormatter.ISO_DATE_TIME)}") Text("Save Recording from ${service.recordingStart!!.format(DateTimeFormatter.ISO_DATE_TIME)}")
} }
} }
else else

View File

@ -58,9 +58,6 @@ fun AudioRecorder(
object : ServiceConnection { object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) { override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
service = (binder as RecorderService.LocalBinder).getService().also { service -> service = (binder as RecorderService.LocalBinder).getService().also { service ->
service.setOnStateChangeListener {
println("asd")
}
} }
} }
@ -69,7 +66,7 @@ fun AudioRecorder(
} }
} }
val isRecording = service?.isRecording ?: false val isRecording = service?.isRecording?.value ?: false
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
Intent(context, RecorderService::class.java).also { intent -> Intent(context, RecorderService::class.java).also { intent ->