diff --git a/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt b/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt index ea70434..3f48125 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt @@ -82,6 +82,9 @@ interface NewPlayer { } class NewPlayerImpl(override val internal_player: Player, override val repository: MediaRepository) : NewPlayer { + + private var callbackListeners: MutableList = 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() diff --git a/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModelImpl.kt b/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModelImpl.kt index 0ed6867..6404e9e 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModelImpl.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModelImpl.kt @@ -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() { diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/VideoPlayerControllerUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/VideoPlayerControllerUI.kt index 01ebe22..c0e47ff 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/VideoPlayerControllerUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/VideoPlayerControllerUI.kt @@ -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), diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/theme/Color.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/theme/Color.kt index ed80ba4..d5a00a4 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/theme/Color.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/theme/Color.kt @@ -93,7 +93,9 @@ fun VideoPlayerControllerUIPreviewEmbeddedColorPreview() { hideUi = {}, seekPositionChanged = {}, seekingFinished = {}, - embeddedDraggedDownBy = {}) + embeddedDraggedDownBy = {}, + fastSeekForward = {}, + fastSeekBackward = {}) } } } diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/GestureUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/GestureUI.kt new file mode 100644 index 0000000..b8bd38a --- /dev/null +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/GestureUI.kt @@ -0,0 +1,199 @@ +/* NewPlayer + * + * @author Christian Schabesberger + * + * Copyright (C) NewPipe e.V. 2024 + * + * 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 . + */ + +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()) {} + } +} \ No newline at end of file diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/TouchUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/TouchUI.kt deleted file mode 100644 index c3f36ca..0000000 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/TouchUI.kt +++ /dev/null @@ -1,214 +0,0 @@ -/* NewPlayer - * - * @author Christian Schabesberger - * - * Copyright (C) NewPipe e.V. 2024 - * - * 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 . - */ - -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()) {} - } -} \ No newline at end of file diff --git a/test-app/src/main/res/drawable/ic_play_seek_triangle.xml b/test-app/src/main/res/drawable/ic_play_seek_triangle.xml new file mode 100644 index 0000000..9c257c4 --- /dev/null +++ b/test-app/src/main/res/drawable/ic_play_seek_triangle.xml @@ -0,0 +1,9 @@ + + +