streamline ui mode change

This commit is contained in:
Christian Schabesberger 2024-09-25 21:12:14 +02:00
parent 12de2c3fac
commit 6aabfd6066
6 changed files with 83 additions and 107 deletions

1
.gitignore vendored
View File

@ -13,3 +13,4 @@
.externalNativeBuild .externalNativeBuild
.cxx .cxx
local.properties local.properties
.kotlin

View File

@ -45,10 +45,8 @@ interface NewPlayerViewModel {
fun pause() fun pause()
fun prevStream() fun prevStream()
fun nextStream() fun nextStream()
fun changeUiMode(newUiModeState: UIModeState, embeddedUiConfig: EmbeddedUiConfig) fun changeUiMode(newUiModeState: UIModeState, embeddedUiConfig: EmbeddedUiConfig?)
fun onBackPressed() fun onBackPressed()
fun showUi()
fun hideUi()
fun seekPositionChanged(newValue: Float) fun seekPositionChanged(newValue: Float)
fun seekingFinished() fun seekingFinished()
fun embeddedDraggedDown(offset: Float) fun embeddedDraggedDown(offset: Float)

View File

@ -32,14 +32,6 @@ open class NewPlayerViewModelDummy : NewPlayerViewModel {
println("dummy impl") println("dummy impl")
} }
override fun showUi() {
println("dummy impl")
}
override fun hideUi() {
println("dummy impl")
}
override fun seekPositionChanged(newValue: Float) { override fun seekPositionChanged(newValue: Float) {
println("dymmy seekPositionChanged: newValue: ${newValue}") println("dymmy seekPositionChanged: newValue: ${newValue}")
} }
@ -116,7 +108,7 @@ open class NewPlayerViewModelDummy : NewPlayerViewModel {
println("dummy impl") 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") println("dummy uiMode change: New UI Mode State: $newUiModeState")
} }
} }

View File

@ -47,9 +47,9 @@ import kotlinx.coroutines.launch
import net.newpipe.newplayer.utils.VideoSize import net.newpipe.newplayer.utils.VideoSize
import net.newpipe.newplayer.NewPlayer import net.newpipe.newplayer.NewPlayer
import net.newpipe.newplayer.NewPlayerException import net.newpipe.newplayer.NewPlayerException
import net.newpipe.newplayer.PlayMode
import net.newpipe.newplayer.RepeatMode import net.newpipe.newplayer.RepeatMode
import net.newpipe.newplayer.ui.ContentScale import net.newpipe.newplayer.ui.ContentScale
import net.newpipe.newplayer.utils.isInPowerSaveMode
import java.util.LinkedList import java.util.LinkedList
val VIDEOPLAYER_UI_STATE = "video_player_ui_state" val VIDEOPLAYER_UI_STATE = "video_player_ui_state"
@ -71,7 +71,7 @@ class NewPlayerViewModelImpl @Inject constructor(
private var playlistItemToBeMoved: Int? = null private var playlistItemToBeMoved: Int? = null
private var playlistItemNewPosition: Int = 0 private var playlistItemNewPosition: Int = 0
private var uiVisibilityJob: Job? = null private var hideUiDelayedJob: Job? = null
private var progressUpdaterJob: Job? = null private var progressUpdaterJob: Job? = null
private var playlistProgressUpdaterJob: Job? = null private var playlistProgressUpdaterJob: Job? = null
@ -142,7 +142,7 @@ class NewPlayerViewModelImpl @Inject constructor(
set(value) { set(value) {
field = value field = value
if (progressUpdaterJob?.isActive == true) { if (progressUpdaterJob?.isActive == true) {
resetProgressUpdatePeriodicallyJob() startProgressUpdatePeriodicallyJob()
} }
} }
@ -161,9 +161,9 @@ class NewPlayerViewModelImpl @Inject constructor(
it.copy(playing = isPlaying, isLoading = false) it.copy(playing = isPlaying, isLoading = false)
} }
if (isPlaying && uiState.value.uiMode.videoControllerUiVisible) { if (isPlaying && uiState.value.uiMode.videoControllerUiVisible) {
resetHideUiDelayedJob() startHideUiDelayedJob()
} else { } else {
uiVisibilityJob?.cancel() hideUiDelayedJob?.cancel()
} }
} }
@ -205,12 +205,7 @@ class NewPlayerViewModelImpl @Inject constructor(
val currentMode = mutableUiState.value.uiMode.toPlayMode() val currentMode = mutableUiState.value.uiMode.toPlayMode()
if (currentMode != newMode) { if (currentMode != newMode) {
mutableUiState.update { changeUiMode(UIModeState.fromPlayMode(newMode), embeddedUiConfig)
it.copy(
uiMode = UIModeState.fromPlayMode(newMode),
embeddedUiConfig = embeddedUiConfig
)
}
} }
} }
} }
@ -309,18 +304,18 @@ class NewPlayerViewModelImpl @Inject constructor(
} }
override fun play() { override fun play() {
hideUi() changeUiMode(uiState.value.uiMode.getUiHiddenState(), null)
newPlayer?.play() newPlayer?.play()
} }
override fun pause() { override fun pause() {
uiVisibilityJob?.cancel() hideUiDelayedJob?.cancel()
newPlayer?.pause() newPlayer?.pause()
} }
override fun prevStream() { override fun prevStream() {
resetHideUiDelayedJob() startHideUiDelayedJob()
newPlayer?.let { newPlayer -> newPlayer?.let { newPlayer ->
if (0 <= newPlayer.currentlyPlayingPlaylistItem - 1) { if (0 <= newPlayer.currentlyPlayingPlaylistItem - 1) {
newPlayer.currentlyPlayingPlaylistItem -= 1 newPlayer.currentlyPlayingPlaylistItem -= 1
@ -329,7 +324,7 @@ class NewPlayerViewModelImpl @Inject constructor(
} }
override fun nextStream() { override fun nextStream() {
resetHideUiDelayedJob() startHideUiDelayedJob()
newPlayer?.let { newPlayer -> newPlayer?.let { newPlayer ->
if (newPlayer.currentlyPlayingPlaylistItem + 1 < if (newPlayer.currentlyPlayingPlaylistItem + 1 <
(newPlayer.exoPlayer.value?.mediaItemCount ?: 0) (newPlayer.exoPlayer.value?.mediaItemCount ?: 0)
@ -339,17 +334,21 @@ class NewPlayerViewModelImpl @Inject constructor(
} }
} }
override fun changeUiMode(newUiModeState: UIModeState, embeddedUiConfig: EmbeddedUiConfig) { override fun changeUiMode(newUiModeState: UIModeState, embeddedUiConfig: EmbeddedUiConfig?) {
if (!uiState.value.uiMode.fullscreen && newUiModeState.fullscreen) { if (newUiModeState == uiState.value.uiMode) {
return;
}
if (!uiState.value.uiMode.fullscreen && newUiModeState.fullscreen && embeddedUiConfig != null) {
this.embeddedUiConfig = embeddedUiConfig this.embeddedUiConfig = embeddedUiConfig
} }
if (!(newUiModeState == UIModeState.EMBEDDED_VIDEO_CONTROLLER_UI || if (!(newUiModeState == UIModeState.EMBEDDED_VIDEO_CONTROLLER_UI ||
newUiModeState == UIModeState.FULLSCREEN_VIDEO_CONTROLLER_UI) newUiModeState == UIModeState.FULLSCREEN_VIDEO_CONTROLLER_UI)
) { ) {
uiVisibilityJob?.cancel() hideUiDelayedJob?.cancel()
} else { } else {
resetHideUiDelayedJob() startHideUiDelayedJob()
} }
if (newUiModeState.isStreamSelect) { if (newUiModeState.isStreamSelect) {
@ -367,33 +366,38 @@ class NewPlayerViewModelImpl @Inject constructor(
progressUpdaterJob?.cancel() 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() { private fun startHideUiDelayedJob() {
mutableUiState.update { hideUiDelayedJob?.cancel()
it.copy(uiMode = it.uiMode.getControllerUiVisibleState()) hideUiDelayedJob = viewModelScope.launch {
}
resetHideUiDelayedJob()
resetProgressUpdatePeriodicallyJob()
}
private fun resetHideUiDelayedJob() {
var ex: Exception? = null
try {
throw Exception()
} catch (e: Exception) {
ex = e
}
uiVisibilityJob?.cancel()
uiVisibilityJob = viewModelScope.launch {
delay(2000) delay(2000)
hideUi() changeUiMode(uiState.value.uiMode.getUiHiddenState(), null)
ex?.printStackTrace()
} }
} }
private fun resetProgressUpdatePeriodicallyJob() { private fun startProgressUpdatePeriodicallyJob() {
progressUpdaterJob?.cancel() progressUpdaterJob?.cancel()
progressUpdaterJob = viewModelScope.launch { progressUpdaterJob = viewModelScope.launch {
while (true) { 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) { override fun seekPositionChanged(newValue: Float) {
uiVisibilityJob?.cancel() hideUiDelayedJob?.cancel()
mutableUiState.update { it.copy(seekerPosition = newValue) } mutableUiState.update { it.copy(seekerPosition = newValue) }
} }
override fun seekingFinished() { override fun seekingFinished() {
resetHideUiDelayedJob() startHideUiDelayedJob()
val seekerPosition = mutableUiState.value.seekerPosition val seekerPosition = mutableUiState.value.seekerPosition
val seekPositionInMs = (newPlayer?.duration?.toFloat() ?: 0F) * seekerPosition val seekPositionInMs = (newPlayer?.duration?.toFloat() ?: 0F) * seekerPosition
newPlayer?.currentPosition = seekPositionInMs.toLong() newPlayer?.currentPosition = seekPositionInMs.toLong()
@ -479,20 +475,21 @@ class NewPlayerViewModelImpl @Inject constructor(
} }
if (mutableUiState.value.uiMode.videoControllerUiVisible) { if (mutableUiState.value.uiMode.videoControllerUiVisible) {
resetHideUiDelayedJob() startHideUiDelayedJob()
} }
} }
override fun finishFastSeek() { override fun finishFastSeek() {
if (mutableUiState.value.uiMode.videoControllerUiVisible) { if (mutableUiState.value.uiMode.videoControllerUiVisible) {
resetHideUiDelayedJob() startHideUiDelayedJob()
} }
val fastSeekAmount = mutableUiState.value.fastSeekSeconds val fastSeekAmount = mutableUiState.value.fastSeekSeconds
if (fastSeekAmount != 0) { if (fastSeekAmount != 0) {
Log.d(TAG, "$fastSeekAmount") Log.d(TAG, "$fastSeekAmount")
newPlayer?.currentPosition = (newPlayer?.currentPosition ?: 0) + (fastSeekAmount * 1000) newPlayer?.currentPosition =
(newPlayer?.currentPosition ?: 0) + (fastSeekAmount * 1000)
mutableUiState.update { mutableUiState.update {
it.copy(fastSeekSeconds = 0) it.copy(fastSeekSeconds = 0)
} }
@ -533,7 +530,7 @@ class NewPlayerViewModelImpl @Inject constructor(
override fun onBackPressed() { override fun onBackPressed() {
val nextMode = uiState.value.uiMode.getNextModeWhenBackPressed() val nextMode = uiState.value.uiMode.getNextModeWhenBackPressed()
if (nextMode != null) { if (nextMode != null) {
updateUiMode(nextMode) changeUiMode(nextMode, null)
} else { } else {
safeTryEmit(mutableOnBackPressed, Unit) safeTryEmit(mutableOnBackPressed, Unit)
} }
@ -593,9 +590,9 @@ class NewPlayerViewModelImpl @Inject constructor(
override fun dialogVisible(visible: Boolean) { override fun dialogVisible(visible: Boolean) {
if (visible) { if (visible) {
uiVisibilityJob?.cancel() hideUiDelayedJob?.cancel()
} else { } else {
resetHideUiDelayedJob() startHideUiDelayedJob()
} }
} }
@ -603,21 +600,6 @@ class NewPlayerViewModelImpl @Inject constructor(
newPlayer?.removePlaylistItem(uniqueId) 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 -> private fun getEmbeddedUiRatio() = newPlayer?.exoPlayer?.value?.let { player ->
val videoRatio = VideoSize.fromMedia3VideoSize(player.videoSize).getRatio() val videoRatio = VideoSize.fromMedia3VideoSize(player.videoSize).getRatio()
return (if (videoRatio.isNaN()) currentContentRatio return (if (videoRatio.isNaN()) currentContentRatio

View File

@ -87,9 +87,9 @@ fun EmbeddedGestureUI(
val defaultOnRegularTap = { val defaultOnRegularTap = {
if (uiState.uiMode.videoControllerUiVisible) { if (uiState.uiMode.videoControllerUiVisible) {
viewModel.hideUi() viewModel.changeUiMode(uiState.uiMode.getUiHiddenState(), null)
} else { } else {
viewModel.showUi() viewModel.changeUiMode(uiState.uiMode.getControllerUiVisibleState(), null)
} }
} }
@ -124,7 +124,7 @@ fun EmbeddedGestureUI(
if (count == 1) { if (count == 1) {
if (uiState.playing) { if (uiState.playing) {
viewModel.pause() viewModel.pause()
viewModel.showUi() viewModel.changeUiMode(uiState.uiMode.getControllerUiVisibleState(), null)
} else { } else {
viewModel.play() viewModel.play()
} }

View File

@ -36,6 +36,8 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue 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.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import net.newpipe.newplayer.model.EmbeddedUiConfig
import net.newpipe.newplayer.model.UIModeState import net.newpipe.newplayer.model.UIModeState
import net.newpipe.newplayer.model.NewPlayerUIState import net.newpipe.newplayer.model.NewPlayerUIState
import net.newpipe.newplayer.model.NewPlayerViewModel import net.newpipe.newplayer.model.NewPlayerViewModel
@ -55,7 +58,7 @@ import net.newpipe.newplayer.utils.getDefaultBrightness
import net.newpipe.newplayer.utils.getEmbeddedUiConfig import net.newpipe.newplayer.utils.getEmbeddedUiConfig
private enum class IndicatorMode { private enum class IndicatorMode {
NONE, VOLUME_INDICATOR_VISSIBLE, BRIGHTNESS_INDICATOR_VISSIBLE NONE, VOLUME_INDICATOR_VISIBLE, BRIGHTNESS_INDICATOR_VISIBLE
} }
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
@ -65,7 +68,7 @@ fun FullscreenGestureUI(
) { ) {
var heightPx by remember { var heightPx by remember {
mutableStateOf(0f) mutableFloatStateOf(0f)
} }
var indicatorMode by remember { var indicatorMode by remember {
@ -74,9 +77,9 @@ fun FullscreenGestureUI(
val defaultOnRegularTap = { val defaultOnRegularTap = {
if (uiState.uiMode.videoControllerUiVisible) { if (uiState.uiMode.videoControllerUiVisible) {
viewModel.hideUi() viewModel.changeUiMode(uiState.uiMode.getUiHiddenState(), null)
} else { } else {
viewModel.showUi() viewModel.changeUiMode(uiState.uiMode.getControllerUiVisibleState(), null)
} }
} }
@ -100,8 +103,8 @@ fun FullscreenGestureUI(
indicatorMode = IndicatorMode.NONE indicatorMode = IndicatorMode.NONE
}, },
onMovement = { change -> onMovement = { change ->
if (indicatorMode == IndicatorMode.NONE || indicatorMode == IndicatorMode.BRIGHTNESS_INDICATOR_VISSIBLE) { if (indicatorMode == IndicatorMode.NONE || indicatorMode == IndicatorMode.BRIGHTNESS_INDICATOR_VISIBLE) {
indicatorMode = IndicatorMode.BRIGHTNESS_INDICATOR_VISSIBLE indicatorMode = IndicatorMode.BRIGHTNESS_INDICATOR_VISIBLE
if (heightPx != 0f) { if (heightPx != 0f) {
viewModel.brightnessChange(-change.y / heightPx, defaultBrightness) viewModel.brightnessChange(-change.y / heightPx, defaultBrightness)
@ -131,7 +134,7 @@ fun FullscreenGestureUI(
if(count == 1) { if(count == 1) {
if(uiState.playing) { if(uiState.playing) {
viewModel.pause() viewModel.pause()
viewModel.showUi() viewModel.changeUiMode(uiState.uiMode.getControllerUiVisibleState(), null)
} else { } else {
viewModel.play() viewModel.play()
} }
@ -145,8 +148,8 @@ fun FullscreenGestureUI(
indicatorMode = IndicatorMode.NONE indicatorMode = IndicatorMode.NONE
}, },
onMovement = { change -> onMovement = { change ->
if (indicatorMode == IndicatorMode.NONE || indicatorMode == IndicatorMode.VOLUME_INDICATOR_VISSIBLE) { if (indicatorMode == IndicatorMode.NONE || indicatorMode == IndicatorMode.VOLUME_INDICATOR_VISIBLE) {
indicatorMode = IndicatorMode.VOLUME_INDICATOR_VISSIBLE indicatorMode = IndicatorMode.VOLUME_INDICATOR_VISIBLE
if (heightPx != 0f) { if (heightPx != 0f) {
viewModel.volumeChange(-change.y / heightPx) viewModel.volumeChange(-change.y / heightPx)
} }
@ -166,14 +169,14 @@ fun FullscreenGestureUI(
IndicatorAnimation( IndicatorAnimation(
modifier = Modifier.align(Alignment.Center), modifier = Modifier.align(Alignment.Center),
visible = indicatorMode == IndicatorMode.VOLUME_INDICATOR_VISSIBLE, visible = indicatorMode == IndicatorMode.VOLUME_INDICATOR_VISIBLE,
) { ) {
VolumeCircle(volumeFraction = uiState.soundVolume) VolumeCircle(volumeFraction = uiState.soundVolume)
} }
IndicatorAnimation( IndicatorAnimation(
modifier = Modifier.align(Alignment.Center), modifier = Modifier.align(Alignment.Center),
visible = indicatorMode == IndicatorMode.BRIGHTNESS_INDICATOR_VISSIBLE, visible = indicatorMode == IndicatorMode.BRIGHTNESS_INDICATOR_VISIBLE,
) { ) {
VolumeCircle( VolumeCircle(
volumeFraction = uiState.brightness ?: defaultBrightness, volumeFraction = uiState.brightness ?: defaultBrightness,
@ -243,15 +246,15 @@ fun FullscreenGestureUIPreview() {
fun FullscreenGestureUIPreviewInteractive() { fun FullscreenGestureUIPreviewInteractive() {
var seekSeconds by remember { var seekSeconds by remember {
mutableStateOf(0) mutableIntStateOf(0)
} }
var brightnessValue by remember { var brightnessValue by remember {
mutableStateOf(0f) mutableFloatStateOf(0f)
} }
var soundVolume by remember { var soundVolume by remember {
mutableStateOf(0f) mutableFloatStateOf(0f)
} }
var uiVisible by remember { var uiVisible by remember {
@ -264,12 +267,12 @@ fun FullscreenGestureUIPreviewInteractive() {
modifier = Modifier, modifier = Modifier,
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
object : NewPlayerViewModelDummy() { object : NewPlayerViewModelDummy() {
override fun hideUi() { override fun changeUiMode(
uiVisible = false newUiModeState: UIModeState,
} embeddedUiConfig: EmbeddedUiConfig?
) {
override fun showUi() { super.changeUiMode(newUiModeState, embeddedUiConfig)
uiVisible = true uiVisible = newUiModeState.videoControllerUiVisible
} }
override fun fastSeek(steps: Int) { override fun fastSeek(steps: Int) {
@ -299,7 +302,7 @@ fun FullscreenGestureUIPreviewInteractive() {
} }
AnimatedVisibility(uiVisible) { AnimatedVisibility(uiVisible) {
Text("UI is Vissible") Text("UI is Visible")
} }
} }
} }