package core.presentation.components import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Pause import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.media3.common.MediaItem import androidx.media3.common.PlaybackParameters import androidx.media3.common.Player import androidx.media3.exoplayer.ExoPlayer import kotlinx.coroutines.delay import androidx.compose.material.icons.filled.Download import androidx.compose.material.icons.filled.GraphicEq import androidx.compose.ui.text.font.FontWeight @Composable fun AppAudioPlayer( url: String, isVoiceMessage: Boolean = false, modifier: Modifier = Modifier, contentColor: Color = Color.White ) { val context = LocalContext.current val exoPlayer = remember { ExoPlayer.Builder(context).build().apply { val mediaItem = MediaItem.fromUri(url) setMediaItem(mediaItem) prepare() } } var isPlaying by remember { mutableStateOf(false) } var currentPosition by remember { mutableLongStateOf(0L) } var duration by remember { mutableLongStateOf(0L) } var playbackSpeed by remember { mutableFloatStateOf(1.0f) } val fileName = remember(url) { url.substringAfterLast("/") } DisposableEffect(Unit) { val listener = object : Player.Listener { override fun onIsPlayingChanged(playing: Boolean) { isPlaying = playing } override fun onPlaybackStateChanged(state: Int) { if (state == Player.STATE_READY) { duration = exoPlayer.duration } } } exoPlayer.addListener(listener) onDispose { exoPlayer.removeListener(listener) exoPlayer.release() } } LaunchedEffect(isPlaying) { while (isPlaying) { currentPosition = exoPlayer.currentPosition delay(100) } } Column( modifier = modifier .fillMaxWidth() .clip(RoundedCornerShape(12.dp)) .background(Color.Black.copy(alpha = 0.2f)) .padding(8.dp) ) { if (!isVoiceMessage) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(bottom = 4.dp) ) { Icon( Icons.Default.GraphicEq, contentDescription = null, tint = contentColor.copy(alpha = 0.7f), modifier = Modifier.size(16.dp) ) Spacer(modifier = Modifier.width(4.dp)) Text( text = fileName, fontSize = 12.sp, fontWeight = FontWeight.Bold, color = contentColor, maxLines = 1, modifier = Modifier.weight(1f) ) } } Row( verticalAlignment = Alignment.CenterVertically ) { Box( modifier = Modifier .size(36.dp) .clip(CircleShape) .background(Color.White) .clickable { if (isPlaying) exoPlayer.pause() else exoPlayer.play() }, contentAlignment = Alignment.Center ) { Icon( imageVector = if (isPlaying) Icons.Default.Pause else Icons.Default.PlayArrow, contentDescription = null, tint = Color(0xFF3390EC), modifier = Modifier.size(24.dp) ) } Column( modifier = Modifier .weight(1f) .padding(horizontal = 12.dp) ) { Slider( value = if (duration > 0) currentPosition.toFloat() / duration.toFloat() else 0f, onValueChange = { val newPos = (it * duration).toLong() exoPlayer.seekTo(newPos) currentPosition = newPos }, modifier = Modifier.height(16.dp), colors = SliderDefaults.colors( thumbColor = Color.White, activeTrackColor = Color.White, inactiveTrackColor = Color.White.copy(alpha = 0.3f) ) ) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween ) { Text( text = formatDuration(currentPosition), fontSize = 10.sp, color = contentColor.copy(alpha = 0.7f) ) Row(verticalAlignment = Alignment.CenterVertically) { Text( text = "3.3 MB", // Mock size fontSize = 10.sp, color = contentColor.copy(alpha = 0.7f) ) Spacer(modifier = Modifier.width(4.dp)) Icon( Icons.Default.Download, contentDescription = null, tint = contentColor.copy(alpha = 0.7f), modifier = Modifier.size(12.dp) ) } } } if (isVoiceMessage) { Surface( onClick = { playbackSpeed = when (playbackSpeed) { 1.0f -> 1.5f 1.5f -> 2.0f else -> 1.0f } exoPlayer.playbackParameters = PlaybackParameters(playbackSpeed) }, color = Color.White.copy(alpha = 0.2f), shape = CircleShape, modifier = Modifier.size(32.dp) ) { Box(contentAlignment = Alignment.Center) { Text( text = "${if (playbackSpeed % 1.0f == 0.0f) playbackSpeed.toInt() else playbackSpeed}x", fontSize = 10.sp, fontWeight = FontWeight.Bold, color = Color.White ) } } } } } } private fun formatDuration(durationMs: Long): String { val totalSeconds = durationMs / 1000 val minutes = totalSeconds / 60 val seconds = totalSeconds % 60 return String.format("%02d:%02d", minutes, seconds) }