advance touch ui: make fullscreen and and embedded view switch possible

This commit is contained in:
Christian Schabesberger 2024-08-05 18:07:17 +02:00
parent ce6ef8a8fd
commit 07a6b0a03f
6 changed files with 230 additions and 30 deletions

View File

@ -47,8 +47,13 @@ interface VideoPlayerViewModel {
fun hideUi() fun hideUi()
fun seekPositionChanged(newValue: Float) fun seekPositionChanged(newValue: Float)
fun seekingFinished() fun seekingFinished()
fun embeddedDraggedDown(offset: Float)
fun fastSeekForward()
fun fastSeekBackward()
interface Listener { interface Listener {
fun onFullscreenToggle(isFullscreen: Boolean) fun onFullscreenToggle(isFullscreen: Boolean) {}
fun embeddedPlayerDraggedDown(offset: Float) {}
} }
} }

View File

@ -263,6 +263,18 @@ class VideoPlayerViewModelImpl @Inject constructor(
Log.i(TAG, "Seek to Ms: $seekPositionInMs") Log.i(TAG, "Seek to Ms: $seekPositionInMs")
} }
override fun embeddedDraggedDown(offset: Float) {
callbackListeners.forEach { it?.embeddedPlayerDraggedDown(offset) }
}
override fun fastSeekForward() {
newPlayer?.fastSeekForward()
}
override fun fastSeekBackward() {
newPlayer?.fastSeekBackward()
}
override fun switchToEmbeddedView() { override fun switchToEmbeddedView() {
callbackListeners.forEach { it?.onFullscreenToggle(false) } callbackListeners.forEach { it?.onFullscreenToggle(false) }
uiVisibilityJob?.cancel() uiVisibilityJob?.cancel()
@ -330,13 +342,25 @@ class VideoPlayerViewModelImpl @Inject constructor(
} }
override fun seekPositionChanged(newValue: Float) { override fun seekPositionChanged(newValue: Float) {
println("dummy impl") println("dymmy seekPositionChanged: newValue: ${newValue}")
} }
override fun seekingFinished() { override fun seekingFinished() {
println("dummy impl") println("dummy impl")
} }
override fun embeddedDraggedDown(offset: Float) {
println("dymmy embeddedDraggedDown: offset: ${offset}")
}
override fun fastSeekForward() {
println("dummy impl")
}
override fun fastSeekBackward() {
println("dummy impl")
}
override fun pause() { override fun pause() {
println("dummy pause") println("dummy pause")
} }

View File

@ -71,7 +71,10 @@ fun VideoPlayerControllerUI(
showUi: () -> Unit, showUi: () -> Unit,
hideUi: () -> Unit, hideUi: () -> Unit,
seekPositionChanged: (Float) -> Unit, seekPositionChanged: (Float) -> Unit,
seekingFinished: () -> Unit seekingFinished: () -> Unit,
embeddedDraggedDownBy: (Float) -> Unit,
fastSeekBackward: () -> Unit,
fastSeekForward: () -> Unit,
) { ) {
if (fullscreen) { if (fullscreen) {
@ -88,12 +91,17 @@ fun VideoPlayerControllerUI(
if (!uiVissible) { if (!uiVissible) {
TouchUi( TouchUi(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize(),
.windowInsetsPadding(WindowInsets.systemGestures), // .windowInsetsPadding(WindowInsets.systemGestures),
hideUi = hideUi, hideUi = hideUi,
showUi = showUi, showUi = showUi,
uiVissible = uiVissible, uiVissible = uiVissible,
fullscreen = fullscreen fullscreen = fullscreen,
switchToFullscreen = switchToFullscreen,
switchToEmbeddedView = switchToEmbeddedView,
embeddedDraggedDownBy = embeddedDraggedDownBy,
fastSeekForward = fastSeekForward,
fastSeekBackward = fastSeekBackward
) )
} }
@ -123,7 +131,12 @@ fun VideoPlayerControllerUI(
hideUi = hideUi, hideUi = hideUi,
showUi = showUi, showUi = showUi,
uiVissible = uiVissible, uiVissible = uiVissible,
fullscreen = fullscreen fullscreen = fullscreen,
switchToFullscreen = switchToFullscreen,
switchToEmbeddedView = switchToEmbeddedView,
embeddedDraggedDownBy = embeddedDraggedDownBy,
fastSeekForward = fastSeekForward,
fastSeekBackward = fastSeekBackward
) )
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {
@ -215,7 +228,10 @@ fun VideoPlayerControllerUIPreviewEmbedded() {
showUi = {}, showUi = {},
hideUi = {}, hideUi = {},
seekPositionChanged = {}, seekPositionChanged = {},
seekingFinished = {}) seekingFinished = {},
embeddedDraggedDownBy = {},
fastSeekBackward = {},
fastSeekForward = {})
} }
} }
} }
@ -242,7 +258,10 @@ fun VideoPlayerControllerUIPreviewLandscape() {
showUi = {}, showUi = {},
hideUi = {}, hideUi = {},
seekPositionChanged = {}, seekPositionChanged = {},
seekingFinished = {}) seekingFinished = {},
embeddedDraggedDownBy = {},
fastSeekBackward = {},
fastSeekForward = {})
} }
} }
} }
@ -270,7 +289,10 @@ fun VideoPlayerControllerUIPreviewPortrait() {
showUi = {}, showUi = {},
hideUi = {}, hideUi = {},
seekPositionChanged = {}, seekPositionChanged = {},
seekingFinished = {}) seekingFinished = {},
embeddedDraggedDownBy = {},
fastSeekForward = {},
fastSeekBackward = {})
} }
} }
} }

View File

@ -161,7 +161,10 @@ fun VideoPlayerUI(
showUi = viewModel::showUi, showUi = viewModel::showUi,
hideUi = viewModel::hideUi, hideUi = viewModel::hideUi,
seekPositionChanged = viewModel::seekPositionChanged, seekPositionChanged = viewModel::seekPositionChanged,
seekingFinished = viewModel::seekingFinished seekingFinished = viewModel::seekingFinished,
embeddedDraggedDownBy = viewModel::embeddedDraggedDown,
fastSeekForward = viewModel::fastSeekForward,
fastSeekBackward = viewModel::fastSeekBackward
) )
} }
} }

View File

@ -92,7 +92,8 @@ fun VideoPlayerControllerUIPreviewEmbeddedColorPreview() {
showUi = {}, showUi = {},
hideUi = {}, hideUi = {},
seekPositionChanged = {}, seekPositionChanged = {},
seekingFinished = {}) seekingFinished = {},
embeddedDraggedDownBy = {})
} }
} }
} }

View File

@ -20,15 +20,33 @@
package net.newpipe.newplayer.ui.videoplayer package net.newpipe.newplayer.ui.videoplayer
import android.util.Log
import android.view.MotionEvent import android.view.MotionEvent
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
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 kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
private const val TAG = "TouchUi"
private data class TouchedPosition(val x: Float, val y: Float) {
operator fun minus(other: TouchedPosition) = TouchedPosition(this.x - other.x, this.y - other.y)
}
const val DELAY_UNTIL_SHOWING_UI_AFTER_TOUCH_IN_MS:Long = 150
@OptIn(ExperimentalComposeUiApi::class) @OptIn(ExperimentalComposeUiApi::class)
@Composable @Composable
@ -38,32 +56,159 @@ fun TouchUi(
showUi: () -> Unit, showUi: () -> Unit,
uiVissible: Boolean, uiVissible: Boolean,
fullscreen: Boolean, fullscreen: Boolean,
switchToFullscreen: () -> Unit,
switchToEmbeddedView: () -> Unit,
embeddedDraggedDownBy: (Float) -> Unit,
fastSeekBackward: () -> Unit,
fastSeekForward: () -> Unit,
) { ) {
Box(modifier = Modifier
.pointerInteropFilter {
when (it.action) {
MotionEvent.ACTION_DOWN -> {
true
}
MotionEvent.ACTION_UP -> { var moveOccured by remember {
if (uiVissible) { mutableStateOf(false)
}
var lastTouchedPosition by remember {
mutableStateOf(TouchedPosition(0f, 0f))
}
var lastTouchTime by remember {
mutableStateOf(System.currentTimeMillis())
}
val composableScope = rememberCoroutineScope()
var showUiJob: Job? by remember{
mutableStateOf(null)
}
val defaultActionDown = { event: MotionEvent ->
lastTouchedPosition = TouchedPosition(event.x, event.y)
moveOccured = false
true
}
val defaultActionUp = { onDoubleTap: () -> Unit ->
val currentTime = System.currentTimeMillis()
if (!moveOccured) {
val timeSinceLastTouch = currentTime - lastTouchTime
if(timeSinceLastTouch <= DELAY_UNTIL_SHOWING_UI_AFTER_TOUCH_IN_MS) {
showUiJob?.cancel()
onDoubleTap()
} else {
if (uiVissible) {
showUiJob = composableScope.launch {
delay(DELAY_UNTIL_SHOWING_UI_AFTER_TOUCH_IN_MS)
hideUi() hideUi()
} else { }
} else {
showUiJob = composableScope.launch {
delay(DELAY_UNTIL_SHOWING_UI_AFTER_TOUCH_IN_MS)
showUi() showUi()
} }
true
} }
MotionEvent.ACTION_MOVE -> {
true
}
else -> false
} }
}) { }
Surface(color = Color.Transparent, modifier = Modifier.fillMaxSize()) { moveOccured = false
lastTouchTime = currentTime
true
}
val handleMove = { event: MotionEvent, lambda: (movement: TouchedPosition) -> Unit ->
val currentTouchedPosition = TouchedPosition(event.x, event.y)
val movement = currentTouchedPosition - lastTouchedPosition
lastTouchedPosition = currentTouchedPosition
moveOccured = true
lambda(movement)
true
}
if (fullscreen) {
Row(modifier = modifier) {
TouchSurface(modifier = Modifier.weight(1f)
.pointerInteropFilter {
when (it.action) {
MotionEvent.ACTION_DOWN -> defaultActionDown(it)
MotionEvent.ACTION_UP -> defaultActionUp(fastSeekBackward)
MotionEvent.ACTION_MOVE -> handleMove(it) { movement ->
}
else -> false
}
})
TouchSurface(modifier = Modifier
.weight(1f)
.pointerInteropFilter {
when (it.action) {
MotionEvent.ACTION_DOWN -> defaultActionDown(it)
MotionEvent.ACTION_UP -> defaultActionUp({})
MotionEvent.ACTION_MOVE -> handleMove(it) { movement ->
if (0 < movement.y) {
switchToEmbeddedView()
}
}
else -> false
}
})
TouchSurface(modifier = Modifier.weight(1f)
.pointerInteropFilter {
when (it.action) {
MotionEvent.ACTION_DOWN -> defaultActionDown(it)
MotionEvent.ACTION_UP -> defaultActionUp(fastSeekForward)
MotionEvent.ACTION_MOVE -> handleMove(it) { movement ->
}
else -> false
}
})
}
} else {
Row(modifier = modifier) {
TouchSurface(modifier = Modifier
.weight(1f)
.pointerInteropFilter {
when (it.action) {
MotionEvent.ACTION_DOWN -> defaultActionDown(it)
MotionEvent.ACTION_UP -> defaultActionUp(fastSeekBackward)
MotionEvent.ACTION_MOVE -> handleMove(it) { movement ->
Log.d(TAG, "${it.x}:${it.y}")
if (0 < movement.y) {
embeddedDraggedDownBy(movement.y)
} else {
switchToFullscreen()
}
}
else -> false
}
})
TouchSurface(modifier = Modifier
.weight(1f)
.pointerInteropFilter {
when (it.action) {
MotionEvent.ACTION_DOWN -> defaultActionDown(it)
MotionEvent.ACTION_UP -> defaultActionUp(fastSeekForward)
MotionEvent.ACTION_MOVE -> handleMove(it) { movement ->
if (0 < movement.y) {
embeddedDraggedDownBy(movement.y)
} else {
switchToFullscreen()
}
}
else -> false
}
})
} }
} }
} }
@Composable
private fun TouchSurface(modifier: Modifier, color: Color = Color.Transparent) {
Box(modifier = modifier) {
Surface(color = color, modifier = Modifier.fillMaxSize()) {}
}
}