put touch surface logic into touch surface itself

This commit is contained in:
Christian Schabesberger 2024-08-06 11:40:50 +02:00
parent 07a6b0a03f
commit ae3ef47a3f
7 changed files with 227 additions and 223 deletions

View File

@ -82,6 +82,9 @@ interface NewPlayer {
}
class NewPlayerImpl(override val internal_player: Player, override val repository: MediaRepository) : NewPlayer {
private var callbackListeners: MutableList<NewPlayer.Listener> = ArrayList()
override val duartion: Long = internal_player.duration
override val bufferedPercentage: Int = internal_player.bufferedPercentage
override var currentPosition: Long = internal_player.currentPosition
@ -112,22 +115,21 @@ class NewPlayerImpl(override val internal_player: Player, override val repositor
}
override fun fastSeekForward() {
TODO("Not yet implemented")
Log.d(TAG, "not implemented fast seek forward")
}
override fun fastSeekBackward() {
TODO("Not yet implemented")
Log.d(TAG, "not implemented fast seek backward")
}
override fun addToPlaylist(newItem: String) {
TODO("Not yet implemented")
Log.d(TAG, "Not implemented add to playlist")
}
override fun addListener(callbackListener: NewPlayer.Listener) {
TODO("Not yet implemented")
callbackListeners.add(callbackListener)
}
override fun setStream(stream: MediaItem) {
if (internal_player.playbackState == Player.STATE_IDLE) {
internal_player.prepare()

View File

@ -269,10 +269,16 @@ class VideoPlayerViewModelImpl @Inject constructor(
override fun fastSeekForward() {
newPlayer?.fastSeekForward()
if (mutableUiState.value.uiVisible) {
resetHideUiDelayedJob()
}
}
override fun fastSeekBackward() {
newPlayer?.fastSeekBackward()
if (mutableUiState.value.uiVisible) {
resetHideUiDelayedJob()
}
}
override fun switchToEmbeddedView() {

View File

@ -50,7 +50,7 @@ import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
import net.newpipe.newplayer.ui.videoplayer.BottomUI
import net.newpipe.newplayer.ui.videoplayer.CenterUI
import net.newpipe.newplayer.ui.videoplayer.TopUI
import net.newpipe.newplayer.ui.videoplayer.TouchUi
import net.newpipe.newplayer.ui.videoplayer.GestureUI
@Composable
fun VideoPlayerControllerUI(
@ -89,7 +89,7 @@ fun VideoPlayerControllerUI(
.union(WindowInsets.waterfall)
if (!uiVissible) {
TouchUi(
GestureUI(
modifier = Modifier
.fillMaxSize(),
// .windowInsetsPadding(WindowInsets.systemGestures),
@ -124,7 +124,7 @@ fun VideoPlayerControllerUI(
}
AnimatedVisibility(uiVissible) {
TouchUi(
GestureUI(
modifier = Modifier
.fillMaxSize()
.windowInsetsPadding(WindowInsets.systemGestures),

View File

@ -93,7 +93,9 @@ fun VideoPlayerControllerUIPreviewEmbeddedColorPreview() {
hideUi = {},
seekPositionChanged = {},
seekingFinished = {},
embeddedDraggedDownBy = {})
embeddedDraggedDownBy = {},
fastSeekForward = {},
fastSeekBackward = {})
}
}
}

View File

@ -0,0 +1,199 @@
/* 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
import android.util.Log
import android.view.MotionEvent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
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.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
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 = 200
@Composable
fun GestureUI(
modifier: Modifier,
hideUi: () -> Unit,
showUi: () -> Unit,
uiVissible: Boolean,
fullscreen: Boolean,
switchToFullscreen: () -> Unit,
switchToEmbeddedView: () -> Unit,
embeddedDraggedDownBy: (Float) -> Unit,
fastSeekBackward: () -> Unit,
fastSeekForward: () -> Unit,
) {
val defaultOnRegularTap = {
if (uiVissible) {
hideUi()
} else {
showUi()
}
}
if (fullscreen) {
Row(modifier = modifier) {
TouchSurface(
modifier = Modifier
.weight(1f),
onRegularTap = defaultOnRegularTap,
onDoubleTab = fastSeekBackward
)
TouchSurface(
modifier = Modifier
.weight(1f),
onRegularTap = defaultOnRegularTap,
onMovement = { movement ->
if (0 < movement.y) {
switchToEmbeddedView()
}
}
)
TouchSurface(
modifier = Modifier
.weight(1f),
onRegularTap = defaultOnRegularTap,
onDoubleTab = fastSeekForward
)
}
} 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),
onDoubleTab = fastSeekBackward,
onRegularTap = defaultOnRegularTap,
onMovement = handleDownwardMovement
)
TouchSurface(
modifier = Modifier
.weight(1f),
onDoubleTab = fastSeekForward,
onRegularTap = defaultOnRegularTap,
onMovement = handleDownwardMovement
)
}
}
}
@Composable
@OptIn(ExperimentalComposeUiApi::class)
private fun TouchSurface(
modifier: Modifier,
color: Color = Color.Transparent,
onDoubleTab: () -> Unit = {},
onRegularTap: () -> Unit = {},
onMovement: (TouchedPosition) -> Unit = {}
) {
var moveOccured by remember {
mutableStateOf(false)
}
var lastTouchedPosition by remember {
mutableStateOf(TouchedPosition(0f, 0f))
}
var lastTouchTime by remember {
mutableStateOf(System.currentTimeMillis())
}
val composableScope = rememberCoroutineScope()
var regularTabJob: Job? by remember {
mutableStateOf(null)
}
val defaultActionDown = { event: MotionEvent ->
lastTouchedPosition = TouchedPosition(event.x, event.y)
moveOccured = false
true
}
val defaultActionUp = { onDoubleTap: () -> Unit, onRegularTap: () -> Unit ->
val currentTime = System.currentTimeMillis()
if (!moveOccured) {
val timeSinceLastTouch = currentTime - lastTouchTime
if (timeSinceLastTouch <= DELAY_UNTIL_SHOWING_UI_AFTER_TOUCH_IN_MS) {
regularTabJob?.cancel()
onDoubleTap()
} else {
regularTabJob = composableScope.launch {
delay(DELAY_UNTIL_SHOWING_UI_AFTER_TOUCH_IN_MS)
onRegularTap()
}
}
}
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
}
Box(modifier = modifier.pointerInteropFilter {
when (it.action) {
MotionEvent.ACTION_DOWN -> defaultActionDown(it)
MotionEvent.ACTION_UP -> defaultActionUp(onDoubleTab, onRegularTap)
MotionEvent.ACTION_MOVE -> handleMove(it, onMovement)
else -> false
}
}) {
Surface(color = color, modifier = Modifier.fillMaxSize()) {}
}
}

View File

@ -1,214 +0,0 @@
/* 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
import android.util.Log
import android.view.MotionEvent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
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.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
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)
@Composable
fun TouchUi(
modifier: Modifier,
hideUi: () -> Unit,
showUi: () -> Unit,
uiVissible: Boolean,
fullscreen: Boolean,
switchToFullscreen: () -> Unit,
switchToEmbeddedView: () -> Unit,
embeddedDraggedDownBy: (Float) -> Unit,
fastSeekBackward: () -> Unit,
fastSeekForward: () -> Unit,
) {
var moveOccured by remember {
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()
}
} else {
showUiJob = composableScope.launch {
delay(DELAY_UNTIL_SHOWING_UI_AFTER_TOUCH_IN_MS)
showUi()
}
}
}
}
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()) {}
}
}

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="20dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M3,2 L22,12 L3,22 Z" />
</vector>