advance touch ui: make fullscreen and and embedded view switch possible
This commit is contained in:
parent
ce6ef8a8fd
commit
07a6b0a03f
|
@ -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) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 = {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,7 +92,8 @@ fun VideoPlayerControllerUIPreviewEmbeddedColorPreview() {
|
||||||
showUi = {},
|
showUi = {},
|
||||||
hideUi = {},
|
hideUi = {},
|
||||||
seekPositionChanged = {},
|
seekPositionChanged = {},
|
||||||
seekingFinished = {})
|
seekingFinished = {},
|
||||||
|
embeddedDraggedDownBy = {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()) {}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue