streamline ui mode change
This commit is contained in:
parent
12de2c3fac
commit
6aabfd6066
|
@ -13,3 +13,4 @@
|
|||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
.kotlin
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
override fun showUi() {
|
||||
if (uiState.value.uiMode.fullscreen && !newUiModeState.fullscreen) {
|
||||
mutableUiState.update {
|
||||
it.copy(uiMode = it.uiMode.getControllerUiVisibleState())
|
||||
it.copy(uiMode = newUiModeState, embeddedUiConfig = this.embeddedUiConfig)
|
||||
}
|
||||
} else {
|
||||
mutableUiState.update {
|
||||
it.copy(uiMode = newUiModeState)
|
||||
}
|
||||
resetHideUiDelayedJob()
|
||||
resetProgressUpdatePeriodicallyJob()
|
||||
}
|
||||
|
||||
private fun resetHideUiDelayedJob() {
|
||||
var ex: Exception? = null
|
||||
try {
|
||||
throw Exception()
|
||||
} catch (e: Exception) {
|
||||
ex = e
|
||||
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
|
||||
}
|
||||
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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue