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