refactor VoiceMessagePreview.kt
This commit is contained in:
+135
-94
@@ -74,64 +74,23 @@ fun VoiceMessagePreview(
|
|||||||
var progress by remember { mutableFloatStateOf(0f) }
|
var progress by remember { mutableFloatStateOf(0f) }
|
||||||
var mediaPlayer by remember { mutableStateOf<MediaPlayer?>(null) }
|
var mediaPlayer by remember { mutableStateOf<MediaPlayer?>(null) }
|
||||||
|
|
||||||
// Initialize MediaPlayer
|
ManageMediaPlayer(
|
||||||
DisposableEffect(voiceMetadata.url, localFile) {
|
voiceMetadata = voiceMetadata,
|
||||||
val player = createMediaPlayer(voiceMetadata.url, localFile)
|
localFile = localFile,
|
||||||
player?.setOnCompletionListener {
|
onCompletion = {
|
||||||
isPlaying = false
|
isPlaying = false
|
||||||
progress = 0f
|
progress = 0f
|
||||||
}
|
},
|
||||||
mediaPlayer = player
|
onPlayerChanged = { mediaPlayer = it },
|
||||||
|
onRelease = { isPlaying = false },
|
||||||
|
)
|
||||||
|
|
||||||
onDispose {
|
TrackPlaybackProgress(
|
||||||
// Stop playback and clean up
|
mediaPlayer = mediaPlayer,
|
||||||
try {
|
isPlaying = isPlaying,
|
||||||
player?.stop()
|
onProgressUpdate = { progress = it },
|
||||||
} catch (e: IllegalStateException) {
|
onInvalidState = { isPlaying = false },
|
||||||
// Player might already be stopped
|
)
|
||||||
Log.d("VoiceMessagePreview", "MediaPlayer stop failed (already stopped)", e)
|
|
||||||
}
|
|
||||||
player?.release()
|
|
||||||
mediaPlayer = null
|
|
||||||
isPlaying = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update progress while playing
|
|
||||||
LaunchedEffect(mediaPlayer, isPlaying) {
|
|
||||||
// Capture player reference to avoid reading volatile state repeatedly
|
|
||||||
val player = mediaPlayer
|
|
||||||
if (player != null && isPlaying) {
|
|
||||||
while (isActive) {
|
|
||||||
try {
|
|
||||||
if (player.isPlaying) {
|
|
||||||
val current = player.currentPosition.toFloat()
|
|
||||||
val duration = player.duration.toFloat()
|
|
||||||
// Validate values before calculating progress
|
|
||||||
val newProgress =
|
|
||||||
if (duration > 0 && current >= 0) {
|
|
||||||
(current / duration).coerceIn(0f, 1f)
|
|
||||||
} else {
|
|
||||||
0f
|
|
||||||
}
|
|
||||||
// Only update if value is valid (not NaN or Infinity)
|
|
||||||
if (newProgress.isFinite()) {
|
|
||||||
progress = newProgress
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Player stopped, exit loop and let LaunchedEffect restart
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} catch (e: IllegalStateException) {
|
|
||||||
// Player in invalid state, stop tracking
|
|
||||||
Log.w("VoiceMessagePreview", "MediaPlayer in invalid state during progress tracking", e)
|
|
||||||
isPlaying = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
delay(100)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
@@ -150,27 +109,13 @@ fun VoiceMessagePreview(
|
|||||||
// Play/Pause Button
|
// Play/Pause Button
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
val player = mediaPlayer
|
handlePlayPauseClick(
|
||||||
if (player != null) {
|
mediaPlayer = mediaPlayer,
|
||||||
try {
|
isPlaying = isPlaying,
|
||||||
if (isPlaying) {
|
progress = progress,
|
||||||
player.pause()
|
onProgressReset = { progress = 0f },
|
||||||
isPlaying = false
|
onPlayingChanged = { isPlaying = it },
|
||||||
} else {
|
)
|
||||||
// Validate progress before comparison
|
|
||||||
if (progress.isFinite() && progress >= 1f) {
|
|
||||||
player.seekTo(0)
|
|
||||||
progress = 0f
|
|
||||||
}
|
|
||||||
player.start()
|
|
||||||
isPlaying = true
|
|
||||||
}
|
|
||||||
} catch (e: IllegalStateException) {
|
|
||||||
// MediaPlayer in invalid state, ignore
|
|
||||||
Log.w("VoiceMessagePreview", "MediaPlayer operation failed in onClick handler", e)
|
|
||||||
isPlaying = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
modifier = Modifier.size(48.dp),
|
modifier = Modifier.size(48.dp),
|
||||||
) {
|
) {
|
||||||
@@ -194,24 +139,11 @@ fun VoiceMessagePreview(
|
|||||||
waveformBrush = Brush.linearGradient(listOf(MaterialTheme.colorScheme.onSurfaceVariant, MaterialTheme.colorScheme.onSurfaceVariant)),
|
waveformBrush = Brush.linearGradient(listOf(MaterialTheme.colorScheme.onSurfaceVariant, MaterialTheme.colorScheme.onSurfaceVariant)),
|
||||||
progressBrush = Brush.linearGradient(listOf(MaterialTheme.colorScheme.primary, MaterialTheme.colorScheme.primary)),
|
progressBrush = Brush.linearGradient(listOf(MaterialTheme.colorScheme.primary, MaterialTheme.colorScheme.primary)),
|
||||||
onProgressChange = { newProgress ->
|
onProgressChange = { newProgress ->
|
||||||
// Validate incoming progress value
|
handleWaveformScrub(
|
||||||
if (newProgress.isFinite() && newProgress >= 0f && newProgress <= 1f) {
|
newProgress = newProgress,
|
||||||
val player = mediaPlayer
|
mediaPlayer = mediaPlayer,
|
||||||
if (player != null) {
|
onProgressChanged = { progress = it },
|
||||||
try {
|
)
|
||||||
val duration = player.duration
|
|
||||||
// Only seek if duration is valid
|
|
||||||
if (duration > 0) {
|
|
||||||
val newPosition = (newProgress * duration).toInt()
|
|
||||||
player.seekTo(newPosition)
|
|
||||||
progress = newProgress
|
|
||||||
}
|
|
||||||
} catch (e: IllegalStateException) {
|
|
||||||
// MediaPlayer in invalid state, ignore
|
|
||||||
Log.w("VoiceMessagePreview", "MediaPlayer seek failed in onProgressChange", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -240,6 +172,115 @@ fun VoiceMessagePreview(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ManageMediaPlayer(
|
||||||
|
voiceMetadata: AudioMeta,
|
||||||
|
localFile: File?,
|
||||||
|
onCompletion: () -> Unit,
|
||||||
|
onPlayerChanged: (MediaPlayer?) -> Unit,
|
||||||
|
onRelease: () -> Unit,
|
||||||
|
) {
|
||||||
|
DisposableEffect(voiceMetadata.url, localFile) {
|
||||||
|
val player = createMediaPlayer(voiceMetadata.url, localFile)
|
||||||
|
player?.setOnCompletionListener { onCompletion() }
|
||||||
|
onPlayerChanged(player)
|
||||||
|
|
||||||
|
onDispose {
|
||||||
|
try {
|
||||||
|
player?.stop()
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
Log.d("VoiceMessagePreview", "MediaPlayer stop failed (already stopped)", e)
|
||||||
|
}
|
||||||
|
player?.release()
|
||||||
|
onPlayerChanged(null)
|
||||||
|
onRelease()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TrackPlaybackProgress(
|
||||||
|
mediaPlayer: MediaPlayer?,
|
||||||
|
isPlaying: Boolean,
|
||||||
|
onProgressUpdate: (Float) -> Unit,
|
||||||
|
onInvalidState: () -> Unit,
|
||||||
|
) {
|
||||||
|
LaunchedEffect(mediaPlayer, isPlaying) {
|
||||||
|
val player = mediaPlayer ?: return@LaunchedEffect
|
||||||
|
if (!isPlaying) return@LaunchedEffect
|
||||||
|
|
||||||
|
while (isActive) {
|
||||||
|
try {
|
||||||
|
if (!player.isPlaying) break
|
||||||
|
calculateProgress(player.currentPosition.toFloat(), player.duration.toFloat())?.let { onProgressUpdate(it) }
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
Log.w("VoiceMessagePreview", "MediaPlayer in invalid state during progress tracking", e)
|
||||||
|
onInvalidState()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
delay(100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculateProgress(
|
||||||
|
current: Float,
|
||||||
|
duration: Float,
|
||||||
|
): Float? {
|
||||||
|
val progress =
|
||||||
|
if (duration > 0 && current >= 0) {
|
||||||
|
(current / duration).coerceIn(0f, 1f)
|
||||||
|
} else {
|
||||||
|
0f
|
||||||
|
}
|
||||||
|
return progress.takeIf { it.isFinite() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handlePlayPauseClick(
|
||||||
|
mediaPlayer: MediaPlayer?,
|
||||||
|
isPlaying: Boolean,
|
||||||
|
progress: Float,
|
||||||
|
onProgressReset: () -> Unit,
|
||||||
|
onPlayingChanged: (Boolean) -> Unit,
|
||||||
|
) {
|
||||||
|
val player = mediaPlayer ?: return
|
||||||
|
try {
|
||||||
|
if (isPlaying) {
|
||||||
|
player.pause()
|
||||||
|
onPlayingChanged(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (progress.isFinite() && progress >= 1f) {
|
||||||
|
player.seekTo(0)
|
||||||
|
onProgressReset()
|
||||||
|
}
|
||||||
|
player.start()
|
||||||
|
onPlayingChanged(true)
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
Log.w("VoiceMessagePreview", "MediaPlayer operation failed in onClick handler", e)
|
||||||
|
onPlayingChanged(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleWaveformScrub(
|
||||||
|
newProgress: Float,
|
||||||
|
mediaPlayer: MediaPlayer?,
|
||||||
|
onProgressChanged: (Float) -> Unit,
|
||||||
|
) {
|
||||||
|
if (!newProgress.isFinite() || newProgress < 0f || newProgress > 1f) return
|
||||||
|
val player = mediaPlayer ?: return
|
||||||
|
try {
|
||||||
|
val duration = player.duration
|
||||||
|
if (duration > 0) {
|
||||||
|
val newPosition = (newProgress * duration).toInt()
|
||||||
|
player.seekTo(newPosition)
|
||||||
|
onProgressChanged(newProgress)
|
||||||
|
}
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
Log.w("VoiceMessagePreview", "MediaPlayer seek failed in onProgressChange", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun createMediaPlayer(
|
private fun createMediaPlayer(
|
||||||
url: String,
|
url: String,
|
||||||
localFile: File?,
|
localFile: File?,
|
||||||
|
|||||||
Reference in New Issue
Block a user