synchronize UI mode and playMode

This commit is contained in:
Christian Schabesberger 2024-08-26 12:09:51 +02:00
parent 47ad16c03d
commit f3d3ce380f
5 changed files with 63 additions and 22 deletions

View File

@ -35,9 +35,9 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.newpipe.newplayer.model.UIModeState
import net.newpipe.newplayer.utils.PlayList import net.newpipe.newplayer.utils.PlayList
import kotlin.Exception import kotlin.Exception
@ -63,13 +63,14 @@ interface NewPlayer {
var currentPosition: Long var currentPosition: Long
var fastSeekAmountSec: Int var fastSeekAmountSec: Int
var playBackMode: PlayMode var playBackMode: PlayMode
var playMode : MutableStateFlow<PlayMode?> var playMode: StateFlow<PlayMode?>
var playlist: PlayList var playlist: PlayList
// callbacks // callbacks
val errorFlow : SharedFlow<Exception> val errorFlow: SharedFlow<Exception>
val onExoPlayerEvent: SharedFlow<Pair<Player, Player.Events>>
// methods // methods
fun prepare() fun prepare()
@ -78,6 +79,7 @@ interface NewPlayer {
fun addToPlaylist(item: String) fun addToPlaylist(item: String)
fun playStream(item: String, playMode: PlayMode) fun playStream(item: String, playMode: PlayMode)
fun playStream(item: String, streamVariant: String, playMode: PlayMode) fun playStream(item: String, streamVariant: String, playMode: PlayMode)
fun setPlayMode(playMode: PlayMode)
data class Builder(val app: Application, val repository: MediaRepository) { data class Builder(val app: Application, val repository: MediaRepository) {
private var mediaSourceFactory: MediaSource.Factory? = null private var mediaSourceFactory: MediaSource.Factory? = null
@ -114,7 +116,7 @@ class NewPlayerImpl(
override val repository: MediaRepository, override val repository: MediaRepository,
) : NewPlayer { ) : NewPlayer {
var mutableErrorFlow = MutableSharedFlow<Exception>() var mutableErrorFlow = MutableSharedFlow<Exception>()
override val errorFlow = mutableErrorFlow.asSharedFlow() override val errorFlow = mutableErrorFlow.asSharedFlow()
override val bufferedPercentage: Int override val bufferedPercentage: Int
@ -130,7 +132,12 @@ class NewPlayerImpl(
private var playerScope = CoroutineScope(Dispatchers.Main + Job()) private var playerScope = CoroutineScope(Dispatchers.Main + Job())
override var playMode = MutableStateFlow<PlayMode?>(null) var mutablePlayMode = MutableStateFlow<PlayMode?>(null)
override var playMode = mutablePlayMode.asStateFlow()
var mutableOnEvent = MutableSharedFlow<Pair<Player, Player.Events>>()
override val onExoPlayerEvent: SharedFlow<Pair<Player, Player.Events>> =
mutableOnEvent.asSharedFlow()
override var playWhenReady: Boolean override var playWhenReady: Boolean
set(value) { set(value) {
@ -157,6 +164,13 @@ class NewPlayerImpl(
} }
} }
} }
override fun onEvents(player: Player, events: Player.Events) {
super.onEvents(player, events)
launchJobAndCollectError {
mutableOnEvent.emit(Pair(player, events))
}
}
}) })
} }
@ -197,11 +211,15 @@ class NewPlayerImpl(
} }
} }
override fun setPlayMode(playMode: PlayMode) {
this.mutablePlayMode.update { playMode }
}
private fun internalPlayStream(mediaItem: MediaItem, playMode: PlayMode) { private fun internalPlayStream(mediaItem: MediaItem, playMode: PlayMode) {
if (internalPlayer.playbackState == Player.STATE_IDLE) { if (internalPlayer.playbackState == Player.STATE_IDLE) {
internalPlayer.prepare() internalPlayer.prepare()
} }
this.playMode.update { playMode } this.mutablePlayMode.update { playMode }
this.internalPlayer.setMediaItem(mediaItem) this.internalPlayer.setMediaItem(mediaItem)
this.internalPlayer.play() this.internalPlayer.play()
} }

View File

@ -0,0 +1,6 @@
package net.newpipe.newplayer
class NewPlayerException : Exception {
constructor(message: String) : super(message)
constructor(message: String, cause: Throwable) : super(message, cause)
}

View File

@ -96,6 +96,17 @@ enum class UIModeState {
else -> this else -> this
} }
fun toPlayMode() = when(this) {
PLACEHOLDER -> null
EMBEDDED_VIDEO -> PlayMode.EMBEDDED_VIDEO
EMBEDDED_VIDEO_CONTROLLER_UI -> PlayMode.EMBEDDED_VIDEO
EMBEDDED_VIDEO_CHAPTER_SELECT -> PlayMode.EMBEDDED_VIDEO
EMBEDDED_VIDEO_STREAM_SELECT -> PlayMode.EMBEDDED_VIDEO
FULLSCREEN_VIDEO -> PlayMode.FULLSCREEN_VIDEO
FULLSCREEN_VIDEO_CONTROLLER_UI -> PlayMode.FULLSCREEN_VIDEO
FULLSCREEN_VIDEO_CHAPTER_SELECT -> PlayMode.FULLSCREEN_VIDEO
FULLSCREEN_VIDEO_STREAM_SELECT -> PlayMode.FULLSCREEN_VIDEO
}
companion object { companion object {
fun fromPlayMode(playMode: PlayMode?) = fun fromPlayMode(playMode: PlayMode?) =

View File

@ -46,7 +46,7 @@ data class VideoPlayerUIState(
val DEFAULT = VideoPlayerUIState( val DEFAULT = VideoPlayerUIState(
// TODO: replace this with the placeholder state. // TODO: replace this with the placeholder state.
// The actual initial state upon starting to play is dictated by the NewPlayer instance // The actual initial state upon starting to play is dictated by the NewPlayer instance
uiMode = UIModeState.EMBEDDED_VIDEO, uiMode = UIModeState.PLACEHOLDER,
//uiMode = UIModeState.PLACEHOLDER, //uiMode = UIModeState.PLACEHOLDER,
playing = false, playing = false,
contentRatio = 16 / 9f, contentRatio = 16 / 9f,

View File

@ -28,7 +28,6 @@ import android.util.Log
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat.getSystemService import androidx.core.content.ContextCompat.getSystemService
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.media3.common.Player import androidx.media3.common.Player
@ -134,7 +133,6 @@ class VideoPlayerViewModelImpl @Inject constructor(
override fun onVideoSizeChanged(videoSize: androidx.media3.common.VideoSize) { override fun onVideoSizeChanged(videoSize: androidx.media3.common.VideoSize) {
super.onVideoSizeChanged(videoSize) super.onVideoSizeChanged(videoSize)
updateContentRatio(VideoSize.fromMedia3VideoSize(videoSize)) updateContentRatio(VideoSize.fromMedia3VideoSize(videoSize))
} }
@ -148,13 +146,13 @@ class VideoPlayerViewModelImpl @Inject constructor(
} }
}) })
} }
newPlayer?.let{ newPlayer -> newPlayer?.let { newPlayer ->
viewModelScope.launch { viewModelScope.launch {
while(true) { newPlayer.playMode.collect { newMode ->
newPlayer.playMode.collect { mode -> val currentMode = mutableUiState.value.uiMode.toPlayMode()
println("blub: $mode") if (currentMode != newMode) {
mutableUiState.update { mutableUiState.update {
it.copy(uiMode = UIModeState.fromPlayMode(mode)) it.copy(uiMode = UIModeState.fromPlayMode(newMode))
} }
} }
} }
@ -184,15 +182,15 @@ class VideoPlayerViewModelImpl @Inject constructor(
@RequiresApi(Build.VERSION_CODES.TIRAMISU) @RequiresApi(Build.VERSION_CODES.TIRAMISU)
override fun initUIState(instanceState: Bundle) { override fun initUIState(instanceState: Bundle) {
val uiState = val recoveredUiState =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) instanceState.getParcelable( if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) instanceState.getParcelable(
VIDEOPLAYER_UI_STATE, VideoPlayerUIState::class.java VIDEOPLAYER_UI_STATE, VideoPlayerUIState::class.java
) )
else instanceState.getParcelable(VIDEOPLAYER_UI_STATE) else instanceState.getParcelable(VIDEOPLAYER_UI_STATE)
uiState?.let { uiState -> if(recoveredUiState != null) {
mutableUiState.update { mutableUiState.update {
uiState recoveredUiState
} }
} }
} }
@ -349,17 +347,25 @@ class VideoPlayerViewModelImpl @Inject constructor(
callbackListeners.forEach { it?.onFullscreenToggle(false) } callbackListeners.forEach { it?.onFullscreenToggle(false) }
uiVisibilityJob?.cancel() uiVisibilityJob?.cancel()
finishFastSeek() finishFastSeek()
mutableUiState.update { updateUiMode(UIModeState.EMBEDDED_VIDEO)
it.copy(uiMode = UIModeState.EMBEDDED_VIDEO)
}
} }
override fun switchToFullscreen() { override fun switchToFullscreen() {
callbackListeners.forEach { it?.onFullscreenToggle(true) } callbackListeners.forEach { it?.onFullscreenToggle(true) }
uiVisibilityJob?.cancel() uiVisibilityJob?.cancel()
finishFastSeek() finishFastSeek()
mutableUiState.update { updateUiMode(UIModeState.FULLSCREEN_VIDEO)
it.copy(uiMode = UIModeState.FULLSCREEN_VIDEO) }
private fun updateUiMode(newState: UIModeState) {
val newPlayMode = newState.toPlayMode()
val currentPlayMode = mutableUiState.value.uiMode.toPlayMode()
if(newPlayMode != currentPlayMode) {
newPlayer?.setPlayMode(newPlayMode!!)
} else {
mutableUiState.update {
it.copy(uiMode = newState)
}
} }
} }