Improved Playing recording dialog
This commit is contained in:
@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBars
|
||||
@ -29,18 +30,26 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.CallMade
|
||||
import androidx.compose.material.icons.automirrored.filled.CallReceived
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
@ -58,6 +67,7 @@ import com.tutpro.baresip.CallRow.Details
|
||||
import com.tutpro.baresip.CustomElements.AlertDialog
|
||||
import com.tutpro.baresip.CustomElements.verticalScrollbar
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
@ -260,10 +270,16 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
||||
val mediaPlayer = remember { MediaPlayer() }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
AlertDialog(
|
||||
PlaybackDialog(
|
||||
showDialog = showDialog,
|
||||
title = stringResource(R.string.playing_recording),
|
||||
message = "",
|
||||
mediaPlayer = mediaPlayer,
|
||||
onStop = {
|
||||
if (mediaPlayer.isPlaying) {
|
||||
mediaPlayer.stop()
|
||||
}
|
||||
mediaPlayer.reset()
|
||||
showDialog.value = false
|
||||
}
|
||||
)
|
||||
|
||||
val isRawState = recording[0] != "" && recording[1] != ""
|
||||
@ -284,13 +300,15 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
||||
if (isRawState) {
|
||||
val fileIn = File(recording[0])
|
||||
val fileOut = File(recording[1])
|
||||
val mergedFileName = "merged_${fileIn.nameWithoutExtension}_${fileOut.nameWithoutExtension}.wav"
|
||||
val mergedFileName =
|
||||
"merged_${fileIn.nameWithoutExtension}_${fileOut.nameWithoutExtension}.wav"
|
||||
val mergedFile = File(BaresipService.filesPath + "/tmp", mergedFileName)
|
||||
|
||||
if (mergedFile.exists()) {
|
||||
Log.d(TAG, "Using already merged file: ${mergedFile.name}")
|
||||
finalFile = mergedFile
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
File(BaresipService.filesPath + "/tmp").mkdirs()
|
||||
if (Utils.mergeWavFiles(fileIn, fileOut, mergedFile)) {
|
||||
finalFile = mergedFile
|
||||
@ -315,7 +333,8 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
||||
Log.w(TAG, "MergeWav: Failed to delete original files: ${e.message}")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// We are already in merged state
|
||||
Log.d(TAG, "Using already merged file: ${recording[0]}")
|
||||
finalFile = File(recording[0])
|
||||
@ -350,12 +369,14 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
||||
Log.e(TAG, "Playback failed: $e")
|
||||
Toast.makeText(ctx, "Playback error", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
Toast.makeText(ctx, "Failed to process audio file", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
mediaPlayer.stop()
|
||||
mediaPlayer.reset()
|
||||
showDialog.value = false
|
||||
@ -367,3 +388,73 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PlaybackDialog(
|
||||
showDialog: MutableState<Boolean>,
|
||||
mediaPlayer: MediaPlayer,
|
||||
onStop: () -> Unit
|
||||
) {
|
||||
if (showDialog.value) {
|
||||
// State to hold progress (0.0f to 1.0f)
|
||||
var currentProgress by remember { mutableFloatStateOf(0f) }
|
||||
|
||||
// Formatted time strings
|
||||
var currentPositionText by remember { mutableStateOf("00:00") }
|
||||
var totalDurationText by remember { mutableStateOf("00:00") }
|
||||
|
||||
// Update progress every 100ms
|
||||
LaunchedEffect(showDialog.value) {
|
||||
if (mediaPlayer.isPlaying) {
|
||||
val duration = mediaPlayer.duration
|
||||
totalDurationText = DateUtils.formatElapsedTime(duration / 1000L)
|
||||
|
||||
while (mediaPlayer.isPlaying) {
|
||||
val current = mediaPlayer.currentPosition
|
||||
currentProgress = if (duration > 0) current.toFloat() / duration.toFloat() else 0f
|
||||
currentPositionText = DateUtils.formatElapsedTime(current / 1000L)
|
||||
delay(100) // Poll every 100ms
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
// If user clicks outside, stop playback
|
||||
onStop()
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(R.string.playing_recording))
|
||||
},
|
||||
text = {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
LinearProgressIndicator(
|
||||
progress = { currentProgress },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(8.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(text = currentPositionText, style = MaterialTheme.typography.bodySmall)
|
||||
Text(text = totalDurationText, style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
onStop()
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.stop))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -261,6 +261,7 @@
|
||||
<string name="time">Time</string>
|
||||
<string name="calls_duration">Duration</string>
|
||||
<string name="playing_recording">Playing recording …</string>
|
||||
<string name="stop">Stop</string>
|
||||
<string name="calls_add_delete_question">Do you want to add \'%1$s\' to contacts or delete
|
||||
%2$s from call history\?</string>
|
||||
<string name="calls_delete_question">Do you want to delete \'%1$s\' %2$s from call history\?</string>
|
||||
|
||||
Reference in New Issue
Block a user