make fastseek work
This commit is contained in:
parent
ae3ef47a3f
commit
990a4aaa12
|
@ -45,7 +45,7 @@ interface NewPlayer {
|
||||||
val bufferedPercentage: Int
|
val bufferedPercentage: Int
|
||||||
val repository: MediaRepository
|
val repository: MediaRepository
|
||||||
var currentPosition: Long
|
var currentPosition: Long
|
||||||
var fastSeekAmountSec: Long
|
var fastSeekAmountSec: Int
|
||||||
var playBackMode: PlayMode
|
var playBackMode: PlayMode
|
||||||
var playList: MutableList<String>
|
var playList: MutableList<String>
|
||||||
|
|
||||||
|
@ -54,6 +54,7 @@ interface NewPlayer {
|
||||||
fun pause()
|
fun pause()
|
||||||
fun fastSeekForward()
|
fun fastSeekForward()
|
||||||
fun fastSeekBackward()
|
fun fastSeekBackward()
|
||||||
|
fun seekTo(millisecond: Long)
|
||||||
fun addToPlaylist(newItem: String)
|
fun addToPlaylist(newItem: String)
|
||||||
fun addListener(callbackListener: Listener)
|
fun addListener(callbackListener: Listener)
|
||||||
|
|
||||||
|
@ -88,7 +89,7 @@ class NewPlayerImpl(override val internal_player: Player, override val repositor
|
||||||
override val duartion: Long = internal_player.duration
|
override val duartion: Long = internal_player.duration
|
||||||
override val bufferedPercentage: Int = internal_player.bufferedPercentage
|
override val bufferedPercentage: Int = internal_player.bufferedPercentage
|
||||||
override var currentPosition: Long = internal_player.currentPosition
|
override var currentPosition: Long = internal_player.currentPosition
|
||||||
override var fastSeekAmountSec: Long = 100
|
override var fastSeekAmountSec: Int = 10
|
||||||
override var playBackMode: PlayMode = PlayMode.EMBEDDED_VIDEO
|
override var playBackMode: PlayMode = PlayMode.EMBEDDED_VIDEO
|
||||||
override var playList: MutableList<String> = ArrayList<String>()
|
override var playList: MutableList<String> = ArrayList<String>()
|
||||||
|
|
||||||
|
@ -115,11 +116,17 @@ class NewPlayerImpl(override val internal_player: Player, override val repositor
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fastSeekForward() {
|
override fun fastSeekForward() {
|
||||||
Log.d(TAG, "not implemented fast seek forward")
|
val currentPosition = internal_player.currentPosition
|
||||||
|
internal_player.seekTo(currentPosition + fastSeekAmountSec * 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fastSeekBackward() {
|
override fun fastSeekBackward() {
|
||||||
Log.d(TAG, "not implemented fast seek backward")
|
val currentPosition = internal_player.currentPosition
|
||||||
|
internal_player.seekTo(currentPosition - fastSeekAmountSec * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun seekTo(millisecond: Long) {
|
||||||
|
internal_player.seekTo(millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addToPlaylist(newItem: String) {
|
override fun addToPlaylist(newItem: String) {
|
||||||
|
|
|
@ -37,7 +37,8 @@ data class VideoPlayerUIState(
|
||||||
val bufferedPercentage: Float,
|
val bufferedPercentage: Float,
|
||||||
val isLoading: Boolean,
|
val isLoading: Boolean,
|
||||||
val durationInMs: Long,
|
val durationInMs: Long,
|
||||||
val playbackPositionInMs: Long
|
val playbackPositionInMs: Long,
|
||||||
|
val fastseekSeconds: Int,
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
companion object {
|
companion object {
|
||||||
val DEFAULT = VideoPlayerUIState(
|
val DEFAULT = VideoPlayerUIState(
|
||||||
|
@ -52,7 +53,8 @@ data class VideoPlayerUIState(
|
||||||
bufferedPercentage = 0f,
|
bufferedPercentage = 0f,
|
||||||
isLoading = true,
|
isLoading = true,
|
||||||
durationInMs = 0,
|
durationInMs = 0,
|
||||||
playbackPositionInMs = 0
|
playbackPositionInMs = 0,
|
||||||
|
fastseekSeconds = 10
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -65,6 +65,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
installExoPlayer()
|
installExoPlayer()
|
||||||
|
mutableUiState.update { it.copy(fastseekSeconds = field?.fastSeekAmountSec ?: 10) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override val uiState = mutableUiState.asStateFlow()
|
override val uiState = mutableUiState.asStateFlow()
|
||||||
|
@ -259,7 +260,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
||||||
resetHideUiDelayedJob()
|
resetHideUiDelayedJob()
|
||||||
val seekerPosition = mutableUiState.value.seekerPosition
|
val seekerPosition = mutableUiState.value.seekerPosition
|
||||||
val seekPositionInMs = (player?.duration?.toFloat() ?: 0F) * seekerPosition
|
val seekPositionInMs = (player?.duration?.toFloat() ?: 0F) * seekerPosition
|
||||||
player?.seekTo(seekPositionInMs.toLong())
|
newPlayer?.seekTo(seekPositionInMs.toLong())
|
||||||
Log.i(TAG, "Seek to Ms: $seekPositionInMs")
|
Log.i(TAG, "Seek to Ms: $seekPositionInMs")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,6 +269,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fastSeekForward() {
|
override fun fastSeekForward() {
|
||||||
|
mutableUiState.update { it.copy(fastseekSeconds = newPlayer?.fastSeekAmountSec ?: 10) }
|
||||||
newPlayer?.fastSeekForward()
|
newPlayer?.fastSeekForward()
|
||||||
if (mutableUiState.value.uiVisible) {
|
if (mutableUiState.value.uiVisible) {
|
||||||
resetHideUiDelayedJob()
|
resetHideUiDelayedJob()
|
||||||
|
@ -275,6 +277,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fastSeekBackward() {
|
override fun fastSeekBackward() {
|
||||||
|
mutableUiState.update { it.copy(fastseekSeconds = newPlayer?.fastSeekAmountSec ?: 10) }
|
||||||
newPlayer?.fastSeekBackward()
|
newPlayer?.fastSeekBackward()
|
||||||
if (mutableUiState.value.uiVisible) {
|
if (mutableUiState.value.uiVisible) {
|
||||||
resetHideUiDelayedJob()
|
resetHideUiDelayedJob()
|
||||||
|
|
|
@ -62,6 +62,7 @@ fun VideoPlayerControllerUI(
|
||||||
durationInMs: Long,
|
durationInMs: Long,
|
||||||
playbackPositionInMs: Long,
|
playbackPositionInMs: Long,
|
||||||
bufferedPercentage: Float,
|
bufferedPercentage: Float,
|
||||||
|
fastSeekSeconds: Int,
|
||||||
play: () -> Unit,
|
play: () -> Unit,
|
||||||
pause: () -> Unit,
|
pause: () -> Unit,
|
||||||
prevStream: () -> Unit,
|
prevStream: () -> Unit,
|
||||||
|
@ -101,7 +102,8 @@ fun VideoPlayerControllerUI(
|
||||||
switchToEmbeddedView = switchToEmbeddedView,
|
switchToEmbeddedView = switchToEmbeddedView,
|
||||||
embeddedDraggedDownBy = embeddedDraggedDownBy,
|
embeddedDraggedDownBy = embeddedDraggedDownBy,
|
||||||
fastSeekForward = fastSeekForward,
|
fastSeekForward = fastSeekForward,
|
||||||
fastSeekBackward = fastSeekBackward
|
fastSeekBackward = fastSeekBackward,
|
||||||
|
fastSeekSeconds = fastSeekSeconds
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +138,8 @@ fun VideoPlayerControllerUI(
|
||||||
switchToEmbeddedView = switchToEmbeddedView,
|
switchToEmbeddedView = switchToEmbeddedView,
|
||||||
embeddedDraggedDownBy = embeddedDraggedDownBy,
|
embeddedDraggedDownBy = embeddedDraggedDownBy,
|
||||||
fastSeekForward = fastSeekForward,
|
fastSeekForward = fastSeekForward,
|
||||||
fastSeekBackward = fastSeekBackward
|
fastSeekBackward = fastSeekBackward,
|
||||||
|
fastSeekSeconds = fastSeekSeconds
|
||||||
)
|
)
|
||||||
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
@ -219,6 +222,7 @@ fun VideoPlayerControllerUIPreviewEmbedded() {
|
||||||
durationInMs = 9*60*1000,
|
durationInMs = 9*60*1000,
|
||||||
playbackPositionInMs = 6*60*1000,
|
playbackPositionInMs = 6*60*1000,
|
||||||
bufferedPercentage = 0.4f,
|
bufferedPercentage = 0.4f,
|
||||||
|
fastSeekSeconds = 10,
|
||||||
play = {},
|
play = {},
|
||||||
pause = {},
|
pause = {},
|
||||||
prevStream = {},
|
prevStream = {},
|
||||||
|
@ -249,6 +253,7 @@ fun VideoPlayerControllerUIPreviewLandscape() {
|
||||||
durationInMs = 9*60*1000,
|
durationInMs = 9*60*1000,
|
||||||
playbackPositionInMs = 6*60*1000,
|
playbackPositionInMs = 6*60*1000,
|
||||||
bufferedPercentage = 0.4f,
|
bufferedPercentage = 0.4f,
|
||||||
|
fastSeekSeconds = 10,
|
||||||
play = {},
|
play = {},
|
||||||
pause = {},
|
pause = {},
|
||||||
prevStream = {},
|
prevStream = {},
|
||||||
|
@ -280,6 +285,7 @@ fun VideoPlayerControllerUIPreviewPortrait() {
|
||||||
durationInMs = 9*60*1000,
|
durationInMs = 9*60*1000,
|
||||||
playbackPositionInMs = 6*60*1000,
|
playbackPositionInMs = 6*60*1000,
|
||||||
bufferedPercentage = 0.4f,
|
bufferedPercentage = 0.4f,
|
||||||
|
fastSeekSeconds = 10,
|
||||||
play = {},
|
play = {},
|
||||||
pause = {},
|
pause = {},
|
||||||
prevStream = {},
|
prevStream = {},
|
||||||
|
|
|
@ -10,6 +10,7 @@ import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
@ -30,6 +31,7 @@ fun VideoPlayerLoadingPlaceholder(aspectRatio: Float = 3F / 1F) {
|
||||||
.height(64.dp)
|
.height(64.dp)
|
||||||
.align((Alignment.Center)))
|
.align((Alignment.Center)))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -152,6 +152,7 @@ fun VideoPlayerUI(
|
||||||
durationInMs = uiState.durationInMs,
|
durationInMs = uiState.durationInMs,
|
||||||
playbackPositionInMs = uiState.playbackPositionInMs,
|
playbackPositionInMs = uiState.playbackPositionInMs,
|
||||||
bufferedPercentage = uiState.bufferedPercentage,
|
bufferedPercentage = uiState.bufferedPercentage,
|
||||||
|
fastSeekSeconds = uiState.fastseekSeconds,
|
||||||
play = viewModel::play,
|
play = viewModel::play,
|
||||||
pause = viewModel::pause,
|
pause = viewModel::pause,
|
||||||
prevStream = viewModel::prevStream,
|
prevStream = viewModel::prevStream,
|
||||||
|
|
|
@ -83,6 +83,7 @@ fun VideoPlayerControllerUIPreviewEmbeddedColorPreview() {
|
||||||
durationInMs = 9*60*1000,
|
durationInMs = 9*60*1000,
|
||||||
playbackPositionInMs = 6*60*1000,
|
playbackPositionInMs = 6*60*1000,
|
||||||
bufferedPercentage = 0.4f,
|
bufferedPercentage = 0.4f,
|
||||||
|
fastSeekSeconds = 10,
|
||||||
play = {},
|
play = {},
|
||||||
pause = {},
|
pause = {},
|
||||||
prevStream = {},
|
prevStream = {},
|
||||||
|
|
|
@ -22,23 +22,43 @@ package net.newpipe.newplayer.ui.videoplayer
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.animateColor
|
||||||
|
import androidx.compose.animation.core.LinearEasing
|
||||||
|
import androidx.compose.animation.core.RepeatMode
|
||||||
|
import androidx.compose.animation.core.infiniteRepeatable
|
||||||
|
import androidx.compose.animation.core.keyframes
|
||||||
|
import androidx.compose.animation.core.rememberInfiniteTransition
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.scale
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.input.pointer.pointerInteropFilter
|
import androidx.compose.ui.input.pointer.pointerInteropFilter
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import net.newpipe.newplayer.R
|
||||||
|
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
|
||||||
|
|
||||||
private const val TAG = "TouchUi"
|
private const val TAG = "TouchUi"
|
||||||
|
|
||||||
|
@ -47,7 +67,10 @@ private data class TouchedPosition(val x: Float, val y: Float) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const val DELAY_UNTIL_SHOWING_UI_AFTER_TOUCH_IN_MS: Long = 200
|
const val DELAY_UNTIL_SHOWING_UI_AFTER_TOUCH_IN_MS: Long = 200
|
||||||
|
const val SEEK_ANIMATION_DURATION_IN_MS = 400
|
||||||
|
const val SEEK_ANIMATION_VISSIBLE_IN_MS = 500L
|
||||||
|
const val SEEK_ANIMATION_FADE_IN = 200
|
||||||
|
const val SEEK_ANIMATION_FADE_OUT = 500
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GestureUI(
|
fun GestureUI(
|
||||||
|
@ -56,6 +79,7 @@ fun GestureUI(
|
||||||
showUi: () -> Unit,
|
showUi: () -> Unit,
|
||||||
uiVissible: Boolean,
|
uiVissible: Boolean,
|
||||||
fullscreen: Boolean,
|
fullscreen: Boolean,
|
||||||
|
fastSeekSeconds: Int,
|
||||||
switchToFullscreen: () -> Unit,
|
switchToFullscreen: () -> Unit,
|
||||||
switchToEmbeddedView: () -> Unit,
|
switchToEmbeddedView: () -> Unit,
|
||||||
embeddedDraggedDownBy: (Float) -> Unit,
|
embeddedDraggedDownBy: (Float) -> Unit,
|
||||||
|
@ -70,14 +94,53 @@ fun GestureUI(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var showFastSeekBack by remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
var showFastSeekForward by remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
val composeScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val doForwardSeek = {
|
||||||
|
showFastSeekForward = true
|
||||||
|
composeScope.launch {
|
||||||
|
delay(SEEK_ANIMATION_VISSIBLE_IN_MS)
|
||||||
|
showFastSeekForward = false
|
||||||
|
}
|
||||||
|
fastSeekForward()
|
||||||
|
}
|
||||||
|
|
||||||
|
val doBackwardSeek = {
|
||||||
|
showFastSeekBack = true
|
||||||
|
composeScope.launch {
|
||||||
|
delay(SEEK_ANIMATION_VISSIBLE_IN_MS)
|
||||||
|
showFastSeekBack = false
|
||||||
|
}
|
||||||
|
fastSeekBackward()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (fullscreen) {
|
if (fullscreen) {
|
||||||
Row(modifier = modifier) {
|
Row(modifier = modifier) {
|
||||||
TouchSurface(
|
TouchSurface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f),
|
.weight(1f),
|
||||||
onRegularTap = defaultOnRegularTap,
|
onRegularTap = defaultOnRegularTap,
|
||||||
onDoubleTab = fastSeekBackward
|
onDoubleTab = doBackwardSeek
|
||||||
)
|
) {
|
||||||
|
FadedAnimationForSeekFeedback(visible = showFastSeekBack) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
FastSeekVisualFeedback(
|
||||||
|
seconds = fastSeekSeconds,
|
||||||
|
backwards = true,
|
||||||
|
modifier = Modifier.align(Alignment.CenterEnd)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
TouchSurface(
|
TouchSurface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f),
|
.weight(1f),
|
||||||
|
@ -92,8 +155,18 @@ fun GestureUI(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f),
|
.weight(1f),
|
||||||
onRegularTap = defaultOnRegularTap,
|
onRegularTap = defaultOnRegularTap,
|
||||||
onDoubleTab = fastSeekForward
|
onDoubleTab = doForwardSeek
|
||||||
)
|
) {
|
||||||
|
FadedAnimationForSeekFeedback(visible = showFastSeekForward) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
FastSeekVisualFeedback(
|
||||||
|
modifier = Modifier.align(Alignment.CenterStart),
|
||||||
|
seconds = fastSeekSeconds,
|
||||||
|
backwards = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else { // (!fullscreen)
|
} else { // (!fullscreen)
|
||||||
val handleDownwardMovement = { movement: TouchedPosition ->
|
val handleDownwardMovement = { movement: TouchedPosition ->
|
||||||
|
@ -109,21 +182,52 @@ fun GestureUI(
|
||||||
TouchSurface(
|
TouchSurface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f),
|
.weight(1f),
|
||||||
onDoubleTab = fastSeekBackward,
|
onDoubleTab = doBackwardSeek,
|
||||||
onRegularTap = defaultOnRegularTap,
|
onRegularTap = defaultOnRegularTap,
|
||||||
onMovement = handleDownwardMovement
|
onMovement = handleDownwardMovement
|
||||||
)
|
) {
|
||||||
|
FadedAnimationForSeekFeedback(visible = showFastSeekBack) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
FastSeekVisualFeedback(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
seconds = fastSeekSeconds,
|
||||||
|
backwards = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
TouchSurface(
|
TouchSurface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f),
|
.weight(1f),
|
||||||
onDoubleTab = fastSeekForward,
|
onDoubleTab = doForwardSeek,
|
||||||
onRegularTap = defaultOnRegularTap,
|
onRegularTap = defaultOnRegularTap,
|
||||||
onMovement = handleDownwardMovement
|
onMovement = handleDownwardMovement
|
||||||
)
|
) {
|
||||||
|
FadedAnimationForSeekFeedback(visible = showFastSeekForward) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
FastSeekVisualFeedback(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
seconds = fastSeekSeconds,
|
||||||
|
backwards = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FadedAnimationForSeekFeedback(visible: Boolean, content: @Composable () -> Unit) {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = visible,
|
||||||
|
enter = fadeIn(animationSpec = tween(SEEK_ANIMATION_FADE_IN)),
|
||||||
|
exit = fadeOut(animationSpec = tween(SEEK_ANIMATION_FADE_OUT))
|
||||||
|
) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@OptIn(ExperimentalComposeUiApi::class)
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
private fun TouchSurface(
|
private fun TouchSurface(
|
||||||
|
@ -131,7 +235,8 @@ private fun TouchSurface(
|
||||||
color: Color = Color.Transparent,
|
color: Color = Color.Transparent,
|
||||||
onDoubleTab: () -> Unit = {},
|
onDoubleTab: () -> Unit = {},
|
||||||
onRegularTap: () -> Unit = {},
|
onRegularTap: () -> Unit = {},
|
||||||
onMovement: (TouchedPosition) -> Unit = {}
|
onMovement: (TouchedPosition) -> Unit = {},
|
||||||
|
content: @Composable () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
var moveOccured by remember {
|
var moveOccured by remember {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
|
@ -194,6 +299,169 @@ private fun TouchSurface(
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
|
content()
|
||||||
Surface(color = color, modifier = Modifier.fillMaxSize()) {}
|
Surface(color = color, modifier = Modifier.fillMaxSize()) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FastSeekVisualFeedback(modifier: Modifier = Modifier, seconds: Int, backwards: Boolean) {
|
||||||
|
|
||||||
|
val contentDescription = String.format(
|
||||||
|
if (backwards) {
|
||||||
|
"Fast seeking backward by %d seconds."
|
||||||
|
//stringResource(id = R.string.fast_seeking_backward)
|
||||||
|
} else {
|
||||||
|
"Fast seeking forward by %d seconds."
|
||||||
|
//stringResource(id = R.string.fast_seeking_forward)
|
||||||
|
}, seconds
|
||||||
|
)
|
||||||
|
|
||||||
|
val infiniteTransition = rememberInfiniteTransition()
|
||||||
|
|
||||||
|
val animatedColor1 by infiniteTransition.animateColor(
|
||||||
|
initialValue = Color.White,
|
||||||
|
targetValue = Color.Transparent,
|
||||||
|
animationSpec = infiniteRepeatable(
|
||||||
|
animation = keyframes {
|
||||||
|
durationMillis = SEEK_ANIMATION_DURATION_IN_MS
|
||||||
|
Color.White.copy(alpha = 1f) at 0 with LinearEasing
|
||||||
|
Color.White.copy(alpha = 0f) at SEEK_ANIMATION_DURATION_IN_MS with LinearEasing
|
||||||
|
},
|
||||||
|
repeatMode = RepeatMode.Restart
|
||||||
|
), label = "Arrow1 animation"
|
||||||
|
)
|
||||||
|
|
||||||
|
val animatedColor2 by infiniteTransition.animateColor(
|
||||||
|
initialValue = Color.White,
|
||||||
|
targetValue = Color.Transparent,
|
||||||
|
animationSpec = infiniteRepeatable(
|
||||||
|
animation = keyframes {
|
||||||
|
durationMillis = SEEK_ANIMATION_DURATION_IN_MS
|
||||||
|
Color.White.copy(alpha = 1f / 3f) at 0 with LinearEasing
|
||||||
|
Color.White.copy(alpha = 0f) at SEEK_ANIMATION_DURATION_IN_MS / 3 with LinearEasing
|
||||||
|
Color.White.copy(alpha = 1f) at SEEK_ANIMATION_DURATION_IN_MS / 3 + 1 with LinearEasing
|
||||||
|
Color.White.copy(alpha = 2f / 3f) at SEEK_ANIMATION_DURATION_IN_MS with LinearEasing
|
||||||
|
},
|
||||||
|
repeatMode = RepeatMode.Restart
|
||||||
|
), label = "Arrow2 animation"
|
||||||
|
)
|
||||||
|
|
||||||
|
val animatedColor3 by infiniteTransition.animateColor(
|
||||||
|
initialValue = Color.White,
|
||||||
|
targetValue = Color.Transparent,
|
||||||
|
animationSpec = infiniteRepeatable(
|
||||||
|
animation = keyframes {
|
||||||
|
durationMillis = SEEK_ANIMATION_DURATION_IN_MS
|
||||||
|
Color.White.copy(alpha = 2f / 3f) at 0 with LinearEasing
|
||||||
|
Color.White.copy(alpha = 0f) at 2 * SEEK_ANIMATION_DURATION_IN_MS / 3 with LinearEasing
|
||||||
|
Color.White.copy(alpha = 1f) at 2 * SEEK_ANIMATION_DURATION_IN_MS / 3 + 1 with LinearEasing
|
||||||
|
Color.White.copy(alpha = 2f / 3f) at SEEK_ANIMATION_DURATION_IN_MS with LinearEasing
|
||||||
|
},
|
||||||
|
repeatMode = RepeatMode.Restart
|
||||||
|
), label = "Arrow3 animation"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
//val secondsString = stringResource(id = R.string.seconds)
|
||||||
|
val secondsString = "Seconds"
|
||||||
|
|
||||||
|
Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Row {
|
||||||
|
SeekerIcon(
|
||||||
|
backwards = backwards,
|
||||||
|
description = contentDescription,
|
||||||
|
color = if (backwards) animatedColor3 else animatedColor1
|
||||||
|
)
|
||||||
|
SeekerIcon(
|
||||||
|
backwards = backwards,
|
||||||
|
description = contentDescription,
|
||||||
|
color = animatedColor2
|
||||||
|
)
|
||||||
|
SeekerIcon(
|
||||||
|
backwards = backwards,
|
||||||
|
description = contentDescription,
|
||||||
|
color = if (backwards) animatedColor1 else animatedColor3
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text(text = "$seconds $secondsString")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SeekerIcon(backwards: Boolean, description: String, color: Color) {
|
||||||
|
Icon(
|
||||||
|
modifier = if (backwards) {
|
||||||
|
Modifier.scale(-1f, 1f)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
},
|
||||||
|
tint = color,
|
||||||
|
painter = painterResource(id = R.drawable.ic_play_seek_triangle),
|
||||||
|
contentDescription = description
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(device = "spec:width=1080px,height=600px,dpi=440,orientation=landscape")
|
||||||
|
@Composable
|
||||||
|
fun FullscreenGestureUIPreview() {
|
||||||
|
VideoPlayerTheme {
|
||||||
|
Surface(modifier = Modifier.wrapContentSize(), color = Color.Black) {
|
||||||
|
GestureUI(
|
||||||
|
modifier = Modifier,
|
||||||
|
hideUi = { },
|
||||||
|
showUi = { },
|
||||||
|
uiVissible = false,
|
||||||
|
fullscreen = true,
|
||||||
|
fastSeekSeconds = 10,
|
||||||
|
switchToFullscreen = { println("switch to fullscreen") },
|
||||||
|
switchToEmbeddedView = { println("switch to embedded") },
|
||||||
|
embeddedDraggedDownBy = { println("embedded dragged down") },
|
||||||
|
fastSeekBackward = { println("fast seek backward") },
|
||||||
|
fastSeekForward = { println("fast seek forward") })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(device = "spec:width=600px,height=400px,dpi=440,orientation=landscape")
|
||||||
|
@Composable
|
||||||
|
fun EmbeddedGestureUIPreview() {
|
||||||
|
VideoPlayerTheme {
|
||||||
|
Surface(modifier = Modifier.wrapContentSize(), color = Color.Black) {
|
||||||
|
GestureUI(
|
||||||
|
modifier = Modifier,
|
||||||
|
hideUi = { },
|
||||||
|
showUi = { },
|
||||||
|
uiVissible = false,
|
||||||
|
fullscreen = false,
|
||||||
|
fastSeekSeconds = 10,
|
||||||
|
switchToFullscreen = { println("switch to fullscreen") },
|
||||||
|
switchToEmbeddedView = { println("switch to embedded") },
|
||||||
|
embeddedDraggedDownBy = { println("embedded dragged down") },
|
||||||
|
fastSeekBackward = { println("fast seek backward") },
|
||||||
|
fastSeekForward = { println("fast seek forward") })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(device = "spec:width=1080px,height=600px,dpi=440,orientation=landscape")
|
||||||
|
@Composable
|
||||||
|
fun FastSeekVisualFeedbackPreviewBackwards() {
|
||||||
|
VideoPlayerTheme {
|
||||||
|
Surface(modifier = Modifier.wrapContentSize(), color = Color.Black) {
|
||||||
|
FastSeekVisualFeedback(seconds = 10, backwards = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(device = "spec:width=1080px,height=600px,dpi=440,orientation=landscape")
|
||||||
|
@Composable
|
||||||
|
fun FastSeekVisualFeedbackPreview() {
|
||||||
|
VideoPlayerTheme {
|
||||||
|
Surface(modifier = Modifier.wrapContentSize(), color = Color.Black) {
|
||||||
|
FastSeekVisualFeedback(seconds = 10, backwards = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,4 +35,7 @@
|
||||||
<string name="widget_description_toggle_fullscreen">Toggle fullscreen</string>
|
<string name="widget_description_toggle_fullscreen">Toggle fullscreen</string>
|
||||||
<string name="widget_description_chapter_selection">Chapter selection</string>
|
<string name="widget_description_chapter_selection">Chapter selection</string>
|
||||||
<string name="widget_descriptoin_playlist_item_selection">Playlist item selection</string>
|
<string name="widget_descriptoin_playlist_item_selection">Playlist item selection</string>
|
||||||
|
<string name="fast_seeking_backward">Fast seeking backward by %d seconds.</string>
|
||||||
|
<string name="fast_seeking_forward">Fast seeking forward by %d seconds.</string>
|
||||||
|
<string name="seconds">Seconds</string>
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in New Issue