diff --git a/.gitignore b/.gitignore index aa724b7..23732d0 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ .externalNativeBuild .cxx local.properties +.kotlin diff --git a/new-player/src/main/java/net/newpipe/newplayer/model/NewPlayerViewModel.kt b/new-player/src/main/java/net/newpipe/newplayer/model/NewPlayerViewModel.kt index 9f57a41..3680685 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/model/NewPlayerViewModel.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/model/NewPlayerViewModel.kt @@ -45,10 +45,8 @@ interface NewPlayerViewModel { fun pause() fun prevStream() fun nextStream() - fun changeUiMode(newUiModeState: UIModeState, embeddedUiConfig: EmbeddedUiConfig) + fun changeUiMode(newUiModeState: UIModeState, embeddedUiConfig: EmbeddedUiConfig?) fun onBackPressed() - fun showUi() - fun hideUi() fun seekPositionChanged(newValue: Float) fun seekingFinished() fun embeddedDraggedDown(offset: Float) diff --git a/new-player/src/main/java/net/newpipe/newplayer/model/NewPlayerViewModelDummy.kt b/new-player/src/main/java/net/newpipe/newplayer/model/NewPlayerViewModelDummy.kt index 35808b2..1814a11 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/model/NewPlayerViewModelDummy.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/model/NewPlayerViewModelDummy.kt @@ -32,14 +32,6 @@ open class NewPlayerViewModelDummy : NewPlayerViewModel { println("dummy impl") } - override fun showUi() { - println("dummy impl") - } - - override fun hideUi() { - println("dummy impl") - } - override fun seekPositionChanged(newValue: Float) { println("dymmy seekPositionChanged: newValue: ${newValue}") } @@ -116,7 +108,7 @@ open class NewPlayerViewModelDummy : NewPlayerViewModel { println("dummy impl") } - override fun changeUiMode(newUiModeState: UIModeState, embeddedUiConfig: EmbeddedUiConfig) { + override fun changeUiMode(newUiModeState: UIModeState, embeddedUiConfig: EmbeddedUiConfig?) { println("dummy uiMode change: New UI Mode State: $newUiModeState") } } diff --git a/new-player/src/main/java/net/newpipe/newplayer/model/NewPlayerViewModelImpl.kt b/new-player/src/main/java/net/newpipe/newplayer/model/NewPlayerViewModelImpl.kt index 422414f..acd93d1 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/model/NewPlayerViewModelImpl.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/model/NewPlayerViewModelImpl.kt @@ -47,9 +47,9 @@ import kotlinx.coroutines.launch import net.newpipe.newplayer.utils.VideoSize import net.newpipe.newplayer.NewPlayer import net.newpipe.newplayer.NewPlayerException +import net.newpipe.newplayer.PlayMode import net.newpipe.newplayer.RepeatMode import net.newpipe.newplayer.ui.ContentScale -import net.newpipe.newplayer.utils.isInPowerSaveMode import java.util.LinkedList val VIDEOPLAYER_UI_STATE = "video_player_ui_state" @@ -71,7 +71,7 @@ class NewPlayerViewModelImpl @Inject constructor( private var playlistItemToBeMoved: Int? = null private var playlistItemNewPosition: Int = 0 - private var uiVisibilityJob: Job? = null + private var hideUiDelayedJob: Job? = null private var progressUpdaterJob: Job? = null private var playlistProgressUpdaterJob: Job? = null @@ -142,7 +142,7 @@ class NewPlayerViewModelImpl @Inject constructor( set(value) { field = value if (progressUpdaterJob?.isActive == true) { - resetProgressUpdatePeriodicallyJob() + startProgressUpdatePeriodicallyJob() } } @@ -161,9 +161,9 @@ class NewPlayerViewModelImpl @Inject constructor( it.copy(playing = isPlaying, isLoading = false) } if (isPlaying && uiState.value.uiMode.videoControllerUiVisible) { - resetHideUiDelayedJob() + startHideUiDelayedJob() } else { - uiVisibilityJob?.cancel() + hideUiDelayedJob?.cancel() } } @@ -205,12 +205,7 @@ class NewPlayerViewModelImpl @Inject constructor( val currentMode = mutableUiState.value.uiMode.toPlayMode() if (currentMode != newMode) { - mutableUiState.update { - it.copy( - uiMode = UIModeState.fromPlayMode(newMode), - embeddedUiConfig = embeddedUiConfig - ) - } + changeUiMode(UIModeState.fromPlayMode(newMode), embeddedUiConfig) } } } @@ -309,18 +304,18 @@ class NewPlayerViewModelImpl @Inject constructor( } override fun play() { - hideUi() + changeUiMode(uiState.value.uiMode.getUiHiddenState(), null) newPlayer?.play() } override fun pause() { - uiVisibilityJob?.cancel() + hideUiDelayedJob?.cancel() newPlayer?.pause() } override fun prevStream() { - resetHideUiDelayedJob() + startHideUiDelayedJob() newPlayer?.let { newPlayer -> if (0 <= newPlayer.currentlyPlayingPlaylistItem - 1) { newPlayer.currentlyPlayingPlaylistItem -= 1 @@ -329,7 +324,7 @@ class NewPlayerViewModelImpl @Inject constructor( } override fun nextStream() { - resetHideUiDelayedJob() + startHideUiDelayedJob() newPlayer?.let { newPlayer -> if (newPlayer.currentlyPlayingPlaylistItem + 1 < (newPlayer.exoPlayer.value?.mediaItemCount ?: 0) @@ -339,17 +334,21 @@ class NewPlayerViewModelImpl @Inject constructor( } } - override fun changeUiMode(newUiModeState: UIModeState, embeddedUiConfig: EmbeddedUiConfig) { - if (!uiState.value.uiMode.fullscreen && newUiModeState.fullscreen) { + override fun changeUiMode(newUiModeState: UIModeState, embeddedUiConfig: EmbeddedUiConfig?) { + if (newUiModeState == uiState.value.uiMode) { + return; + } + + if (!uiState.value.uiMode.fullscreen && newUiModeState.fullscreen && embeddedUiConfig != null) { this.embeddedUiConfig = embeddedUiConfig } if (!(newUiModeState == UIModeState.EMBEDDED_VIDEO_CONTROLLER_UI || newUiModeState == UIModeState.FULLSCREEN_VIDEO_CONTROLLER_UI) ) { - uiVisibilityJob?.cancel() + hideUiDelayedJob?.cancel() } else { - resetHideUiDelayedJob() + startHideUiDelayedJob() } if (newUiModeState.isStreamSelect) { @@ -367,33 +366,38 @@ class NewPlayerViewModelImpl @Inject constructor( progressUpdaterJob?.cancel() } - updateUiMode(newUiModeState) + if (uiState.value.uiMode.fullscreen && !newUiModeState.fullscreen) { + mutableUiState.update { + it.copy(uiMode = newUiModeState, embeddedUiConfig = this.embeddedUiConfig) + } + } else { + mutableUiState.update { + it.copy(uiMode = newUiModeState) + } + } + + val newPlayMode = newUiModeState.toPlayMode() + // take the next value from the player because changeUiMode is called when the playBackMode + // of the player changes. If this value was taken from the viewModel instead + // this would lead to an endless loop. of changeMode state calling it self over and over again + // through the callback of the newPlayer?.playBackMode change + val currentPlayMode = newPlayer?.playBackMode?.value ?: PlayMode.IDLE + if (newPlayMode != currentPlayMode) { + newPlayer?.playBackMode?.update { + newPlayMode + } + } } - override fun showUi() { - mutableUiState.update { - it.copy(uiMode = it.uiMode.getControllerUiVisibleState()) - } - resetHideUiDelayedJob() - resetProgressUpdatePeriodicallyJob() - } - - private fun resetHideUiDelayedJob() { - var ex: Exception? = null - try { - throw Exception() - } catch (e: Exception) { - ex = e - } - uiVisibilityJob?.cancel() - uiVisibilityJob = viewModelScope.launch { + private fun startHideUiDelayedJob() { + hideUiDelayedJob?.cancel() + hideUiDelayedJob = viewModelScope.launch { delay(2000) - hideUi() - ex?.printStackTrace() + changeUiMode(uiState.value.uiMode.getUiHiddenState(), null) } } - private fun resetProgressUpdatePeriodicallyJob() { + private fun startProgressUpdatePeriodicallyJob() { progressUpdaterJob?.cancel() progressUpdaterJob = viewModelScope.launch { while (true) { @@ -446,21 +450,13 @@ class NewPlayerViewModelImpl @Inject constructor( } } - override fun hideUi() { - progressUpdaterJob?.cancel() - uiVisibilityJob?.cancel() - mutableUiState.update { - it.copy(uiMode = it.uiMode.getUiHiddenState()) - } - } - override fun seekPositionChanged(newValue: Float) { - uiVisibilityJob?.cancel() + hideUiDelayedJob?.cancel() mutableUiState.update { it.copy(seekerPosition = newValue) } } override fun seekingFinished() { - resetHideUiDelayedJob() + startHideUiDelayedJob() val seekerPosition = mutableUiState.value.seekerPosition val seekPositionInMs = (newPlayer?.duration?.toFloat() ?: 0F) * seekerPosition newPlayer?.currentPosition = seekPositionInMs.toLong() @@ -479,20 +475,21 @@ class NewPlayerViewModelImpl @Inject constructor( } if (mutableUiState.value.uiMode.videoControllerUiVisible) { - resetHideUiDelayedJob() + startHideUiDelayedJob() } } override fun finishFastSeek() { if (mutableUiState.value.uiMode.videoControllerUiVisible) { - resetHideUiDelayedJob() + startHideUiDelayedJob() } val fastSeekAmount = mutableUiState.value.fastSeekSeconds if (fastSeekAmount != 0) { Log.d(TAG, "$fastSeekAmount") - newPlayer?.currentPosition = (newPlayer?.currentPosition ?: 0) + (fastSeekAmount * 1000) + newPlayer?.currentPosition = + (newPlayer?.currentPosition ?: 0) + (fastSeekAmount * 1000) mutableUiState.update { it.copy(fastSeekSeconds = 0) } @@ -533,7 +530,7 @@ class NewPlayerViewModelImpl @Inject constructor( override fun onBackPressed() { val nextMode = uiState.value.uiMode.getNextModeWhenBackPressed() if (nextMode != null) { - updateUiMode(nextMode) + changeUiMode(nextMode, null) } else { safeTryEmit(mutableOnBackPressed, Unit) } @@ -593,9 +590,9 @@ class NewPlayerViewModelImpl @Inject constructor( override fun dialogVisible(visible: Boolean) { if (visible) { - uiVisibilityJob?.cancel() + hideUiDelayedJob?.cancel() } else { - resetHideUiDelayedJob() + startHideUiDelayedJob() } } @@ -603,21 +600,6 @@ class NewPlayerViewModelImpl @Inject constructor( newPlayer?.removePlaylistItem(uniqueId) } - - private fun updateUiMode(newState: UIModeState) { - val newPlayMode = newState.toPlayMode() - val currentPlayMode = mutableUiState.value.uiMode.toPlayMode() - if (newPlayMode != currentPlayMode) { - newPlayer?.playBackMode?.update { - newPlayMode!! - } - } else { - mutableUiState.update { - it.copy(uiMode = newState) - } - } - } - private fun getEmbeddedUiRatio() = newPlayer?.exoPlayer?.value?.let { player -> val videoRatio = VideoSize.fromMedia3VideoSize(player.videoSize).getRatio() return (if (videoRatio.isNaN()) currentContentRatio diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/gesture_ui/EmbeddedGestureUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/gesture_ui/EmbeddedGestureUI.kt index 49f2f3c..40407a5 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/gesture_ui/EmbeddedGestureUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/gesture_ui/EmbeddedGestureUI.kt @@ -87,9 +87,9 @@ fun EmbeddedGestureUI( val defaultOnRegularTap = { if (uiState.uiMode.videoControllerUiVisible) { - viewModel.hideUi() + viewModel.changeUiMode(uiState.uiMode.getUiHiddenState(), null) } else { - viewModel.showUi() + viewModel.changeUiMode(uiState.uiMode.getControllerUiVisibleState(), null) } } @@ -124,7 +124,7 @@ fun EmbeddedGestureUI( if (count == 1) { if (uiState.playing) { viewModel.pause() - viewModel.showUi() + viewModel.changeUiMode(uiState.uiMode.getControllerUiVisibleState(), null) } else { viewModel.play() } diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/gesture_ui/FullscreenGestureUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/gesture_ui/FullscreenGestureUI.kt index aa0fdf1..a2cde13 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/gesture_ui/FullscreenGestureUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/gesture_ui/FullscreenGestureUI.kt @@ -36,6 +36,8 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -46,6 +48,7 @@ import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import androidx.media3.common.util.UnstableApi +import net.newpipe.newplayer.model.EmbeddedUiConfig import net.newpipe.newplayer.model.UIModeState import net.newpipe.newplayer.model.NewPlayerUIState import net.newpipe.newplayer.model.NewPlayerViewModel @@ -55,7 +58,7 @@ import net.newpipe.newplayer.utils.getDefaultBrightness import net.newpipe.newplayer.utils.getEmbeddedUiConfig private enum class IndicatorMode { - NONE, VOLUME_INDICATOR_VISSIBLE, BRIGHTNESS_INDICATOR_VISSIBLE + NONE, VOLUME_INDICATOR_VISIBLE, BRIGHTNESS_INDICATOR_VISIBLE } @OptIn(UnstableApi::class) @@ -65,7 +68,7 @@ fun FullscreenGestureUI( ) { var heightPx by remember { - mutableStateOf(0f) + mutableFloatStateOf(0f) } var indicatorMode by remember { @@ -74,9 +77,9 @@ fun FullscreenGestureUI( val defaultOnRegularTap = { if (uiState.uiMode.videoControllerUiVisible) { - viewModel.hideUi() + viewModel.changeUiMode(uiState.uiMode.getUiHiddenState(), null) } else { - viewModel.showUi() + viewModel.changeUiMode(uiState.uiMode.getControllerUiVisibleState(), null) } } @@ -100,8 +103,8 @@ fun FullscreenGestureUI( indicatorMode = IndicatorMode.NONE }, onMovement = { change -> - if (indicatorMode == IndicatorMode.NONE || indicatorMode == IndicatorMode.BRIGHTNESS_INDICATOR_VISSIBLE) { - indicatorMode = IndicatorMode.BRIGHTNESS_INDICATOR_VISSIBLE + if (indicatorMode == IndicatorMode.NONE || indicatorMode == IndicatorMode.BRIGHTNESS_INDICATOR_VISIBLE) { + indicatorMode = IndicatorMode.BRIGHTNESS_INDICATOR_VISIBLE if (heightPx != 0f) { viewModel.brightnessChange(-change.y / heightPx, defaultBrightness) @@ -131,7 +134,7 @@ fun FullscreenGestureUI( if(count == 1) { if(uiState.playing) { viewModel.pause() - viewModel.showUi() + viewModel.changeUiMode(uiState.uiMode.getControllerUiVisibleState(), null) } else { viewModel.play() } @@ -145,8 +148,8 @@ fun FullscreenGestureUI( indicatorMode = IndicatorMode.NONE }, onMovement = { change -> - if (indicatorMode == IndicatorMode.NONE || indicatorMode == IndicatorMode.VOLUME_INDICATOR_VISSIBLE) { - indicatorMode = IndicatorMode.VOLUME_INDICATOR_VISSIBLE + if (indicatorMode == IndicatorMode.NONE || indicatorMode == IndicatorMode.VOLUME_INDICATOR_VISIBLE) { + indicatorMode = IndicatorMode.VOLUME_INDICATOR_VISIBLE if (heightPx != 0f) { viewModel.volumeChange(-change.y / heightPx) } @@ -166,14 +169,14 @@ fun FullscreenGestureUI( IndicatorAnimation( modifier = Modifier.align(Alignment.Center), - visible = indicatorMode == IndicatorMode.VOLUME_INDICATOR_VISSIBLE, + visible = indicatorMode == IndicatorMode.VOLUME_INDICATOR_VISIBLE, ) { VolumeCircle(volumeFraction = uiState.soundVolume) } IndicatorAnimation( modifier = Modifier.align(Alignment.Center), - visible = indicatorMode == IndicatorMode.BRIGHTNESS_INDICATOR_VISSIBLE, + visible = indicatorMode == IndicatorMode.BRIGHTNESS_INDICATOR_VISIBLE, ) { VolumeCircle( volumeFraction = uiState.brightness ?: defaultBrightness, @@ -243,15 +246,15 @@ fun FullscreenGestureUIPreview() { fun FullscreenGestureUIPreviewInteractive() { var seekSeconds by remember { - mutableStateOf(0) + mutableIntStateOf(0) } var brightnessValue by remember { - mutableStateOf(0f) + mutableFloatStateOf(0f) } var soundVolume by remember { - mutableStateOf(0f) + mutableFloatStateOf(0f) } var uiVisible by remember { @@ -264,12 +267,12 @@ fun FullscreenGestureUIPreviewInteractive() { modifier = Modifier, @OptIn(UnstableApi::class) object : NewPlayerViewModelDummy() { - override fun hideUi() { - uiVisible = false - } - - override fun showUi() { - uiVisible = true + override fun changeUiMode( + newUiModeState: UIModeState, + embeddedUiConfig: EmbeddedUiConfig? + ) { + super.changeUiMode(newUiModeState, embeddedUiConfig) + uiVisible = newUiModeState.videoControllerUiVisible } override fun fastSeek(steps: Int) { @@ -299,7 +302,7 @@ fun FullscreenGestureUIPreviewInteractive() { } AnimatedVisibility(uiVisible) { - Text("UI is Vissible") + Text("UI is Visible") } } }