Add variable playback speed feature for voice messages

Add playback speed control for voice messages with support for 0.5×, 1×, 1.5×, and 2× playback speeds. The speed button is displayed above the timestamp and cycles through the available speeds when tapped.
This commit is contained in:
Florian 2025-10-09 21:43:47 +02:00 committed by GitHub
parent 1c26c03cde
commit 5e07691fcd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 203 additions and 15 deletions

View file

@ -8,6 +8,8 @@
package io.element.android.features.messages.impl.timeline.components.event
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@ -16,6 +18,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@ -109,19 +112,30 @@ fun TimelineItemVoiceView(
}
}
Spacer(Modifier.width(8.dp))
Text(
text = state.time,
color = ElementTheme.colors.textSecondary,
style = ElementTheme.typography.fontBodySmMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
PlaybackSpeedButton(
speed = state.playbackSpeed,
onClick = { state.eventSink(VoiceMessageEvents.ChangePlaybackSpeed) },
)
Text(
text = state.time,
color = ElementTheme.colors.textSecondary,
style = ElementTheme.typography.fontBodySmMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
Spacer(Modifier.width(8.dp))
WaveformPlaybackView(
showCursor = state.showCursor,
playbackProgress = state.progress,
waveform = content.waveform,
modifier = Modifier.height(34.dp),
modifier = Modifier
.weight(1f)
.height(34.dp),
seekEnabled = !isTalkbackActive(),
onSeek = { state.eventSink(VoiceMessageEvents.Seek(it)) },
)
@ -172,6 +186,36 @@ private fun RetryButton(
}
}
@Composable
private fun PlaybackSpeedButton(
speed: Float,
onClick: () -> Unit,
) {
val speedText = when (speed) {
0.5f -> "0.5×"
1.0f -> "1×"
1.5f -> "1.5×"
2.0f -> "2×"
else -> "${speed}×"
}
androidx.compose.foundation.layout.Box(
modifier = Modifier
.background(
color = ElementTheme.colors.bgCanvasDefault,
shape = RoundedCornerShape(12.dp)
)
.padding(horizontal = 8.dp, vertical = 4.dp)
.clickable(onClick = onClick),
contentAlignment = Alignment.Center,
) {
Text(
text = speedText,
color = ElementTheme.colors.iconSecondary,
style = ElementTheme.typography.fontBodyXsMedium,
)
}
}
@Composable
private fun ControlIcon(
imageVector: ImageVector,
@ -295,3 +339,14 @@ internal fun ProgressButtonPreview() = ElementPreview {
ProgressButton(displayImmediately = false)
}
}
@PreviewsDayNight
@Composable
internal fun PlaybackSpeedButtonPreview() = ElementPreview {
Row {
PlaybackSpeedButton(speed = 0.5f, onClick = {})
PlaybackSpeedButton(speed = 1.0f, onClick = {})
PlaybackSpeedButton(speed = 1.5f, onClick = {})
PlaybackSpeedButton(speed = 2.0f, onClick = {})
}
}