new: allow user to seek a position in the record

This commit is contained in:
2026-06-19 13:53:10 +02:00
parent 511e3b5b9a
commit 7720a71825
7 changed files with 48 additions and 3 deletions
@@ -49,6 +49,10 @@ class AndroidAudioPlayer(
?.milliseconds
?: 0.milliseconds
override fun seekTo(position: Duration) {
player.seekTo(position.inWholeMilliseconds)
}
override fun load(uri: String) {
val mediaItem = MediaItem.fromUri(uri)
player.setMediaItem(mediaItem)
@@ -10,6 +10,8 @@ interface AudioPlayer {
val duration: Duration
fun seekTo(position: Duration)
fun play()
fun pause()
@@ -13,7 +13,9 @@ import platform.AVFoundation.duration
import platform.AVFoundation.pause
import platform.AVFoundation.play
import platform.AVFoundation.replaceCurrentItemWithPlayerItem
import platform.AVFoundation.seekToTime
import platform.CoreMedia.CMTimeGetSeconds
import platform.CoreMedia.CMTimeMakeWithSeconds
import platform.Foundation.NSNotificationCenter
import platform.Foundation.NSOperationQueue
import platform.Foundation.NSURL
@@ -45,6 +47,12 @@ class IosAudioPlayer : AudioPlayer {
return seconds?.seconds ?: 0.milliseconds
}
@OptIn(ExperimentalForeignApi::class)
override fun seekTo(position: Duration) {
val seconds = position.inWholeSeconds
player?.seekToTime(CMTimeMakeWithSeconds(seconds.toDouble(), preferredTimescale = 1000))
}
@OptIn(ExperimentalForeignApi::class)
override fun load(uri: String) {
release()
@@ -33,6 +33,10 @@ class JvmAudioPlayer : AudioPlayer {
override val duration: Duration
get() = (clip?.microsecondLength ?: 0L).microseconds
override fun seekTo(position: Duration) {
clip?.microsecondPosition = position.inWholeMicroseconds
}
override fun load(uri: String) {
release()
@@ -20,6 +20,10 @@ class WebAudioPlayer : AudioPlayer {
override val duration: Duration
get() = (audio?.duration?.takeIf { it.isFinite() } ?: 0.0).seconds
override fun seekTo(position: Duration) {
audio?.currentTime = position.inWholeMicroseconds.toDouble()
}
override fun load(uri: String) {
release()
@@ -18,14 +18,17 @@ import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Slider
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
@@ -58,6 +61,7 @@ fun PlayerScreen(
recordTitle = recordTitle,
uiState = uiState,
onMainPlayActionButtonClicked = viewModel::performMainPlayAction,
onSeek = viewModel::seekTo,
onBackClicked = onBackClicked,
)
}
@@ -68,6 +72,7 @@ fun PlayerScreen(
recordTitle: String,
uiState: PlayerUiState,
onMainPlayActionButtonClicked: () -> Unit,
onSeek: (progress: Float) -> Unit,
onBackClicked: () -> Unit,
) {
Scaffold(
@@ -103,8 +108,18 @@ fun PlayerScreen(
Spacer(modifier = Modifier.weight(1f))
LinearProgressIndicator(
progress = { uiState.progress },
var seekProgress by remember { mutableStateOf<Float?>(null) }
Slider(
value = seekProgress ?: uiState.progress,
onValueChange = { seekProgress = it },
onValueChangeFinished = {
seekProgress?.let { fraction ->
if (uiState.durationMs > 0) {
onSeek(fraction)
}
}
seekProgress = null
},
modifier = Modifier.fillMaxWidth(),
)
@@ -145,6 +160,7 @@ private fun PlayerScreenPlayingPreview() {
recordTitle = "Demat",
uiState = PlayerUiState(playbackState = PlaybackState.PLAYING, positionMs = 1234, durationMs = 5000),
onMainPlayActionButtonClicked = {},
onSeek = {},
onBackClicked = {},
)
}
@@ -158,6 +174,7 @@ private fun PlayerScreenPausedPreview() {
recordTitle = "Demat",
uiState = PlayerUiState(playbackState = PlaybackState.PAUSED, positionMs = 1234, durationMs = 5000),
onMainPlayActionButtonClicked = {},
onSeek = {},
onBackClicked = {},
)
}
@@ -171,6 +188,7 @@ private fun PlayerScreenEndedPreview() {
recordTitle = "Demat",
uiState = PlayerUiState(playbackState = PlaybackState.ENDED, positionMs = 1234, durationMs = 5000),
onMainPlayActionButtonClicked = {},
onSeek = {},
onBackClicked = {},
)
}
@@ -81,6 +81,11 @@ class PlayerViewModel(
}
}
fun seekTo(progress: Float) {
val position = (progress.toDouble() * uiState.value.durationMs).milliseconds
audioPlayer.seekTo(position)
}
fun stop() {
audioPlayer.stop()
}