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
.cxx
local.properties
.kotlin

View File

@ -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)

View File

@ -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")
}
}

View File

@ -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

View File

@ -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()
}

View File

@ -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")
}
}
}