package core.presentation.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape 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 @Composable fun AppAudioPlayer( url: String, isVoiceMessage: Boolean = false, modifier: Modifier = Modifier, contentColor: Color = MaterialTheme.colorScheme.onSurfaceVariant ) { 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) } 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) } } Row( modifier = modifier .fillMaxWidth() .padding(vertical = 4.dp), verticalAlignment = Alignment.CenterVertically ) { IconButton( onClick = { if (isPlaying) exoPlayer.pause() else exoPlayer.play() }, modifier = Modifier.size(32.dp) ) { Icon( imageVector = if (isPlaying) Icons.Default.Pause else Icons.Default.PlayArrow, contentDescription = null, tint = contentColor ) } Column(modifier = Modifier.weight(1f).padding(horizontal = 8.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(24.dp), colors = SliderDefaults.colors( thumbColor = contentColor, activeTrackColor = contentColor, inactiveTrackColor = contentColor.copy(alpha = 0.3f) ) ) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween ) { Text( text = formatDuration(currentPosition), fontSize = 10.sp, color = contentColor.copy(alpha = 0.7f) ) Text( text = formatDuration(duration), fontSize = 10.sp, color = contentColor.copy(alpha = 0.7f) ) } } if (isVoiceMessage) { TextButton( onClick = { playbackSpeed = when (playbackSpeed) { 1.0f -> 1.5f 1.5f -> 2.0f else -> 1.0f } exoPlayer.playbackParameters = PlaybackParameters(playbackSpeed) }, contentPadding = PaddingValues(0.dp), modifier = Modifier.width(40.dp) ) { Text( text = "${playbackSpeed}x", fontSize = 12.sp, color = contentColor, style = MaterialTheme.typography.labelSmall ) } } } } 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) }