make brightnes/volume indicators appear and disapear
This commit is contained in:
parent
c27f2685c8
commit
fb28aea8f8
|
@ -39,6 +39,8 @@ data class VideoPlayerUIState(
|
||||||
val durationInMs: Long,
|
val durationInMs: Long,
|
||||||
val playbackPositionInMs: Long,
|
val playbackPositionInMs: Long,
|
||||||
val fastseekSeconds: Int,
|
val fastseekSeconds: Int,
|
||||||
|
val soundVolume: Float,
|
||||||
|
val brightnes: Float
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
companion object {
|
companion object {
|
||||||
val DEFAULT = VideoPlayerUIState(
|
val DEFAULT = VideoPlayerUIState(
|
||||||
|
@ -54,7 +56,9 @@ data class VideoPlayerUIState(
|
||||||
isLoading = true,
|
isLoading = true,
|
||||||
durationInMs = 0,
|
durationInMs = 0,
|
||||||
playbackPositionInMs = 0,
|
playbackPositionInMs = 0,
|
||||||
fastseekSeconds = 0
|
fastseekSeconds = 0,
|
||||||
|
soundVolume = 0f,
|
||||||
|
brightnes = 0f
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -50,6 +50,8 @@ interface VideoPlayerViewModel {
|
||||||
fun embeddedDraggedDown(offset: Float)
|
fun embeddedDraggedDown(offset: Float)
|
||||||
fun fastSeek(count: Int)
|
fun fastSeek(count: Int)
|
||||||
fun finishFastSeek()
|
fun finishFastSeek()
|
||||||
|
fun brightnesChange(changeRate: Float)
|
||||||
|
fun volumeChange(changeRate: Float)
|
||||||
|
|
||||||
interface Listener {
|
interface Listener {
|
||||||
fun onFullscreenToggle(isFullscreen: Boolean) {}
|
fun onFullscreenToggle(isFullscreen: Boolean) {}
|
||||||
|
|
|
@ -291,6 +291,14 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun brightnesChange(changeRate: Float) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun volumeChange(changeRate: Float) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
override fun switchToEmbeddedView() {
|
override fun switchToEmbeddedView() {
|
||||||
callbackListeners.forEach { it?.onFullscreenToggle(false) }
|
callbackListeners.forEach { it?.onFullscreenToggle(false) }
|
||||||
uiVisibilityJob?.cancel()
|
uiVisibilityJob?.cancel()
|
||||||
|
@ -378,6 +386,14 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
||||||
println("dummy impl")
|
println("dummy impl")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun brightnesChange(changeRate: Float) {
|
||||||
|
println("dummy impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun volumeChange(changeRate: Float) {
|
||||||
|
println("dummy impl")
|
||||||
|
}
|
||||||
|
|
||||||
override fun pause() {
|
override fun pause() {
|
||||||
println("dummy pause")
|
println("dummy pause")
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,8 @@ fun VideoPlayerControllerUI(
|
||||||
playbackPositionInMs: Long,
|
playbackPositionInMs: Long,
|
||||||
bufferedPercentage: Float,
|
bufferedPercentage: Float,
|
||||||
fastSeekSeconds: Int,
|
fastSeekSeconds: Int,
|
||||||
|
soundVolume: Float,
|
||||||
|
brightnes: Float,
|
||||||
play: () -> Unit,
|
play: () -> Unit,
|
||||||
pause: () -> Unit,
|
pause: () -> Unit,
|
||||||
prevStream: () -> Unit,
|
prevStream: () -> Unit,
|
||||||
|
@ -75,7 +77,9 @@ fun VideoPlayerControllerUI(
|
||||||
seekingFinished: () -> Unit,
|
seekingFinished: () -> Unit,
|
||||||
embeddedDraggedDownBy: (Float) -> Unit,
|
embeddedDraggedDownBy: (Float) -> Unit,
|
||||||
fastSeek: (Int) -> Unit,
|
fastSeek: (Int) -> Unit,
|
||||||
finishFastSeek: () -> Unit
|
finishFastSeek: () -> Unit,
|
||||||
|
brightnesChange: (Float) -> Unit,
|
||||||
|
volumeChange: (Float) -> Unit
|
||||||
) {
|
) {
|
||||||
|
|
||||||
if (fullscreen) {
|
if (fullscreen) {
|
||||||
|
@ -99,11 +103,15 @@ fun VideoPlayerControllerUI(
|
||||||
uiVissible = uiVissible,
|
uiVissible = uiVissible,
|
||||||
fullscreen = fullscreen,
|
fullscreen = fullscreen,
|
||||||
fastSeekSeconds = fastSeekSeconds,
|
fastSeekSeconds = fastSeekSeconds,
|
||||||
|
brightnes = brightnes,
|
||||||
|
soundVolume = soundVolume,
|
||||||
switchToFullscreen = switchToFullscreen,
|
switchToFullscreen = switchToFullscreen,
|
||||||
switchToEmbeddedView = switchToEmbeddedView,
|
switchToEmbeddedView = switchToEmbeddedView,
|
||||||
embeddedDraggedDownBy = embeddedDraggedDownBy,
|
embeddedDraggedDownBy = embeddedDraggedDownBy,
|
||||||
fastSeek = fastSeek,
|
fastSeek = fastSeek,
|
||||||
fastSeekFinished = finishFastSeek
|
fastSeekFinished = finishFastSeek,
|
||||||
|
brightnesChange = brightnesChange,
|
||||||
|
volumeChange = volumeChange
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,11 +143,15 @@ fun VideoPlayerControllerUI(
|
||||||
uiVissible = uiVissible,
|
uiVissible = uiVissible,
|
||||||
fullscreen = fullscreen,
|
fullscreen = fullscreen,
|
||||||
fastSeekSeconds = fastSeekSeconds,
|
fastSeekSeconds = fastSeekSeconds,
|
||||||
|
soundVolume = soundVolume,
|
||||||
|
brightnes = brightnes,
|
||||||
switchToFullscreen = switchToFullscreen,
|
switchToFullscreen = switchToFullscreen,
|
||||||
switchToEmbeddedView = switchToEmbeddedView,
|
switchToEmbeddedView = switchToEmbeddedView,
|
||||||
embeddedDraggedDownBy = embeddedDraggedDownBy,
|
embeddedDraggedDownBy = embeddedDraggedDownBy,
|
||||||
fastSeek = fastSeek,
|
fastSeek = fastSeek,
|
||||||
fastSeekFinished = finishFastSeek
|
fastSeekFinished = finishFastSeek,
|
||||||
|
volumeChange = volumeChange,
|
||||||
|
brightnesChange = brightnesChange
|
||||||
)
|
)
|
||||||
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
@ -223,6 +235,8 @@ fun VideoPlayerControllerUIPreviewEmbedded() {
|
||||||
playbackPositionInMs = 6 * 60 * 1000,
|
playbackPositionInMs = 6 * 60 * 1000,
|
||||||
bufferedPercentage = 0.4f,
|
bufferedPercentage = 0.4f,
|
||||||
fastSeekSeconds = 0,
|
fastSeekSeconds = 0,
|
||||||
|
soundVolume = 0f,
|
||||||
|
brightnes = 0f,
|
||||||
play = {},
|
play = {},
|
||||||
pause = {},
|
pause = {},
|
||||||
prevStream = {},
|
prevStream = {},
|
||||||
|
@ -235,7 +249,9 @@ fun VideoPlayerControllerUIPreviewEmbedded() {
|
||||||
seekingFinished = {},
|
seekingFinished = {},
|
||||||
embeddedDraggedDownBy = {},
|
embeddedDraggedDownBy = {},
|
||||||
fastSeek = {},
|
fastSeek = {},
|
||||||
finishFastSeek = {})
|
finishFastSeek = {},
|
||||||
|
brightnesChange = {},
|
||||||
|
volumeChange = {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -254,6 +270,8 @@ fun VideoPlayerControllerUIPreviewLandscape() {
|
||||||
playbackPositionInMs = 6 * 60 * 1000,
|
playbackPositionInMs = 6 * 60 * 1000,
|
||||||
bufferedPercentage = 0.4f,
|
bufferedPercentage = 0.4f,
|
||||||
fastSeekSeconds = 0,
|
fastSeekSeconds = 0,
|
||||||
|
brightnes = 0f,
|
||||||
|
soundVolume = 0f,
|
||||||
play = {},
|
play = {},
|
||||||
pause = {},
|
pause = {},
|
||||||
prevStream = {},
|
prevStream = {},
|
||||||
|
@ -266,7 +284,9 @@ fun VideoPlayerControllerUIPreviewLandscape() {
|
||||||
seekingFinished = {},
|
seekingFinished = {},
|
||||||
embeddedDraggedDownBy = {},
|
embeddedDraggedDownBy = {},
|
||||||
fastSeek = {},
|
fastSeek = {},
|
||||||
finishFastSeek = {})
|
finishFastSeek = {},
|
||||||
|
brightnesChange = {},
|
||||||
|
volumeChange = {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -286,6 +306,8 @@ fun VideoPlayerControllerUIPreviewPortrait() {
|
||||||
playbackPositionInMs = 6 * 60 * 1000,
|
playbackPositionInMs = 6 * 60 * 1000,
|
||||||
bufferedPercentage = 0.4f,
|
bufferedPercentage = 0.4f,
|
||||||
fastSeekSeconds = 0,
|
fastSeekSeconds = 0,
|
||||||
|
brightnes = 0f,
|
||||||
|
soundVolume = 0f,
|
||||||
play = {},
|
play = {},
|
||||||
pause = {},
|
pause = {},
|
||||||
prevStream = {},
|
prevStream = {},
|
||||||
|
@ -298,7 +320,9 @@ fun VideoPlayerControllerUIPreviewPortrait() {
|
||||||
seekingFinished = {},
|
seekingFinished = {},
|
||||||
embeddedDraggedDownBy = {},
|
embeddedDraggedDownBy = {},
|
||||||
fastSeek = {},
|
fastSeek = {},
|
||||||
finishFastSeek = {})
|
finishFastSeek = {},
|
||||||
|
brightnesChange = {},
|
||||||
|
volumeChange = {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -153,6 +153,8 @@ fun VideoPlayerUI(
|
||||||
playbackPositionInMs = uiState.playbackPositionInMs,
|
playbackPositionInMs = uiState.playbackPositionInMs,
|
||||||
bufferedPercentage = uiState.bufferedPercentage,
|
bufferedPercentage = uiState.bufferedPercentage,
|
||||||
fastSeekSeconds = uiState.fastseekSeconds,
|
fastSeekSeconds = uiState.fastseekSeconds,
|
||||||
|
brightnes = uiState.brightnes,
|
||||||
|
soundVolume = uiState.soundVolume,
|
||||||
play = viewModel::play,
|
play = viewModel::play,
|
||||||
pause = viewModel::pause,
|
pause = viewModel::pause,
|
||||||
prevStream = viewModel::prevStream,
|
prevStream = viewModel::prevStream,
|
||||||
|
@ -165,7 +167,9 @@ fun VideoPlayerUI(
|
||||||
seekingFinished = viewModel::seekingFinished,
|
seekingFinished = viewModel::seekingFinished,
|
||||||
embeddedDraggedDownBy = viewModel::embeddedDraggedDown,
|
embeddedDraggedDownBy = viewModel::embeddedDraggedDown,
|
||||||
fastSeek = viewModel::fastSeek,
|
fastSeek = viewModel::fastSeek,
|
||||||
finishFastSeek = viewModel::finishFastSeek
|
finishFastSeek = viewModel::finishFastSeek,
|
||||||
|
volumeChange = viewModel::volumeChange,
|
||||||
|
brightnesChange = viewModel::brightnesChange
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,6 +84,8 @@ fun VideoPlayerControllerUIPreviewEmbeddedColorPreview() {
|
||||||
playbackPositionInMs = 6*60*1000,
|
playbackPositionInMs = 6*60*1000,
|
||||||
bufferedPercentage = 0.4f,
|
bufferedPercentage = 0.4f,
|
||||||
fastSeekSeconds = 10,
|
fastSeekSeconds = 10,
|
||||||
|
brightnes = 0f,
|
||||||
|
soundVolume = 0f,
|
||||||
play = {},
|
play = {},
|
||||||
pause = {},
|
pause = {},
|
||||||
prevStream = {},
|
prevStream = {},
|
||||||
|
@ -96,7 +98,9 @@ fun VideoPlayerControllerUIPreviewEmbeddedColorPreview() {
|
||||||
seekingFinished = {},
|
seekingFinished = {},
|
||||||
embeddedDraggedDownBy = {},
|
embeddedDraggedDownBy = {},
|
||||||
fastSeek = {},
|
fastSeek = {},
|
||||||
finishFastSeek = {})
|
finishFastSeek = {},
|
||||||
|
brightnesChange = {},
|
||||||
|
volumeChange = {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,10 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
|
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
|
||||||
|
import net.newpipe.newplayer.ui.videoplayer.gesture_ui.EmbeddedGestureUI
|
||||||
import net.newpipe.newplayer.ui.videoplayer.gesture_ui.FastSeekVisualFeedback
|
import net.newpipe.newplayer.ui.videoplayer.gesture_ui.FastSeekVisualFeedback
|
||||||
|
import net.newpipe.newplayer.ui.videoplayer.gesture_ui.FullscreenGestureUI
|
||||||
|
import net.newpipe.newplayer.ui.videoplayer.gesture_ui.FullscreenGestureUIPreview
|
||||||
import net.newpipe.newplayer.ui.videoplayer.gesture_ui.TouchSurface
|
import net.newpipe.newplayer.ui.videoplayer.gesture_ui.TouchSurface
|
||||||
import net.newpipe.newplayer.ui.videoplayer.gesture_ui.TouchedPosition
|
import net.newpipe.newplayer.ui.videoplayer.gesture_ui.TouchedPosition
|
||||||
|
|
||||||
|
@ -61,11 +64,15 @@ fun GestureUI(
|
||||||
uiVissible: Boolean,
|
uiVissible: Boolean,
|
||||||
fullscreen: Boolean,
|
fullscreen: Boolean,
|
||||||
fastSeekSeconds: Int,
|
fastSeekSeconds: Int,
|
||||||
|
brightnes: Float,
|
||||||
|
soundVolume: Float,
|
||||||
switchToFullscreen: () -> Unit,
|
switchToFullscreen: () -> Unit,
|
||||||
switchToEmbeddedView: () -> Unit,
|
switchToEmbeddedView: () -> Unit,
|
||||||
embeddedDraggedDownBy: (Float) -> Unit,
|
embeddedDraggedDownBy: (Float) -> Unit,
|
||||||
fastSeek: (Int) -> Unit,
|
fastSeek: (Int) -> Unit,
|
||||||
fastSeekFinished: () -> Unit
|
fastSeekFinished: () -> Unit,
|
||||||
|
volumeChange: (Float) -> Unit,
|
||||||
|
brightnesChange: (Float) -> Unit,
|
||||||
) {
|
) {
|
||||||
val defaultOnRegularTap = {
|
val defaultOnRegularTap = {
|
||||||
if (uiVissible) {
|
if (uiVissible) {
|
||||||
|
@ -76,227 +83,27 @@ fun GestureUI(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fullscreen) {
|
if (fullscreen) {
|
||||||
Row(modifier = modifier) {
|
FullscreenGestureUI(
|
||||||
TouchSurface(
|
uiVissible = uiVissible,
|
||||||
modifier = Modifier
|
fastSeekSeconds = fastSeekSeconds,
|
||||||
.weight(1f),
|
hideUi = hideUi,
|
||||||
multitapDurationInMs = FAST_SEEKMODE_DURATION,
|
showUi = showUi,
|
||||||
onRegularTap = defaultOnRegularTap,
|
fastSeek = fastSeek,
|
||||||
onMultiTap = {
|
brightnes = brightnes,
|
||||||
println("multitap ${-it}")
|
volume = soundVolume,
|
||||||
fastSeek(-it)
|
switchToEmbeddedView = switchToEmbeddedView,
|
||||||
},
|
fastSeekFinished = fastSeekFinished,
|
||||||
onMultiTapFinished = fastSeekFinished
|
volumeChange = volumeChange,
|
||||||
) {
|
brightnesChange = brightnesChange)
|
||||||
FadedAnimationForSeekFeedback(fastSeekSeconds, backwards = true) { fastSeekSecondsToDisplay ->
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
|
||||||
FastSeekVisualFeedback(
|
|
||||||
seconds = -fastSeekSecondsToDisplay,
|
|
||||||
backwards = true,
|
|
||||||
modifier = Modifier.align(Alignment.CenterEnd)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TouchSurface(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f),
|
|
||||||
onRegularTap = defaultOnRegularTap,
|
|
||||||
multitapDurationInMs = FAST_SEEKMODE_DURATION,
|
|
||||||
onMovement = { movement ->
|
|
||||||
if (0 < movement.y) {
|
|
||||||
switchToEmbeddedView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
TouchSurface(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f),
|
|
||||||
onRegularTap = defaultOnRegularTap,
|
|
||||||
multitapDurationInMs = FAST_SEEKMODE_DURATION,
|
|
||||||
onMultiTap = fastSeek,
|
|
||||||
onMultiTapFinished = fastSeekFinished
|
|
||||||
) {
|
|
||||||
FadedAnimationForSeekFeedback(fastSeekSeconds) { fastSeekSecondsToDisplay ->
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
|
||||||
FastSeekVisualFeedback(
|
|
||||||
modifier = Modifier.align(Alignment.CenterStart),
|
|
||||||
seconds = fastSeekSecondsToDisplay,
|
|
||||||
backwards = false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else { // (!fullscreen)
|
|
||||||
val handleDownwardMovement = { movement: TouchedPosition ->
|
|
||||||
Log.d(TAG, "${movement.x}:${movement.y}")
|
|
||||||
if (0 < movement.y) {
|
|
||||||
embeddedDraggedDownBy(movement.y)
|
|
||||||
} else {
|
|
||||||
switchToFullscreen()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(modifier = modifier) {
|
|
||||||
TouchSurface(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f),
|
|
||||||
multitapDurationInMs = FAST_SEEKMODE_DURATION,
|
|
||||||
onRegularTap = defaultOnRegularTap,
|
|
||||||
onMultiTap = {
|
|
||||||
fastSeek(-it)
|
|
||||||
},
|
|
||||||
onMultiTapFinished = fastSeekFinished,
|
|
||||||
onMovement = handleDownwardMovement
|
|
||||||
) {
|
|
||||||
FadedAnimationForSeekFeedback(fastSeekSeconds, backwards = true) { fastSeekSecondsToDisplay ->
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
|
||||||
FastSeekVisualFeedback(
|
|
||||||
modifier = Modifier.align(Alignment.Center),
|
|
||||||
seconds = -fastSeekSecondsToDisplay,
|
|
||||||
backwards = true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TouchSurface(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f),
|
|
||||||
multitapDurationInMs = FAST_SEEKMODE_DURATION,
|
|
||||||
onRegularTap = defaultOnRegularTap,
|
|
||||||
onMovement = handleDownwardMovement,
|
|
||||||
onMultiTap = fastSeek,
|
|
||||||
onMultiTapFinished = fastSeekFinished
|
|
||||||
) {
|
|
||||||
FadedAnimationForSeekFeedback(fastSeekSeconds) { fastSeekSecondsToDisplay ->
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
|
||||||
FastSeekVisualFeedback(
|
|
||||||
modifier = Modifier.align(Alignment.Center),
|
|
||||||
seconds = fastSeekSecondsToDisplay,
|
|
||||||
backwards = false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun FadedAnimationForSeekFeedback(
|
|
||||||
fastSeekSeconds: Int,
|
|
||||||
backwards: Boolean = false,
|
|
||||||
content: @Composable (fastSeekSecondsToDisplay:Int) -> Unit
|
|
||||||
) {
|
|
||||||
|
|
||||||
var lastSecondsValue by remember {
|
|
||||||
mutableStateOf(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
val vissible = if (backwards) {
|
|
||||||
fastSeekSeconds < 0
|
|
||||||
} else {
|
} else {
|
||||||
0 < fastSeekSeconds
|
EmbeddedGestureUI(
|
||||||
|
fastSeekSeconds = fastSeekSeconds,
|
||||||
|
uiVissible = uiVissible,
|
||||||
|
switchToFullscreen = switchToFullscreen,
|
||||||
|
embeddedDraggedDownBy = embeddedDraggedDownBy,
|
||||||
|
fastSeek = fastSeek,
|
||||||
|
fastSeekFinished = fastSeekFinished,
|
||||||
|
hideUi = hideUi,
|
||||||
|
showUi = showUi)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
val disapearEmediatly = if (backwards) {
|
|
||||||
0 < fastSeekSeconds
|
|
||||||
} else {
|
|
||||||
fastSeekSeconds < 0
|
|
||||||
}
|
|
||||||
|
|
||||||
val valueToDisplay = if(vissible) {
|
|
||||||
lastSecondsValue = fastSeekSeconds
|
|
||||||
fastSeekSeconds
|
|
||||||
} else {
|
|
||||||
lastSecondsValue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!disapearEmediatly) {
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = vissible,
|
|
||||||
enter = fadeIn(animationSpec = tween(SEEK_ANIMATION_FADE_IN)),
|
|
||||||
exit = fadeOut(
|
|
||||||
animationSpec = tween(SEEK_ANIMATION_FADE_OUT)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
content(valueToDisplay)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Preview(device = "spec:width=1080px,height=600px,dpi=440,orientation=landscape")
|
|
||||||
@Composable
|
|
||||||
fun FullscreenGestureUIPreview() {
|
|
||||||
VideoPlayerTheme {
|
|
||||||
Surface(modifier = Modifier.wrapContentSize(), color = Color.DarkGray) {
|
|
||||||
GestureUI(
|
|
||||||
modifier = Modifier,
|
|
||||||
hideUi = { },
|
|
||||||
showUi = { },
|
|
||||||
uiVissible = false,
|
|
||||||
fullscreen = true,
|
|
||||||
fastSeekSeconds = 0,
|
|
||||||
switchToFullscreen = { println("switch to fullscreen") },
|
|
||||||
switchToEmbeddedView = { println("switch to embedded") },
|
|
||||||
embeddedDraggedDownBy = { println("embedded dragged down") },
|
|
||||||
fastSeek = { println("fast seek by $it steps") },
|
|
||||||
fastSeekFinished = {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(device = "spec:width=1080px,height=600px,dpi=440,orientation=landscape")
|
|
||||||
@Composable
|
|
||||||
fun FullscreenGestureUIPreviewInteractive() {
|
|
||||||
|
|
||||||
var seekSeconds by remember {
|
|
||||||
mutableStateOf(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
VideoPlayerTheme {
|
|
||||||
Surface(modifier = Modifier.wrapContentSize(), color = Color.Black) {
|
|
||||||
GestureUI(
|
|
||||||
modifier = Modifier,
|
|
||||||
hideUi = { },
|
|
||||||
showUi = { },
|
|
||||||
uiVissible = false,
|
|
||||||
fullscreen = true,
|
|
||||||
fastSeekSeconds = seekSeconds,
|
|
||||||
switchToFullscreen = { println("switch to fullscreen") },
|
|
||||||
switchToEmbeddedView = { println("switch to embedded") },
|
|
||||||
embeddedDraggedDownBy = { println("embedded dragged down") },
|
|
||||||
fastSeek = { seekSeconds = it * 10 },
|
|
||||||
fastSeekFinished = {
|
|
||||||
seekSeconds = 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Preview(device = "spec:width=600px,height=400px,dpi=440,orientation=landscape")
|
|
||||||
@Composable
|
|
||||||
fun EmbeddedGestureUIPreview() {
|
|
||||||
VideoPlayerTheme {
|
|
||||||
Surface(modifier = Modifier.wrapContentSize(), color = Color.DarkGray) {
|
|
||||||
GestureUI(
|
|
||||||
modifier = Modifier,
|
|
||||||
hideUi = { },
|
|
||||||
showUi = { },
|
|
||||||
uiVissible = false,
|
|
||||||
fullscreen = false,
|
|
||||||
fastSeekSeconds = 0,
|
|
||||||
switchToFullscreen = { println("switch to fullscreen") },
|
|
||||||
switchToEmbeddedView = { println("switch to embedded") },
|
|
||||||
embeddedDraggedDownBy = { println("embedded dragged down") },
|
|
||||||
fastSeek = { println("Fast seek by $it steps") },
|
|
||||||
fastSeekFinished = {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
/* NewPlayer
|
||||||
|
*
|
||||||
|
* @author Christian Schabesberger
|
||||||
|
*
|
||||||
|
* Copyright (C) NewPipe e.V. 2024 <code(at)newpipe-ev.de>
|
||||||
|
*
|
||||||
|
* NewPlayer is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* NewPlayer is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with NewPlayer. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package net.newpipe.newplayer.ui.videoplayer.gesture_ui
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
|
||||||
|
import net.newpipe.newplayer.ui.videoplayer.FAST_SEEKMODE_DURATION
|
||||||
|
|
||||||
|
private const val TAG = "EmbeddedGestureUI"
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun EmbeddedGestureUI(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
fastSeekSeconds: Int,
|
||||||
|
uiVissible: Boolean,
|
||||||
|
switchToFullscreen: () -> Unit,
|
||||||
|
embeddedDraggedDownBy: (Float) -> Unit,
|
||||||
|
fastSeek: (Int) -> Unit,
|
||||||
|
fastSeekFinished: () -> Unit,
|
||||||
|
hideUi: () -> Unit,
|
||||||
|
showUi: () -> Unit
|
||||||
|
) {
|
||||||
|
|
||||||
|
val handleDownwardMovement = { movement: TouchedPosition ->
|
||||||
|
Log.d(TAG, "${movement.x}:${movement.y}")
|
||||||
|
if (0 < movement.y) {
|
||||||
|
embeddedDraggedDownBy(movement.y)
|
||||||
|
} else {
|
||||||
|
switchToFullscreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val defaultOnRegularTap = {
|
||||||
|
if (uiVissible) {
|
||||||
|
hideUi()
|
||||||
|
} else {
|
||||||
|
showUi()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(modifier = modifier) {
|
||||||
|
TouchSurface(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f),
|
||||||
|
multitapDurationInMs = FAST_SEEKMODE_DURATION,
|
||||||
|
onRegularTap = defaultOnRegularTap,
|
||||||
|
onMultiTap = {
|
||||||
|
fastSeek(-it)
|
||||||
|
},
|
||||||
|
onMultiTapFinished = fastSeekFinished,
|
||||||
|
onMovement = handleDownwardMovement
|
||||||
|
) {
|
||||||
|
FadedAnimationForSeekFeedback(
|
||||||
|
fastSeekSeconds,
|
||||||
|
backwards = true
|
||||||
|
) { fastSeekSecondsToDisplay ->
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
FastSeekVisualFeedback(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
seconds = -fastSeekSecondsToDisplay,
|
||||||
|
backwards = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TouchSurface(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f),
|
||||||
|
multitapDurationInMs = FAST_SEEKMODE_DURATION,
|
||||||
|
onRegularTap = defaultOnRegularTap,
|
||||||
|
onMovement = handleDownwardMovement,
|
||||||
|
onMultiTap = fastSeek,
|
||||||
|
onMultiTapFinished = fastSeekFinished
|
||||||
|
) {
|
||||||
|
FadedAnimationForSeekFeedback(fastSeekSeconds) { fastSeekSecondsToDisplay ->
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
FastSeekVisualFeedback(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
seconds = fastSeekSecondsToDisplay,
|
||||||
|
backwards = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Preview(device = "spec:width=600px,height=400px,dpi=440,orientation=landscape")
|
||||||
|
@Composable
|
||||||
|
fun EmbeddedGestureUIPreview() {
|
||||||
|
VideoPlayerTheme {
|
||||||
|
Surface(modifier = Modifier.wrapContentSize(), color = Color.DarkGray) {
|
||||||
|
EmbeddedGestureUI(
|
||||||
|
modifier = Modifier,
|
||||||
|
hideUi = { },
|
||||||
|
showUi = { },
|
||||||
|
uiVissible = false,
|
||||||
|
fastSeekSeconds = 0,
|
||||||
|
switchToFullscreen = { println("switch to fullscreen") },
|
||||||
|
embeddedDraggedDownBy = { println("embedded dragged down") },
|
||||||
|
fastSeek = { println("Fast seek by $it steps") },
|
||||||
|
fastSeekFinished = {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -20,12 +20,16 @@
|
||||||
|
|
||||||
package net.newpipe.newplayer.ui.videoplayer.gesture_ui
|
package net.newpipe.newplayer.ui.videoplayer.gesture_ui
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.animateColor
|
import androidx.compose.animation.animateColor
|
||||||
import androidx.compose.animation.core.LinearEasing
|
import androidx.compose.animation.core.LinearEasing
|
||||||
import androidx.compose.animation.core.RepeatMode
|
import androidx.compose.animation.core.RepeatMode
|
||||||
import androidx.compose.animation.core.infiniteRepeatable
|
import androidx.compose.animation.core.infiniteRepeatable
|
||||||
import androidx.compose.animation.core.keyframes
|
import androidx.compose.animation.core.keyframes
|
||||||
import androidx.compose.animation.core.rememberInfiniteTransition
|
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.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
@ -37,29 +41,35 @@ import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
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.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.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.scale
|
import androidx.compose.ui.draw.scale
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
import net.newpipe.newplayer.R
|
import net.newpipe.newplayer.R
|
||||||
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
|
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
|
||||||
import net.newpipe.newplayer.ui.videoplayer.SEEK_ANIMATION_DURATION_IN_MS
|
import net.newpipe.newplayer.ui.videoplayer.SEEK_ANIMATION_DURATION_IN_MS
|
||||||
|
import net.newpipe.newplayer.ui.videoplayer.SEEK_ANIMATION_FADE_IN
|
||||||
|
import net.newpipe.newplayer.ui.videoplayer.SEEK_ANIMATION_FADE_OUT
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FastSeekVisualFeedback(modifier: Modifier = Modifier, seconds: Int, backwards: Boolean) {
|
fun FastSeekVisualFeedback(modifier: Modifier = Modifier, seconds: Int, backwards: Boolean) {
|
||||||
|
|
||||||
val contentDescription = String.format(
|
val contentDescription = String.format(
|
||||||
if (backwards) {
|
if (backwards) {
|
||||||
"Fast seeking backward by %d seconds."
|
//"Fast seeking backward by %d seconds."
|
||||||
//stringResource(id = R.string.fast_seeking_backward)
|
stringResource(id = R.string.fast_seeking_backward)
|
||||||
} else {
|
} else {
|
||||||
"Fast seeking forward by %d seconds."
|
//"Fast seeking forward by %d seconds."
|
||||||
//stringResource(id = R.string.fast_seeking_forward)
|
stringResource(id = R.string.fast_seeking_forward)
|
||||||
}, seconds
|
}, seconds
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -142,6 +152,49 @@ fun FastSeekVisualFeedback(modifier: Modifier = Modifier, seconds: Int, backward
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FadedAnimationForSeekFeedback(
|
||||||
|
fastSeekSeconds: Int,
|
||||||
|
backwards: Boolean = false,
|
||||||
|
content: @Composable (fastSeekSecondsToDisplay:Int) -> Unit
|
||||||
|
) {
|
||||||
|
|
||||||
|
var lastSecondsValue by remember {
|
||||||
|
mutableStateOf(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
val vissible = if (backwards) {
|
||||||
|
fastSeekSeconds < 0
|
||||||
|
} else {
|
||||||
|
0 < fastSeekSeconds
|
||||||
|
}
|
||||||
|
|
||||||
|
val disapearEmediatly = if (backwards) {
|
||||||
|
0 < fastSeekSeconds
|
||||||
|
} else {
|
||||||
|
fastSeekSeconds < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
val valueToDisplay = if(vissible) {
|
||||||
|
lastSecondsValue = fastSeekSeconds
|
||||||
|
fastSeekSeconds
|
||||||
|
} else {
|
||||||
|
lastSecondsValue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!disapearEmediatly) {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = vissible,
|
||||||
|
enter = fadeIn(animationSpec = tween(SEEK_ANIMATION_FADE_IN)),
|
||||||
|
exit = fadeOut(
|
||||||
|
animationSpec = tween(SEEK_ANIMATION_FADE_OUT)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
content(valueToDisplay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SeekerIcon(backwards: Boolean, description: String, color: Color) {
|
private fun SeekerIcon(backwards: Boolean, description: String, color: Color) {
|
||||||
Icon(
|
Icon(
|
||||||
|
|
|
@ -0,0 +1,241 @@
|
||||||
|
/* NewPlayer
|
||||||
|
*
|
||||||
|
* @author Christian Schabesberger
|
||||||
|
*
|
||||||
|
* Copyright (C) NewPipe e.V. 2024 <code(at)newpipe-ev.de>
|
||||||
|
*
|
||||||
|
* NewPlayer is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* NewPlayer is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with NewPlayer. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package net.newpipe.newplayer.ui.videoplayer.gesture_ui
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.scaleIn
|
||||||
|
import androidx.compose.animation.scaleOut
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
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.Color
|
||||||
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
|
||||||
|
import net.newpipe.newplayer.ui.videoplayer.FAST_SEEKMODE_DURATION
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FullscreenGestureUI(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
uiVissible: Boolean,
|
||||||
|
fastSeekSeconds: Int,
|
||||||
|
volume: Float,
|
||||||
|
brightnes: Float,
|
||||||
|
hideUi: () -> Unit,
|
||||||
|
showUi: () -> Unit,
|
||||||
|
fastSeek: (Int) -> Unit,
|
||||||
|
fastSeekFinished: () -> Unit,
|
||||||
|
switchToEmbeddedView: () -> Unit,
|
||||||
|
volumeChange: (Float) -> Unit,
|
||||||
|
brightnesChange: (Float) -> Unit
|
||||||
|
) {
|
||||||
|
val defaultOnRegularTap = {
|
||||||
|
if (uiVissible) {
|
||||||
|
hideUi()
|
||||||
|
} else {
|
||||||
|
showUi()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var heightPx by remember {
|
||||||
|
mutableStateOf(0f)
|
||||||
|
}
|
||||||
|
|
||||||
|
var volumeIndicatorVissible by remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
var brightnesIndicatorVissible by remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(modifier = modifier.onGloballyPositioned { coordinates ->
|
||||||
|
heightPx = coordinates.size.height.toFloat()
|
||||||
|
}) {
|
||||||
|
Row {
|
||||||
|
TouchSurface(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f),
|
||||||
|
multitapDurationInMs = FAST_SEEKMODE_DURATION,
|
||||||
|
onRegularTap = defaultOnRegularTap,
|
||||||
|
onMultiTap = {
|
||||||
|
println("multitap ${-it}")
|
||||||
|
fastSeek(-it)
|
||||||
|
},
|
||||||
|
onMultiTapFinished = fastSeekFinished,
|
||||||
|
onUp = {
|
||||||
|
brightnesIndicatorVissible = false
|
||||||
|
},
|
||||||
|
onMovement = {change ->
|
||||||
|
brightnesIndicatorVissible = true
|
||||||
|
if (heightPx != 0f) {
|
||||||
|
brightnesChange(-change.y / heightPx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
FadedAnimationForSeekFeedback(
|
||||||
|
fastSeekSeconds,
|
||||||
|
backwards = true
|
||||||
|
) { fastSeekSecondsToDisplay ->
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
FastSeekVisualFeedback(
|
||||||
|
seconds = -fastSeekSecondsToDisplay,
|
||||||
|
backwards = true,
|
||||||
|
modifier = Modifier.align(Alignment.CenterEnd)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TouchSurface(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f),
|
||||||
|
onRegularTap = defaultOnRegularTap,
|
||||||
|
multitapDurationInMs = FAST_SEEKMODE_DURATION,
|
||||||
|
onMovement = { movement ->
|
||||||
|
if (0 < movement.y) {
|
||||||
|
switchToEmbeddedView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
TouchSurface(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f),
|
||||||
|
onRegularTap = defaultOnRegularTap,
|
||||||
|
multitapDurationInMs = FAST_SEEKMODE_DURATION,
|
||||||
|
onMultiTap = fastSeek,
|
||||||
|
onMultiTapFinished = fastSeekFinished,
|
||||||
|
onUp = {
|
||||||
|
volumeIndicatorVissible = false
|
||||||
|
},
|
||||||
|
onMovement = { change ->
|
||||||
|
volumeIndicatorVissible = true
|
||||||
|
if (heightPx != 0f) {
|
||||||
|
volumeChange(-change.y / heightPx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
FadedAnimationForSeekFeedback(fastSeekSeconds) { fastSeekSecondsToDisplay ->
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
FastSeekVisualFeedback(
|
||||||
|
modifier = Modifier.align(Alignment.CenterStart),
|
||||||
|
seconds = fastSeekSecondsToDisplay,
|
||||||
|
backwards = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnimatedVisibility(modifier = Modifier.align(Alignment.Center),
|
||||||
|
visible = volumeIndicatorVissible,
|
||||||
|
enter = scaleIn(initialScale = 0.95f, animationSpec = tween(100)),
|
||||||
|
exit = scaleOut(targetScale = 0.95f, animationSpec = tween(100))) {
|
||||||
|
VolumeCircle(volumeFraction = volume)
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(modifier = Modifier.align(Alignment.Center),
|
||||||
|
visible = brightnesIndicatorVissible,
|
||||||
|
enter = scaleIn(initialScale = 0.95f, animationSpec = tween(100)),
|
||||||
|
exit = scaleOut(targetScale = 0.95f, animationSpec = tween(100))) {
|
||||||
|
VolumeCircle(
|
||||||
|
volumeFraction = brightnes,
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
isBrightnes = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Preview(device = "spec:width=1080px,height=600px,dpi=440,orientation=landscape")
|
||||||
|
@Composable
|
||||||
|
fun FullscreenGestureUIPreview() {
|
||||||
|
VideoPlayerTheme {
|
||||||
|
Surface(modifier = Modifier.wrapContentSize(), color = Color.DarkGray) {
|
||||||
|
FullscreenGestureUI(
|
||||||
|
modifier = Modifier,
|
||||||
|
hideUi = { },
|
||||||
|
showUi = { },
|
||||||
|
uiVissible = false,
|
||||||
|
fastSeekSeconds = 0,
|
||||||
|
volume = 0f,
|
||||||
|
brightnes = 0f,
|
||||||
|
fastSeek = { println("fast seek by $it steps") },
|
||||||
|
fastSeekFinished = {},
|
||||||
|
switchToEmbeddedView = {},
|
||||||
|
brightnesChange = {},
|
||||||
|
volumeChange = {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(device = "spec:width=1080px,height=600px,dpi=440,orientation=landscape")
|
||||||
|
@Composable
|
||||||
|
fun FullscreenGestureUIPreviewInteractive() {
|
||||||
|
|
||||||
|
var seekSeconds by remember {
|
||||||
|
mutableStateOf(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
var brightnesValue by remember {
|
||||||
|
mutableStateOf(0f)
|
||||||
|
}
|
||||||
|
|
||||||
|
var soundVolume by remember {
|
||||||
|
mutableStateOf(0f)
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoPlayerTheme {
|
||||||
|
Surface(modifier = Modifier.wrapContentSize(), color = Color.Gray) {
|
||||||
|
FullscreenGestureUI(
|
||||||
|
modifier = Modifier,
|
||||||
|
hideUi = { },
|
||||||
|
showUi = { },
|
||||||
|
uiVissible = false,
|
||||||
|
fastSeekSeconds = seekSeconds,
|
||||||
|
volume = soundVolume,
|
||||||
|
brightnes = brightnesValue,
|
||||||
|
fastSeek = { seekSeconds = it * 10 },
|
||||||
|
fastSeekFinished = {
|
||||||
|
seekSeconds = 0
|
||||||
|
},
|
||||||
|
switchToEmbeddedView = {},
|
||||||
|
brightnesChange = {
|
||||||
|
brightnesValue = (brightnesValue + it).coerceIn(0f, 1f)
|
||||||
|
},
|
||||||
|
volumeChange = {
|
||||||
|
soundVolume = (soundVolume + it).coerceIn(0f, 1f)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,6 +47,7 @@ fun TouchSurface(
|
||||||
onMultiTap: (Int) -> Unit = {},
|
onMultiTap: (Int) -> Unit = {},
|
||||||
onMultiTapFinished: () -> Unit = {},
|
onMultiTapFinished: () -> Unit = {},
|
||||||
onRegularTap: () -> Unit = {},
|
onRegularTap: () -> Unit = {},
|
||||||
|
onUp: () -> Unit = {},
|
||||||
onMovement: (TouchedPosition) -> Unit = {},
|
onMovement: (TouchedPosition) -> Unit = {},
|
||||||
content: @Composable () -> Unit = {}
|
content: @Composable () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
|
@ -82,6 +83,7 @@ fun TouchSurface(
|
||||||
}
|
}
|
||||||
|
|
||||||
val defaultActionUp = { onMultiTap: (Int) -> Unit, onRegularTap: () -> Unit ->
|
val defaultActionUp = { onMultiTap: (Int) -> Unit, onRegularTap: () -> Unit ->
|
||||||
|
onUp()
|
||||||
val currentTime = System.currentTimeMillis()
|
val currentTime = System.currentTimeMillis()
|
||||||
if (!moveOccured) {
|
if (!moveOccured) {
|
||||||
val timeSinceLastTouch = currentTime - lastTouchTime
|
val timeSinceLastTouch = currentTime - lastTouchTime
|
||||||
|
|
|
@ -57,12 +57,19 @@ private const val LINE_STROKE_WIDTH = 4
|
||||||
private const val CIRCLE_SIZE = 100
|
private const val CIRCLE_SIZE = 100
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun VolumeCircle(modifier: Modifier = Modifier, volumeFraction: Float, isBrightnes: Boolean = false) {
|
fun VolumeCircle(
|
||||||
assert(0f < volumeFraction && volumeFraction < 1f) {
|
modifier: Modifier = Modifier,
|
||||||
Log.e(TAG, "Volume fraction must be in ragne [0;1]. It was $volumeFraction")
|
volumeFraction: Float,
|
||||||
|
isBrightnes: Boolean = false
|
||||||
|
) {
|
||||||
|
assert(0f <= volumeFraction && volumeFraction <= 1f) {
|
||||||
|
Log.e(TAG, "Volume fraction must be in ragne [0;1]. It was $volumeFraction")
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(modifier.shadow(elevation = 1.dp, shape = CircleShape).padding(2.dp)){
|
Box(
|
||||||
|
modifier
|
||||||
|
.shadow(elevation = 1.dp, shape = CircleShape)
|
||||||
|
.padding(2.dp)) {
|
||||||
Canvas(Modifier.size(CIRCLE_SIZE.dp)) {
|
Canvas(Modifier.size(CIRCLE_SIZE.dp)) {
|
||||||
val arcSize = (CIRCLE_SIZE - LINE_STROKE_WIDTH).dp.toPx();
|
val arcSize = (CIRCLE_SIZE - LINE_STROKE_WIDTH).dp.toPx();
|
||||||
drawCircle(color = Color.Black.copy(alpha = 0.3f), radius = (CIRCLE_SIZE / 2).dp.toPx())
|
drawCircle(color = Color.Black.copy(alpha = 0.3f), radius = (CIRCLE_SIZE / 2).dp.toPx())
|
||||||
|
@ -80,7 +87,9 @@ fun VolumeCircle(modifier: Modifier = Modifier, volumeFraction: Float, isBrightn
|
||||||
}
|
}
|
||||||
|
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.align(Alignment.Center).size(60.dp),
|
modifier = Modifier
|
||||||
|
.align(Alignment.Center)
|
||||||
|
.size(60.dp),
|
||||||
imageVector = (if (isBrightnes) getBrightnesIcon(volumeFraction = volumeFraction)
|
imageVector = (if (isBrightnes) getBrightnesIcon(volumeFraction = volumeFraction)
|
||||||
else getVolumeIcon(volumeFraction = volumeFraction)),
|
else getVolumeIcon(volumeFraction = volumeFraction)),
|
||||||
contentDescription = stringResource(
|
contentDescription = stringResource(
|
||||||
|
|
Loading…
Reference in New Issue