fix(android): make top audio strip controls functional
This commit is contained in:
@@ -317,6 +317,8 @@ fun ChatScreen(
|
||||
var dismissedPinnedMessageId by remember { mutableStateOf<Long?>(null) }
|
||||
var topAudioStrip by remember { mutableStateOf<TopAudioStrip?>(null) }
|
||||
var forceStopAudioSourceId by remember { mutableStateOf<String?>(null) }
|
||||
var forceToggleAudioSourceId by remember { mutableStateOf<String?>(null) }
|
||||
var forceCycleSpeedAudioSourceId by remember { mutableStateOf<String?>(null) }
|
||||
var actionMenuMessage by remember { mutableStateOf<MessageItem?>(null) }
|
||||
var pendingDeleteForAll by remember { mutableStateOf(false) }
|
||||
var showDeleteDialog by remember { mutableStateOf(false) }
|
||||
@@ -662,12 +664,15 @@ fun ChatScreen(
|
||||
Surface(
|
||||
shape = CircleShape,
|
||||
color = MaterialTheme.colorScheme.primary.copy(alpha = 0.2f),
|
||||
modifier = Modifier.size(30.dp),
|
||||
modifier = Modifier
|
||||
.size(30.dp)
|
||||
.clip(CircleShape)
|
||||
.clickable { forceToggleAudioSourceId = strip.sourceId },
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.PlayArrow,
|
||||
contentDescription = "Play top audio",
|
||||
imageVector = if (strip.isPlaying) Icons.Filled.Pause else Icons.Filled.PlayArrow,
|
||||
contentDescription = if (strip.isPlaying) "Pause top audio" else "Play top audio",
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
@@ -688,9 +693,12 @@ fun ChatScreen(
|
||||
Surface(
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.65f),
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.clickable { forceCycleSpeedAudioSourceId = strip.sourceId },
|
||||
) {
|
||||
Text(
|
||||
text = "1x",
|
||||
text = strip.speedLabel,
|
||||
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
)
|
||||
@@ -698,6 +706,8 @@ fun ChatScreen(
|
||||
IconButton(
|
||||
onClick = {
|
||||
forceStopAudioSourceId = strip.sourceId
|
||||
forceToggleAudioSourceId = null
|
||||
forceCycleSpeedAudioSourceId = null
|
||||
topAudioStrip = null
|
||||
},
|
||||
) {
|
||||
@@ -762,11 +772,23 @@ fun ChatScreen(
|
||||
}
|
||||
},
|
||||
forceStopAudioSourceId = forceStopAudioSourceId,
|
||||
forceToggleAudioSourceId = forceToggleAudioSourceId,
|
||||
forceCycleSpeedAudioSourceId = forceCycleSpeedAudioSourceId,
|
||||
onForceStopAudioSourceHandled = { sourceId ->
|
||||
if (forceStopAudioSourceId == sourceId) {
|
||||
forceStopAudioSourceId = null
|
||||
}
|
||||
},
|
||||
onForceToggleAudioSourceHandled = { sourceId ->
|
||||
if (forceToggleAudioSourceId == sourceId) {
|
||||
forceToggleAudioSourceId = null
|
||||
}
|
||||
},
|
||||
onForceCycleSpeedAudioSourceHandled = { sourceId ->
|
||||
if (forceCycleSpeedAudioSourceId == sourceId) {
|
||||
forceCycleSpeedAudioSourceId = null
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
if (state.actionState.mode == MessageSelectionMode.MULTI) {
|
||||
onToggleMessageMultiSelection(message)
|
||||
@@ -1789,7 +1811,11 @@ private fun MessageBubble(
|
||||
onAttachmentVideoClick: (String) -> Unit,
|
||||
onAudioPlaybackChanged: (TopAudioStrip) -> Unit,
|
||||
forceStopAudioSourceId: String?,
|
||||
forceToggleAudioSourceId: String?,
|
||||
forceCycleSpeedAudioSourceId: String?,
|
||||
onForceStopAudioSourceHandled: (String) -> Unit,
|
||||
onForceToggleAudioSourceHandled: (String) -> Unit,
|
||||
onForceCycleSpeedAudioSourceHandled: (String) -> Unit,
|
||||
onClick: () -> Unit,
|
||||
onLongPress: () -> Unit,
|
||||
) {
|
||||
@@ -2009,7 +2035,11 @@ private fun MessageBubble(
|
||||
messageId = message.id,
|
||||
onPlaybackChanged = onAudioPlaybackChanged,
|
||||
forceStopAudioSourceId = forceStopAudioSourceId,
|
||||
forceToggleAudioSourceId = forceToggleAudioSourceId,
|
||||
forceCycleSpeedAudioSourceId = forceCycleSpeedAudioSourceId,
|
||||
onForceStopAudioSourceHandled = onForceStopAudioSourceHandled,
|
||||
onForceToggleAudioSourceHandled = onForceToggleAudioSourceHandled,
|
||||
onForceCycleSpeedAudioSourceHandled = onForceCycleSpeedAudioSourceHandled,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2484,7 +2514,11 @@ private fun AudioAttachmentPlayer(
|
||||
messageId: Long,
|
||||
onPlaybackChanged: (TopAudioStrip) -> Unit,
|
||||
forceStopAudioSourceId: String?,
|
||||
forceToggleAudioSourceId: String?,
|
||||
forceCycleSpeedAudioSourceId: String?,
|
||||
onForceStopAudioSourceHandled: (String) -> Unit,
|
||||
onForceToggleAudioSourceHandled: (String) -> Unit,
|
||||
onForceCycleSpeedAudioSourceHandled: (String) -> Unit,
|
||||
) {
|
||||
var isPlaying by remember(url) { mutableStateOf(false) }
|
||||
var isPrepared by remember(url) { mutableStateOf(false) }
|
||||
@@ -2494,6 +2528,18 @@ private fun AudioAttachmentPlayer(
|
||||
var speedIndex by remember(url) { mutableStateOf(0) }
|
||||
var isSeeking by remember(url) { mutableStateOf(false) }
|
||||
var seekFraction by remember(url) { mutableStateOf(0f) }
|
||||
fun emitTopStrip(playing: Boolean) {
|
||||
onPlaybackChanged(
|
||||
TopAudioStrip(
|
||||
messageId = messageId,
|
||||
sourceId = "player:$url",
|
||||
title = playbackTitle,
|
||||
subtitle = playbackSubtitle,
|
||||
isPlaying = playing,
|
||||
speedLabel = formatAudioSpeed(speedOptions[speedIndex]),
|
||||
)
|
||||
)
|
||||
}
|
||||
val mediaPlayer = remember(url) {
|
||||
MediaPlayer().apply {
|
||||
setAudioAttributes(
|
||||
@@ -2511,15 +2557,7 @@ private fun AudioAttachmentPlayer(
|
||||
runCatching { player.seekTo(0) }
|
||||
positionMs = 0
|
||||
AppAudioFocusCoordinator.release("player:$url")
|
||||
onPlaybackChanged(
|
||||
TopAudioStrip(
|
||||
messageId = messageId,
|
||||
sourceId = "player:$url",
|
||||
title = playbackTitle,
|
||||
subtitle = playbackSubtitle,
|
||||
isPlaying = false,
|
||||
)
|
||||
)
|
||||
emitTopStrip(false)
|
||||
}
|
||||
setDataSource(url)
|
||||
prepareAsync()
|
||||
@@ -2531,15 +2569,7 @@ private fun AudioAttachmentPlayer(
|
||||
if (activeId != null && activeId != currentId && isPlaying) {
|
||||
runCatching { mediaPlayer.pause() }
|
||||
isPlaying = false
|
||||
onPlaybackChanged(
|
||||
TopAudioStrip(
|
||||
messageId = messageId,
|
||||
sourceId = currentId,
|
||||
title = playbackTitle,
|
||||
subtitle = playbackSubtitle,
|
||||
isPlaying = false,
|
||||
)
|
||||
)
|
||||
emitTopStrip(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2551,17 +2581,48 @@ private fun AudioAttachmentPlayer(
|
||||
positionMs = 0
|
||||
isPlaying = false
|
||||
AppAudioFocusCoordinator.release(currentId)
|
||||
onPlaybackChanged(
|
||||
TopAudioStrip(
|
||||
messageId = messageId,
|
||||
sourceId = currentId,
|
||||
title = playbackTitle,
|
||||
subtitle = playbackSubtitle,
|
||||
isPlaying = false,
|
||||
)
|
||||
)
|
||||
emitTopStrip(false)
|
||||
onForceStopAudioSourceHandled(currentId)
|
||||
}
|
||||
LaunchedEffect(forceToggleAudioSourceId) {
|
||||
val currentId = "player:$url"
|
||||
if (forceToggleAudioSourceId != currentId) return@LaunchedEffect
|
||||
if (!isPrepared) {
|
||||
onForceToggleAudioSourceHandled(currentId)
|
||||
return@LaunchedEffect
|
||||
}
|
||||
if (isPlaying) {
|
||||
runCatching { mediaPlayer.pause() }
|
||||
isPlaying = false
|
||||
AppAudioFocusCoordinator.release(currentId)
|
||||
emitTopStrip(false)
|
||||
} else {
|
||||
if (durationMs > 0 && positionMs >= durationMs - 200) {
|
||||
runCatching { mediaPlayer.seekTo(0) }
|
||||
positionMs = 0
|
||||
}
|
||||
runCatching {
|
||||
mediaPlayer.playbackParams = mediaPlayer.playbackParams.setSpeed(speedOptions[speedIndex])
|
||||
}
|
||||
AppAudioFocusCoordinator.request(currentId)
|
||||
runCatching { mediaPlayer.start() }
|
||||
isPlaying = true
|
||||
emitTopStrip(true)
|
||||
}
|
||||
onForceToggleAudioSourceHandled(currentId)
|
||||
}
|
||||
LaunchedEffect(forceCycleSpeedAudioSourceId) {
|
||||
val currentId = "player:$url"
|
||||
if (forceCycleSpeedAudioSourceId != currentId) return@LaunchedEffect
|
||||
speedIndex = (speedIndex + 1) % speedOptions.size
|
||||
if (isPrepared) {
|
||||
runCatching {
|
||||
mediaPlayer.playbackParams = mediaPlayer.playbackParams.setSpeed(speedOptions[speedIndex])
|
||||
}
|
||||
}
|
||||
emitTopStrip(isPlaying)
|
||||
onForceCycleSpeedAudioSourceHandled(currentId)
|
||||
}
|
||||
LaunchedEffect(isPlaying, isPrepared, isSeeking) {
|
||||
if (!isPlaying || !isPrepared || isSeeking) return@LaunchedEffect
|
||||
while (isPlaying) {
|
||||
@@ -2582,15 +2643,7 @@ private fun AudioAttachmentPlayer(
|
||||
mediaPlayer.stop()
|
||||
}
|
||||
AppAudioFocusCoordinator.release("player:$url")
|
||||
onPlaybackChanged(
|
||||
TopAudioStrip(
|
||||
messageId = messageId,
|
||||
sourceId = "player:$url",
|
||||
title = playbackTitle,
|
||||
subtitle = playbackSubtitle,
|
||||
isPlaying = false,
|
||||
)
|
||||
)
|
||||
emitTopStrip(false)
|
||||
mediaPlayer.release()
|
||||
}
|
||||
}
|
||||
@@ -2612,15 +2665,7 @@ private fun AudioAttachmentPlayer(
|
||||
mediaPlayer.pause()
|
||||
isPlaying = false
|
||||
AppAudioFocusCoordinator.release("player:$url")
|
||||
onPlaybackChanged(
|
||||
TopAudioStrip(
|
||||
messageId = messageId,
|
||||
sourceId = "player:$url",
|
||||
title = playbackTitle,
|
||||
subtitle = playbackSubtitle,
|
||||
isPlaying = false,
|
||||
)
|
||||
)
|
||||
emitTopStrip(false)
|
||||
} else {
|
||||
if (durationMs > 0 && positionMs >= durationMs - 200) {
|
||||
runCatching { mediaPlayer.seekTo(0) }
|
||||
@@ -2632,15 +2677,7 @@ private fun AudioAttachmentPlayer(
|
||||
AppAudioFocusCoordinator.request("player:$url")
|
||||
mediaPlayer.start()
|
||||
isPlaying = true
|
||||
onPlaybackChanged(
|
||||
TopAudioStrip(
|
||||
messageId = messageId,
|
||||
sourceId = "player:$url",
|
||||
title = playbackTitle,
|
||||
subtitle = playbackSubtitle,
|
||||
isPlaying = true,
|
||||
)
|
||||
)
|
||||
emitTopStrip(true)
|
||||
}
|
||||
},
|
||||
enabled = isPrepared,
|
||||
@@ -2659,10 +2696,11 @@ private fun AudioAttachmentPlayer(
|
||||
mediaPlayer.playbackParams = mediaPlayer.playbackParams.setSpeed(speedOptions[speedIndex])
|
||||
}
|
||||
}
|
||||
emitTopStrip(isPlaying)
|
||||
},
|
||||
enabled = isPrepared,
|
||||
) {
|
||||
Text("${speedOptions[speedIndex]}x")
|
||||
Text(formatAudioSpeed(speedOptions[speedIndex]))
|
||||
}
|
||||
}
|
||||
if (isVoice) {
|
||||
@@ -2794,6 +2832,14 @@ private fun formatDuration(ms: Int): String {
|
||||
return "$min:${sec.toString().padStart(2, '0')}"
|
||||
}
|
||||
|
||||
private fun formatAudioSpeed(speed: Float): String {
|
||||
return if (speed % 1f == 0f) {
|
||||
"${speed.toInt()}x"
|
||||
} else {
|
||||
"${speed}x"
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveRemoteAudioDurationMs(url: String): Int? {
|
||||
return runCatching {
|
||||
val retriever = MediaMetadataRetriever()
|
||||
@@ -3008,7 +3054,11 @@ private fun ChatInfoTabContent(
|
||||
messageId = entry.sourceMessageId ?: entry.title.hashCode().toLong(),
|
||||
onPlaybackChanged = {},
|
||||
forceStopAudioSourceId = null,
|
||||
forceToggleAudioSourceId = null,
|
||||
forceCycleSpeedAudioSourceId = null,
|
||||
onForceStopAudioSourceHandled = {},
|
||||
onForceToggleAudioSourceHandled = {},
|
||||
onForceCycleSpeedAudioSourceHandled = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -3152,4 +3202,5 @@ private data class TopAudioStrip(
|
||||
val title: String,
|
||||
val subtitle: String,
|
||||
val isPlaying: Boolean,
|
||||
val speedLabel: String = "1x",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user