diff --git a/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt b/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt index 226143a..ebe619f 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt @@ -32,8 +32,10 @@ enum class PlayMode { EMBEDDED_VIDEO, FULLSCREEN_VIDEO, PIP, - BACKGROUND, - AUDIO_FOREGROUND, + BACKGROUND_VIDEO, + BACKGROUND_AUDIO, + FULLSCREEN_AUDIO, + EMBEDDED_AUDIO } enum class RepeatMode { 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 705a75e..ad56ba6 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 @@ -44,8 +44,7 @@ interface NewPlayerViewModel { fun pause() fun prevStream() fun nextStream() - fun switchToFullscreen(embeddedUiConfig: EmbeddedUiConfig) - fun switchToEmbeddedView() + fun changeUiMode(newUiModeState: UIModeState, embeddedUiConfig: EmbeddedUiConfig) fun onBackPressed() fun showUi() fun hideUi() @@ -56,8 +55,6 @@ interface NewPlayerViewModel { fun finishFastSeek() fun brightnessChange(changeRate: Float, systemBrightness: Float) fun volumeChange(changeRate: Float) - fun openStreamSelection(selectChapter: Boolean, embeddedUiConfig: EmbeddedUiConfig) - fun closeStreamSelection() fun chapterSelected(chapterId: Int) fun streamSelected(streamId: Int) fun cycleRepeatMode() 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 a6fe1cc..9b94bce 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 @@ -1,6 +1,7 @@ package net.newpipe.newplayer.model import android.os.Bundle +import androidx.media3.common.util.UnstableApi import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow @@ -8,6 +9,7 @@ import kotlinx.coroutines.flow.asSharedFlow import net.newpipe.newplayer.NewPlayer import net.newpipe.newplayer.ui.ContentScale +@UnstableApi open class NewPlayerViewModelDummy : NewPlayerViewModel { override var newPlayer: NewPlayer? = null override val uiState = MutableStateFlow(NewPlayerUIState.DEFAULT) @@ -25,18 +27,10 @@ open class NewPlayerViewModelDummy : NewPlayerViewModel { println("dummy impl") } - override fun switchToEmbeddedView() { - println("dummy impl") - } - override fun onBackPressed() { println("dummy impl") } - override fun switchToFullscreen(embeddedUiConfig: EmbeddedUiConfig) { - println("dummy impl") - } - override fun showUi() { println("dummy impl") } @@ -73,14 +67,6 @@ open class NewPlayerViewModelDummy : NewPlayerViewModel { println("dummy impl") } - override fun openStreamSelection(selectChapter: Boolean, embeddedUiConfig: EmbeddedUiConfig) { - println("dummy impl") - } - - override fun closeStreamSelection() { - println("dummy impl") - } - override fun chapterSelected(chapterId: Int) { println("dummp impl chapter selected: $chapterId") } @@ -128,4 +114,8 @@ open class NewPlayerViewModelDummy : NewPlayerViewModel { override fun nextStream() { println("dummy impl") } + + 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 06760ee..84245af 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 @@ -150,7 +150,7 @@ class NewPlayerViewModelImpl @Inject constructor( mutableUiState.update { it.copy(playing = isPlaying, isLoading = false) } - if (isPlaying && uiState.value.uiMode.controllerUiVisible) { + if (isPlaying && uiState.value.uiMode.videoControllerUiVisible) { resetHideUiDelayedJob() } else { uiVisibilityJob?.cancel() @@ -329,6 +329,41 @@ class NewPlayerViewModelImpl @Inject constructor( } } + override fun changeUiMode(newUiModeState: UIModeState, embeddedUiConfig: EmbeddedUiConfig) { + if (!uiState.value.uiMode.fullscreen) { + this.embeddedUiConfig = embeddedUiConfig + } + + if (newUiModeState.isStreamSelect) { + resetPlaylistProgressUpdaterJob() + uiVisibilityJob?.cancel() + } + + if (newUiModeState.isChapterSelect) { + resetPlaylistProgressUpdaterJob() + uiVisibilityJob?.cancel() + } + + if ((uiState.value.uiMode.isStreamSelect || uiState.value.uiMode.isChapterSelect) + && (!newUiModeState.isStreamSelect && !newUiModeState.isChapterSelect) + ) { + playlistProgressUpdaterJob?.cancel() + progressUpdaterJob?.cancel() + } + + if(uiState.value.uiMode.fullscreen && !newUiModeState.fullscreen) { + uiVisibilityJob?.cancel() + finishFastSeek() + } + + if(!uiState.value.uiMode.fullscreen && newUiModeState.fullscreen) { + uiVisibilityJob?.cancel() + finishFastSeek() + } + + updateUiMode(newUiModeState) + } + override fun showUi() { mutableUiState.update { it.copy(uiMode = it.uiMode.getControllerUiVisibleState()) @@ -430,13 +465,13 @@ class NewPlayerViewModelImpl @Inject constructor( ) } - if (mutableUiState.value.uiMode.controllerUiVisible) { + if (mutableUiState.value.uiMode.videoControllerUiVisible) { resetHideUiDelayedJob() } } override fun finishFastSeek() { - if (mutableUiState.value.uiMode.controllerUiVisible) { + if (mutableUiState.value.uiMode.videoControllerUiVisible) { resetHideUiDelayedJob() } @@ -482,34 +517,6 @@ class NewPlayerViewModelImpl @Inject constructor( } } - override fun openStreamSelection(selectChapter: Boolean, embeddedUiConfig: EmbeddedUiConfig) { - uiVisibilityJob?.cancel() - if (!uiState.value.uiMode.fullscreen) { - this.embeddedUiConfig = embeddedUiConfig - } - updateUiMode( - if (selectChapter) uiState.value.uiMode.getChapterSelectUiState() - else uiState.value.uiMode.getStreamSelectUiState() - ) - if (selectChapter) { - resetProgressUpdatePeriodicallyJob() - } else { - resetPlaylistProgressUpdaterJob() - } - } - - override fun closeStreamSelection() { - playlistProgressUpdaterJob?.cancel() - progressUpdaterJob?.cancel() - updateUiMode(uiState.value.uiMode.getUiHiddenState()) - } - - override fun switchToEmbeddedView() { - uiVisibilityJob?.cancel() - finishFastSeek() - updateUiMode(UIModeState.EMBEDDED_VIDEO) - } - override fun onBackPressed() { val nextMode = uiState.value.uiMode.getNextModeWhenBackPressed() if (nextMode != null) { @@ -519,14 +526,6 @@ class NewPlayerViewModelImpl @Inject constructor( } } - override fun switchToFullscreen(embeddedUiConfig: EmbeddedUiConfig) { - uiVisibilityJob?.cancel() - finishFastSeek() - - this.embeddedUiConfig = embeddedUiConfig - updateUiMode(UIModeState.FULLSCREEN_VIDEO) - } - override fun chapterSelected(chapterId: Int) { newPlayer?.selectChapter(chapterId) } diff --git a/new-player/src/main/java/net/newpipe/newplayer/model/UIModeState.kt b/new-player/src/main/java/net/newpipe/newplayer/model/UIModeState.kt index bd9e8e8..d0eb606 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/model/UIModeState.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/model/UIModeState.kt @@ -33,7 +33,12 @@ enum class UIModeState { FULLSCREEN_VIDEO, FULLSCREEN_VIDEO_CONTROLLER_UI, FULLSCREEN_VIDEO_CHAPTER_SELECT, - FULLSCREEN_VIDEO_STREAM_SELECT; + FULLSCREEN_VIDEO_STREAM_SELECT, + + EMBEDDED_AUDIO, + FULLSCREEN_AUDIO, + AUDIO_CHAPTER_SELECT, + AUDIO_STREAM_SELECT; val fullscreen: Boolean get() = @@ -44,10 +49,13 @@ enum class UIModeState { FULLSCREEN_VIDEO_CONTROLLER_UI -> true FULLSCREEN_VIDEO_CHAPTER_SELECT -> true FULLSCREEN_VIDEO_STREAM_SELECT -> true + FULLSCREEN_AUDIO -> true + AUDIO_CHAPTER_SELECT -> true + AUDIO_STREAM_SELECT -> true else -> false } - val controllerUiVisible: Boolean + val videoControllerUiVisible: Boolean get() = when (this) { EMBEDDED_VIDEO_CONTROLLER_UI -> true @@ -60,6 +68,7 @@ enum class UIModeState { when (this) { EMBEDDED_VIDEO_STREAM_SELECT -> true FULLSCREEN_VIDEO_STREAM_SELECT -> true + AUDIO_STREAM_SELECT -> true else -> false } @@ -68,6 +77,7 @@ enum class UIModeState { when (this) { EMBEDDED_VIDEO_CHAPTER_SELECT -> true FULLSCREEN_VIDEO_CHAPTER_SELECT -> true + AUDIO_CHAPTER_SELECT -> true else -> false } @@ -125,6 +135,10 @@ enum class UIModeState { EMBEDDED_VIDEO_CHAPTER_SELECT -> EMBEDDED_VIDEO_STREAM_SELECT EMBEDDED_VIDEO_CONTROLLER_UI -> EMBEDDED_VIDEO_STREAM_SELECT + FULLSCREEN_AUDIO -> AUDIO_STREAM_SELECT + EMBEDDED_AUDIO -> AUDIO_STREAM_SELECT + AUDIO_CHAPTER_SELECT -> AUDIO_STREAM_SELECT + else -> this } @@ -138,6 +152,10 @@ enum class UIModeState { EMBEDDED_VIDEO_STREAM_SELECT -> EMBEDDED_VIDEO_CHAPTER_SELECT EMBEDDED_VIDEO_CONTROLLER_UI -> EMBEDDED_VIDEO_CHAPTER_SELECT + FULLSCREEN_AUDIO -> AUDIO_CHAPTER_SELECT + EMBEDDED_AUDIO -> AUDIO_CHAPTER_SELECT + AUDIO_STREAM_SELECT -> AUDIO_CHAPTER_SELECT + else -> this } @@ -151,6 +169,10 @@ enum class UIModeState { FULLSCREEN_VIDEO_CONTROLLER_UI -> PlayMode.FULLSCREEN_VIDEO FULLSCREEN_VIDEO_CHAPTER_SELECT -> PlayMode.FULLSCREEN_VIDEO FULLSCREEN_VIDEO_STREAM_SELECT -> PlayMode.FULLSCREEN_VIDEO + EMBEDDED_AUDIO -> PlayMode.EMBEDDED_AUDIO + FULLSCREEN_AUDIO -> PlayMode.FULLSCREEN_AUDIO + AUDIO_CHAPTER_SELECT -> PlayMode.FULLSCREEN_AUDIO + AUDIO_STREAM_SELECT -> PlayMode.FULLSCREEN_AUDIO } fun getNextModeWhenBackPressed() = when (this) { @@ -160,7 +182,13 @@ enum class UIModeState { FULLSCREEN_VIDEO_STREAM_SELECT -> FULLSCREEN_VIDEO FULLSCREEN_VIDEO_CHAPTER_SELECT -> FULLSCREEN_VIDEO FULLSCREEN_VIDEO_CONTROLLER_UI -> EMBEDDED_VIDEO - else -> null + PLACEHOLDER -> null + EMBEDDED_VIDEO -> null + EMBEDDED_VIDEO_CONTROLLER_UI -> null + EMBEDDED_AUDIO -> null + FULLSCREEN_AUDIO -> EMBEDDED_AUDIO + AUDIO_CHAPTER_SELECT -> FULLSCREEN_AUDIO + AUDIO_STREAM_SELECT -> FULLSCREEN_AUDIO } companion object { @@ -170,8 +198,10 @@ enum class UIModeState { PlayMode.EMBEDDED_VIDEO -> EMBEDDED_VIDEO PlayMode.FULLSCREEN_VIDEO -> FULLSCREEN_VIDEO PlayMode.PIP -> TODO() - PlayMode.BACKGROUND -> TODO() - PlayMode.AUDIO_FOREGROUND -> TODO() + PlayMode.BACKGROUND_VIDEO -> TODO() + PlayMode.BACKGROUND_AUDIO -> TODO() + PlayMode.FULLSCREEN_AUDIO -> FULLSCREEN_AUDIO + PlayMode.EMBEDDED_AUDIO -> EMBEDDED_AUDIO } } } \ No newline at end of file diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/LoadingPlaceholder.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/LoadingPlaceholder.kt index 1729a37..0d9e05d 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/LoadingPlaceholder.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/LoadingPlaceholder.kt @@ -40,7 +40,7 @@ import androidx.compose.ui.unit.dp import net.newpipe.newplayer.ui.theme.VideoPlayerTheme @Composable -fun VideoPlayerLoadingPlaceholder(aspectRatio: Float = 3F / 1F) { +fun LoadingPlaceholder(aspectRatio: Float = 3F / 1F) { Surface( modifier = Modifier .fillMaxWidth() @@ -63,6 +63,6 @@ fun VideoPlayerLoadingPlaceholder(aspectRatio: Float = 3F / 1F) { @Composable fun VideoPlayerLoaidingPlaceholderPreview() { VideoPlayerTheme { - VideoPlayerLoadingPlaceholder() + LoadingPlaceholder() } } \ No newline at end of file diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/NewPlayerUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/NewPlayerUI.kt index d99e724..fd899da 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/NewPlayerUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/NewPlayerUI.kt @@ -26,26 +26,16 @@ import android.util.Log import android.view.SurfaceView import androidx.activity.compose.BackHandler import androidx.annotation.OptIn -import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material3.Surface import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalView import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.viewinterop.AndroidView @@ -59,9 +49,8 @@ import androidx.media3.common.util.UnstableApi import net.newpipe.newplayer.model.UIModeState import net.newpipe.newplayer.model.NewPlayerViewModel import net.newpipe.newplayer.model.NewPlayerViewModelDummy -import net.newpipe.newplayer.ui.streamselect.StreamSelectUI +import net.newpipe.newplayer.ui.audioplayer.AudioPlayerUI import net.newpipe.newplayer.ui.theme.VideoPlayerTheme -import net.newpipe.newplayer.ui.videoplayer.VideoPlayerControllerUI import net.newpipe.newplayer.ui.videoplayer.VideoPlayerUi import net.newpipe.newplayer.utils.LockScreenOrientation import net.newpipe.newplayer.utils.getDefaultBrightness @@ -75,9 +64,9 @@ fun NewPlayerUI( viewModel: NewPlayerViewModel?, ) { if (viewModel == null) { - VideoPlayerLoadingPlaceholder() + LoadingPlaceholder() } else if (viewModel.newPlayer == null) { - VideoPlayerLoadingPlaceholder(viewModel.uiState.collectAsState().value.embeddedUiRatio) + LoadingPlaceholder(viewModel.uiState.collectAsState().value.embeddedUiRatio) } else { val uiState by viewModel.uiState.collectAsState() @@ -142,12 +131,22 @@ fun NewPlayerUI( ) } - // Set UI - if (uiState.uiMode == UIModeState.PLACEHOLDER) { - VideoPlayerLoadingPlaceholder(uiState.embeddedUiRatio) - } else { + if (uiState.uiMode == UIModeState.FULLSCREEN_VIDEO || + uiState.uiMode == UIModeState.FULLSCREEN_VIDEO_CONTROLLER_UI || + uiState.uiMode == UIModeState.FULLSCREEN_VIDEO_CHAPTER_SELECT || + uiState.uiMode == UIModeState.FULLSCREEN_VIDEO_STREAM_SELECT || + uiState.uiMode == UIModeState.EMBEDDED_VIDEO || + uiState.uiMode == UIModeState.EMBEDDED_VIDEO_CONTROLLER_UI || + uiState.uiMode == UIModeState.EMBEDDED_VIDEO_STREAM_SELECT || + uiState.uiMode == UIModeState.EMBEDDED_VIDEO_CHAPTER_SELECT + ) { VideoPlayerUi(viewModel = viewModel, uiState = uiState) + } else if (uiState.uiMode == UIModeState.FULLSCREEN_AUDIO) { + AudioPlayerUI(viewModel = viewModel, uiState = uiState) + } else { + LoadingPlaceholder(uiState.embeddedUiRatio) } + } } diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/audioplayer/AudioPlayerTopBar.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/audioplayer/AudioPlayerTopBar.kt index 1c9890d..6e7df58 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/audioplayer/AudioPlayerTopBar.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/audioplayer/AudioPlayerTopBar.kt @@ -40,6 +40,7 @@ import net.newpipe.newplayer.R import net.newpipe.newplayer.model.EmbeddedUiConfig import net.newpipe.newplayer.model.NewPlayerUIState import net.newpipe.newplayer.model.NewPlayerViewModel +import net.newpipe.newplayer.model.UIModeState import net.newpipe.newplayer.utils.getEmbeddedUiConfig @androidx.annotation.OptIn(UnstableApi::class) @@ -59,10 +60,7 @@ fun AudioPlayerTopBar( AnimatedVisibility(visible = uiState.chapters.isNotEmpty()) { IconButton( onClick = { - viewModel.openStreamSelection( - selectChapter = true, - embeddedUiConfig - ) + viewModel.changeUiMode(UIModeState.AUDIO_CHAPTER_SELECT, embeddedUiConfig) }, ) { Icon( @@ -74,10 +72,7 @@ fun AudioPlayerTopBar( AnimatedVisibility(visible = 1 < uiState.playList.size) { IconButton( onClick = { - viewModel.openStreamSelection( - selectChapter = false, - embeddedUiConfig - ) + viewModel.changeUiMode(UIModeState.AUDIO_STREAM_SELECT, embeddedUiConfig) }, ) { Icon( diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/streamselect/StreamSelectTopBar.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/streamselect/StreamSelectTopBar.kt index eda9589..4cec3c7 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/streamselect/StreamSelectTopBar.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/streamselect/StreamSelectTopBar.kt @@ -20,6 +20,7 @@ package net.newpipe.newplayer.ui.streamselect +import android.app.Activity import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.PlaylistAdd @@ -37,18 +38,21 @@ import androidx.compose.material3.TopAppBarDefaults.topAppBarColors import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.media3.common.util.UnstableApi import net.newpipe.newplayer.R import net.newpipe.newplayer.RepeatMode +import net.newpipe.newplayer.model.EmbeddedUiConfig import net.newpipe.newplayer.model.NewPlayerUIState import net.newpipe.newplayer.model.NewPlayerViewModel import net.newpipe.newplayer.model.NewPlayerViewModelDummy import net.newpipe.newplayer.ui.common.RepeatModeButton import net.newpipe.newplayer.ui.common.ShuffleModeButton import net.newpipe.newplayer.ui.theme.VideoPlayerTheme +import net.newpipe.newplayer.utils.getEmbeddedUiConfig import net.newpipe.newplayer.utils.getLocale import net.newpipe.newplayer.utils.getPlaylistDurationInMS import net.newpipe.newplayer.utils.getTimeStringFromMs @@ -62,6 +66,12 @@ fun StreamSelectTopBar( uiState: NewPlayerUIState ) { + val embeddedUiConfig = + if (LocalContext.current is Activity) + getEmbeddedUiConfig(activity = LocalContext.current as Activity) + else + EmbeddedUiConfig.DUMMY + TopAppBar(modifier = modifier, colors = topAppBarColors(containerColor = Color.Transparent), title = { @@ -91,7 +101,9 @@ fun StreamSelectTopBar( } IconButton( - onClick = viewModel::closeStreamSelection + onClick = { + viewModel.changeUiMode(uiState.uiMode.getUiHiddenState(), embeddedUiConfig) + } ) { Icon( imageVector = Icons.Filled.Close, diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/streamselect/StreamSelectUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/streamselect/StreamSelectUI.kt index b18a0b4..72124f5 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/streamselect/StreamSelectUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/streamselect/StreamSelectUI.kt @@ -20,6 +20,7 @@ package net.newpipe.newplayer.ui.streamselect +import android.app.Activity import androidx.annotation.OptIn import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -36,15 +37,18 @@ import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.media3.common.util.UnstableApi +import net.newpipe.newplayer.model.EmbeddedUiConfig import net.newpipe.newplayer.model.NewPlayerUIState import net.newpipe.newplayer.model.NewPlayerViewModel import net.newpipe.newplayer.model.NewPlayerViewModelDummy import net.newpipe.newplayer.ui.theme.VideoPlayerTheme import net.newpipe.newplayer.ui.videoplayer.STREAMSELECT_UI_BACKGROUND_COLOR import net.newpipe.newplayer.utils.ReorderHapticFeedbackType +import net.newpipe.newplayer.utils.getEmbeddedUiConfig import net.newpipe.newplayer.utils.getInsets import net.newpipe.newplayer.utils.rememberReorderHapticFeedback import sh.calvin.reorderable.ReorderableItem @@ -60,6 +64,12 @@ fun StreamSelectUI( uiState: NewPlayerUIState ) { val insets = getInsets() + + val embeddedUiConfig = if (LocalContext.current is Activity) + getEmbeddedUiConfig(activity = LocalContext.current as Activity) + else + EmbeddedUiConfig.DUMMY + Surface( modifier = Modifier.fillMaxSize(), color = STREAMSELECT_UI_BACKGROUND_COLOR @@ -72,8 +82,12 @@ fun StreamSelectUI( topBar = { if (isChapterSelect) { ChapterSelectTopBar( - onClose = - viewModel::closeStreamSelection + onClose = { + viewModel.changeUiMode( + uiState.uiMode.getUiHiddenState(), + embeddedUiConfig + ) + } ) } else { StreamSelectTopBar(viewModel = viewModel, uiState = uiState) @@ -142,7 +156,9 @@ fun ReorderableStreamItemsList( verticalArrangement = Arrangement.spacedBy(5.dp), state = lazyListState ) { - itemsIndexed(uiState.playList, key = { _, item -> item.mediaId.toLong() }) { index, playlistItem -> + itemsIndexed( + uiState.playList, + key = { _, item -> item.mediaId.toLong() }) { index, playlistItem -> ReorderableItem( state = reorderableLazyListState, key = playlistItem.mediaId.toLong() diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/VideoPlayerControllerUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/VideoPlayerControllerUI.kt index 93d5f41..300fab2 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/VideoPlayerControllerUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/VideoPlayerControllerUI.kt @@ -60,7 +60,7 @@ fun VideoPlayerControllerUI( val insets = getInsets() - AnimatedVisibility(uiState.uiMode.controllerUiVisible) { + AnimatedVisibility(uiState.uiMode.videoControllerUiVisible) { Surface( modifier = Modifier.fillMaxSize(), color = CONTROLLER_UI_BACKGROUND_COLOR ) {} @@ -82,7 +82,7 @@ fun VideoPlayerControllerUI( } } - AnimatedVisibility(uiState.uiMode.controllerUiVisible) { + AnimatedVisibility(uiState.uiMode.videoControllerUiVisible) { AnimatedVisibility(visible = !uiState.isLoading) { Box(modifier = Modifier.fillMaxSize()) { diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/controller/BottomUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/controller/BottomUI.kt index f10e2ac..f7d7214 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/controller/BottomUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/controller/BottomUI.kt @@ -77,10 +77,13 @@ fun BottomUI( } IconButton( - onClick = if (uiState.uiMode.fullscreen) viewModel::switchToEmbeddedView - else { - { // <- head of lambda ... yea kotlin is weird - viewModel.switchToFullscreen(embeddedUiConfig) + onClick = if (uiState.uiMode.fullscreen) { + { + viewModel.changeUiMode(UIModeState.EMBEDDED_VIDEO, embeddedUiConfig) + } + } else { + { + viewModel.changeUiMode(UIModeState.FULLSCREEN_VIDEO, embeddedUiConfig) } } ) { @@ -94,7 +97,6 @@ fun BottomUI( } - /////////////////////////////////////////////////////////////////// // Preview /////////////////////////////////////////////////////////////////// diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/controller/TopUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/controller/TopUI.kt index 29c5d23..7c19d64 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/controller/TopUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/controller/TopUI.kt @@ -53,6 +53,7 @@ import net.newpipe.newplayer.R import net.newpipe.newplayer.model.NewPlayerUIState import net.newpipe.newplayer.model.NewPlayerViewModel import net.newpipe.newplayer.model.NewPlayerViewModelDummy +import net.newpipe.newplayer.model.UIModeState import net.newpipe.newplayer.ui.theme.VideoPlayerTheme import net.newpipe.newplayer.ui.theme.video_player_onSurface import net.newpipe.newplayer.utils.getEmbeddedUiConfig @@ -103,7 +104,12 @@ fun TopUI( } AnimatedVisibility(visible = uiState.chapters.isNotEmpty()) { IconButton( - onClick = { viewModel.openStreamSelection(selectChapter = true, embeddedUiConfig) }, + onClick = { + viewModel.changeUiMode( + uiState.uiMode.getChapterSelectUiState(), + embeddedUiConfig + ) + } ) { Icon( imageVector = Icons.AutoMirrored.Filled.MenuBook, @@ -114,8 +120,8 @@ fun TopUI( AnimatedVisibility(visible = 1 < uiState.playList.size) { IconButton( onClick = { - viewModel.openStreamSelection( - selectChapter = false, + viewModel.changeUiMode( + uiState.uiMode.getStreamSelectUiState(), embeddedUiConfig ) }, 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 36896f7..1b62a6b 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 @@ -23,6 +23,7 @@ package net.newpipe.newplayer.ui.videoplayer.gesture_ui import android.app.Activity import android.util.Log +import androidx.annotation.OptIn import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize @@ -38,14 +39,17 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview +import androidx.media3.common.util.UnstableApi import net.newpipe.newplayer.model.NewPlayerUIState import net.newpipe.newplayer.model.NewPlayerViewModel import net.newpipe.newplayer.model.NewPlayerViewModelDummy +import net.newpipe.newplayer.model.UIModeState import net.newpipe.newplayer.ui.theme.VideoPlayerTheme import net.newpipe.newplayer.utils.getEmbeddedUiConfig private const val TAG = "EmbeddedGestureUI" +@OptIn(UnstableApi::class) @Composable fun EmbeddedGestureUI( modifier: Modifier = Modifier, viewModel: NewPlayerViewModel, uiState: NewPlayerUIState @@ -66,7 +70,7 @@ fun EmbeddedGestureUI( // this check is there to allow a temporary move up in the downward gesture if (downwardMovementMode == false) { - viewModel.switchToFullscreen(embeddedUiConfig) + viewModel.changeUiMode(UIModeState.FULLSCREEN_VIDEO, embeddedUiConfig) } else { viewModel.embeddedDraggedDown(movement.y) } @@ -78,7 +82,7 @@ fun EmbeddedGestureUI( } val defaultOnRegularTap = { - if (uiState.uiMode.controllerUiVisible) { + if (uiState.uiMode.videoControllerUiVisible) { viewModel.hideUi() } else { viewModel.showUi() @@ -151,6 +155,7 @@ fun EmbeddedGestureUI( } +@OptIn(UnstableApi::class) @Preview(device = "spec:width=600px,height=400px,dpi=440,orientation=landscape") @Composable fun EmbeddedGestureUIPreview() { @@ -159,10 +164,6 @@ fun EmbeddedGestureUIPreview() { EmbeddedGestureUI( modifier = Modifier, viewModel = object : NewPlayerViewModelDummy() { - override fun switchToEmbeddedView() { - println("switch to fullscreen") - } - override fun embeddedDraggedDown(offset: Float) { println("embedded view dragged down by $offset") } 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 0c567d8..aa0fdf1 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 @@ -22,6 +22,7 @@ package net.newpipe.newplayer.ui.videoplayer.gesture_ui import android.app.Activity +import androidx.annotation.OptIn import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring @@ -44,17 +45,20 @@ import androidx.compose.ui.graphics.Color 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.UIModeState import net.newpipe.newplayer.model.NewPlayerUIState import net.newpipe.newplayer.model.NewPlayerViewModel import net.newpipe.newplayer.model.NewPlayerViewModelDummy import net.newpipe.newplayer.ui.theme.VideoPlayerTheme import net.newpipe.newplayer.utils.getDefaultBrightness +import net.newpipe.newplayer.utils.getEmbeddedUiConfig private enum class IndicatorMode { NONE, VOLUME_INDICATOR_VISSIBLE, BRIGHTNESS_INDICATOR_VISSIBLE } +@OptIn(UnstableApi::class) @Composable fun FullscreenGestureUI( modifier: Modifier = Modifier, viewModel: NewPlayerViewModel, uiState: NewPlayerUIState @@ -69,7 +73,7 @@ fun FullscreenGestureUI( } val defaultOnRegularTap = { - if (uiState.uiMode.controllerUiVisible) { + if (uiState.uiMode.videoControllerUiVisible) { viewModel.hideUi() } else { viewModel.showUi() @@ -79,6 +83,7 @@ fun FullscreenGestureUI( val activity = LocalContext.current as Activity val defaultBrightness = getDefaultBrightness(activity) + val embeddedUiConfig = getEmbeddedUiConfig(activity = activity) Box(modifier = modifier.onGloballyPositioned { coordinates -> heightPx = coordinates.size.height.toFloat() @@ -119,7 +124,7 @@ fun FullscreenGestureUI( onRegularTap = defaultOnRegularTap, onMovement = { movement -> if (0 < movement.y) { - viewModel.switchToEmbeddedView() + viewModel.changeUiMode(UIModeState.EMBEDDED_VIDEO, embeddedUiConfig) } }, onMultiTap = { count -> @@ -215,6 +220,7 @@ fun IndicatorAnimation(modifier: Modifier, visible: Boolean, content: @Composabl } +@OptIn(UnstableApi::class) @Preview(device = "spec:width=1080px,height=600px,dpi=440,orientation=landscape") @Composable fun FullscreenGestureUIPreview() { @@ -231,6 +237,7 @@ fun FullscreenGestureUIPreview() { } } +@OptIn(UnstableApi::class) @Preview(device = "spec:parent=pixel_8,orientation=landscape") @Composable fun FullscreenGestureUIPreviewInteractive() { @@ -255,6 +262,7 @@ fun FullscreenGestureUIPreviewInteractive() { Surface(modifier = Modifier.wrapContentSize(), color = Color.Gray) { FullscreenGestureUI( modifier = Modifier, + @OptIn(UnstableApi::class) object : NewPlayerViewModelDummy() { override fun hideUi() { uiVisible = false