new: allow user to seek a position in the record
This commit is contained in:
+4
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user