diff --git a/.kotlin/errors/errors-1724916413735.log b/.kotlin/errors/errors-1724916413735.log new file mode 100644 index 0000000..9300f96 --- /dev/null +++ b/.kotlin/errors/errors-1724916413735.log @@ -0,0 +1,4 @@ +kotlin version: 2.0.20-Beta2 +error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output: + 1. Kotlin compile daemon is ready + 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 38f08ac..7959f1a 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 @@ -55,13 +55,39 @@ enum class UIModeState { else -> false } - val systemUiVisible: Boolean + val isStreamSelect: Boolean get() = - when (this) { + when(this) { + EMBEDDED_VIDEO_STREAM_SELECT -> true + FULLSCREEN_VIDEO_STREAM_SELECT -> true + else -> false + } + + val isChapterSelect: Boolean + get() = + when(this) { + EMBEDDED_VIDEO_CHAPTER_SELECT -> true + FULLSCREEN_VIDEO_CHAPTER_SELECT -> true + else -> false + } + + val systemInsetsVisible: Boolean + get() = + when(this) { FULLSCREEN_VIDEO -> false else -> true } + val fitScreenRotation: Boolean + get() = + when(this) { + FULLSCREEN_VIDEO -> true + FULLSCREEN_VIDEO_CONTROLLER_UI -> true + FULLSCREEN_VIDEO_CHAPTER_SELECT -> true + FULLSCREEN_VIDEO_STREAM_SELECT -> true + else -> false + } + // STATE TRANSITIONS fun getControllerUiVisibleState() = @@ -72,7 +98,6 @@ enum class UIModeState { else -> this } - fun getUiHiddenState() = when (this) { FULLSCREEN_VIDEO -> FULLSCREEN_VIDEO diff --git a/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModel.kt b/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModel.kt index 83facf8..d251be0 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModel.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModel.kt @@ -43,7 +43,7 @@ interface VideoPlayerViewModel { fun pause() fun prevStream() fun nextStream() - fun switchToFullscreen() + fun switchToFullscreen(embeddedUiConfig: EmbeddedUiConfig) fun switchToEmbeddedView() fun showUi() fun hideUi() @@ -54,5 +54,6 @@ interface VideoPlayerViewModel { fun finishFastSeek() fun brightnessChange(changeRate: Float, systemBrightness: Float) fun volumeChange(changeRate: Float) - fun onReportEmbeddedConfig(embeddedUiConfig: EmbeddedUiConfig?) + fun openStreamSelection(selectChapter: Boolean) + fun closeStreamSelection() } \ No newline at end of file diff --git a/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModelImpl.kt b/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModelImpl.kt index 9e52922..8a60ab5 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModelImpl.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModelImpl.kt @@ -65,12 +65,15 @@ class VideoPlayerViewModelImpl @Inject constructor( private var uiVisibilityJob: Job? = null private var progressUpdaterJob: Job? = null + // this is necesary to restore the embedded view UI configuration when returning from fullscreen + private var embeddedUiConfig: EmbeddedUiConfig? = null + private val audioManager = getSystemService(application.applicationContext, AudioManager::class.java)!! init { - val soundVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC).toFloat() / - audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC).toFloat() + val soundVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) + .toFloat() / audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC).toFloat() mutableUiState.update { it.copy(soundVolume = soundVolume) } @@ -154,7 +157,7 @@ class VideoPlayerViewModelImpl @Inject constructor( override fun onPlaylistMetadataChanged(mediaMetadata: MediaMetadata) { super.onPlaylistMetadataChanged(mediaMetadata) updatePlaylist() - } + } }) } newPlayer?.let { newPlayer -> @@ -163,7 +166,8 @@ class VideoPlayerViewModelImpl @Inject constructor( val currentMode = mutableUiState.value.uiMode.toPlayMode() if (currentMode != newMode) { mutableUiState.update { - it.copy(uiMode = UIModeState.fromPlayMode(newMode)) + it.copy(uiMode = UIModeState.fromPlayMode(newMode), + embeddedUiConfig = embeddedUiConfig) } } } @@ -353,18 +357,20 @@ class VideoPlayerViewModelImpl @Inject constructor( } } - override fun onReportEmbeddedConfig(embeddedUiConfig: EmbeddedUiConfig?) { - if (embeddedUiConfig == null) { - mutableUiState.update { - it.copy(embeddedUiConfig = null) - } - } else { - if (uiState.value.embeddedUiConfig == null) { - println("gurken: ${embeddedUiConfig}") - mutableUiState.update { - it.copy(embeddedUiConfig = embeddedUiConfig) - } - } + override fun openStreamSelection(selectChapter: Boolean) { + mutableUiState.update { + it.copy( + uiMode = if (selectChapter) it.uiMode.getChapterSelectUiState() + else it.uiMode.getStreamSelectUiState() + ) + } + } + + override fun closeStreamSelection() { + mutableUiState.update { + it.copy( + uiMode = it.uiMode.getUiHiddenState() + ) } } @@ -374,10 +380,11 @@ class VideoPlayerViewModelImpl @Inject constructor( updateUiMode(UIModeState.EMBEDDED_VIDEO) } - override fun switchToFullscreen() { + override fun switchToFullscreen(embeddedUiConfig: EmbeddedUiConfig) { uiVisibilityJob?.cancel() finishFastSeek() + this.embeddedUiConfig = embeddedUiConfig updateUiMode(UIModeState.FULLSCREEN_VIDEO) } @@ -405,8 +412,7 @@ class VideoPlayerViewModelImpl @Inject constructor( newPlayer?.let { newPlayer -> viewModelScope.launch { val playlist = getPlaylistItemsFromItemList( - newPlayer.playlist, - newPlayer.repository + newPlayer.playlist, newPlayer.repository ) mutableUiState.update { it.copy(playList = playlist) diff --git a/new-player/src/main/java/net/newpipe/newplayer/model/ViewoPlayerViewModelDummy.kt b/new-player/src/main/java/net/newpipe/newplayer/model/ViewoPlayerViewModelDummy.kt index b9ef5ea..67024e0 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/model/ViewoPlayerViewModelDummy.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/model/ViewoPlayerViewModelDummy.kt @@ -29,7 +29,7 @@ open class VideoPlayerViewModelDummy : VideoPlayerViewModel { println("dummy impl") } - override fun switchToFullscreen() { + override fun switchToFullscreen(embeddedUiConfig: EmbeddedUiConfig) { println("dummy impl") } @@ -69,7 +69,11 @@ open class VideoPlayerViewModelDummy : VideoPlayerViewModel { println("dummy impl") } - override fun onReportEmbeddedConfig(embeddedUiConfig: EmbeddedUiConfig?) { + override fun openStreamSelection(selectChapter: Boolean) { + println("dummy impl") + } + + override fun closeStreamSelection() { println("dummy impl") } diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/VideoPlayerControllerUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/VideoPlayerControllerUI.kt index 05817c3..9797fc8 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/VideoPlayerControllerUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/VideoPlayerControllerUI.kt @@ -119,7 +119,9 @@ fun VideoPlayerControllerUI( .align(Alignment.TopStart) .fillMaxWidth() .defaultMinSize(minHeight = 45.dp) - .padding(top = 4.dp, start = 16.dp, end = 16.dp) + .padding(top = 4.dp, start = 16.dp, end = 16.dp), + viewModel = viewModel, + uiState = uiState ) BottomUI( diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/VideoPlayerUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/VideoPlayerUI.kt index c3f08bd..1952af6 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/VideoPlayerUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/VideoPlayerUI.kt @@ -24,6 +24,7 @@ import android.app.Activity import android.content.pm.ActivityInfo import android.util.Log import android.view.SurfaceView +import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxHeight @@ -37,7 +38,6 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -57,6 +57,7 @@ import net.newpipe.newplayer.model.EmbeddedUiConfig import net.newpipe.newplayer.model.VideoPlayerViewModel import net.newpipe.newplayer.model.VideoPlayerViewModelDummy import net.newpipe.newplayer.ui.theme.VideoPlayerTheme +import net.newpipe.newplayer.ui.videoplayer.StreamSelectUI import net.newpipe.newplayer.utils.LockScreenOrientation import net.newpipe.newplayer.utils.getDefaultBrightness import net.newpipe.newplayer.utils.setScreenBrightness @@ -89,37 +90,17 @@ fun VideoPlayerUI( val lifecycleOwner = LocalLifecycleOwner.current val defaultBrightness = getDefaultBrightness(activity) - val screenOrientation = activity.requestedOrientation // Setup fullscreen - var embeddedUiConfig: EmbeddedUiConfig? by rememberSaveable { - mutableStateOf(null) - } - - LaunchedEffect(uiState.uiMode.fullscreen) { - if (uiState.uiMode.fullscreen) { - viewModel.onReportEmbeddedConfig( - EmbeddedUiConfig( - WindowCompat.getInsetsController( - window, - view - ).isAppearanceLightStatusBars, - defaultBrightness, - screenOrientation, - ) - ) - } else { - viewModel.onReportEmbeddedConfig(null) - } - } - LaunchedEffect(uiState.uiMode.fullscreen) { if (uiState.uiMode.fullscreen) { WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = false } else { + println("gurken dings") uiState.embeddedUiConfig?.let { + println("gurken bumbs") WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = it.systemBarInLightMode } @@ -128,17 +109,16 @@ fun VideoPlayerUI( // setup immersive mode LaunchedEffect( - key1 = uiState.uiMode.controllerUiVisible, - key2 = uiState.uiMode.fullscreen + key1 = uiState.uiMode.systemInsetsVisible, ) { - if (uiState.uiMode.fullscreen && !uiState.uiMode.systemUiVisible) { + if (uiState.uiMode.fullscreen && !uiState.uiMode.systemInsetsVisible) { windowInsetsController.hide(WindowInsetsCompat.Type.systemBars()) } else { windowInsetsController.show(WindowInsetsCompat.Type.systemBars()) } } - if (uiState.uiMode.fullscreen) { + if (uiState.uiMode.fitScreenRotation) { if (uiState.contentRatio < 1) { LockScreenOrientation(orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) } else { @@ -195,9 +175,19 @@ fun VideoPlayerUI( ) } + // the checks if VideoPlayerControllerUI should be visible or not are done by + // The VideoPlayerControllerUI composable itself. This is because Visibility of + // the controller is more complicated than just using a simple if statement. VideoPlayerControllerUI( viewModel, uiState = uiState ) + + AnimatedVisibility(visible = uiState.uiMode.isStreamSelect) { + StreamSelectUI(viewModel = viewModel, uiState = uiState, isChapterSelect = false) + } + AnimatedVisibility(visible = uiState.uiMode.isChapterSelect) { + StreamSelectUI(viewModel = viewModel, uiState = uiState, isChapterSelect = true) + } } } } diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/BottomUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/BottomUI.kt index 854732b..b4a1561 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/BottomUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/BottomUI.kt @@ -20,6 +20,7 @@ package net.newpipe.newplayer.ui.videoplayer +import android.app.Activity import android.app.LocaleConfig import android.icu.text.DecimalFormat import androidx.compose.foundation.layout.Arrangement @@ -38,6 +39,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.core.os.ConfigurationCompat @@ -51,6 +53,7 @@ import net.newpipe.newplayer.ui.seeker.Seeker import net.newpipe.newplayer.ui.seeker.SeekerColors import net.newpipe.newplayer.ui.seeker.SeekerDefaults import net.newpipe.newplayer.ui.theme.VideoPlayerTheme +import net.newpipe.newplayer.utils.getEmbeddedUiConfig import net.newpipe.newplayer.utils.getLocale import net.newpipe.newplayer.utils.getTimeStringFromMs import java.util.Locale @@ -79,9 +82,14 @@ fun BottomUI( Text(getTimeStringFromMs(uiState.durationInMs, getLocale() ?: Locale.US)) + val embeddedUiConfig = getEmbeddedUiConfig(LocalContext.current as Activity) IconButton( onClick = if (uiState.uiMode.fullscreen) viewModel::switchToEmbeddedView - else viewModel::switchToFullscreen + else { + { // <- head of lambda ... yea kotlin is weird + viewModel.switchToFullscreen(embeddedUiConfig) + } + } ) { Icon( imageVector = if (uiState.uiMode.fullscreen) Icons.Filled.FullscreenExit diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/StreamSelectUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/StreamSelectUI.kt index 5776b15..dd3d45b 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/StreamSelectUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/StreamSelectUI.kt @@ -80,7 +80,7 @@ fun StreamSelectUI( topBar = { if (isChapterSelect) { ChapterSelectTopBar(onClose = { - TODO("implement me") + viewModel.closeStreamSelection() }) } else { StreamSelectTopBar() diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/TopUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/TopUI.kt index 40f7f2f..41ab8cd 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/TopUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/TopUI.kt @@ -44,11 +44,18 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import net.newpipe.newplayer.R +import net.newpipe.newplayer.model.VideoPlayerUIState +import net.newpipe.newplayer.model.VideoPlayerViewModel +import net.newpipe.newplayer.model.VideoPlayerViewModelDummy import net.newpipe.newplayer.ui.theme.VideoPlayerTheme import net.newpipe.newplayer.ui.theme.video_player_onSurface @Composable -fun TopUI(modifier: Modifier) { +fun TopUI( + modifier: Modifier, + viewModel: VideoPlayerViewModel, + uiState: VideoPlayerUIState +) { Row( modifier = modifier, verticalAlignment = Alignment.CenterVertically, @@ -81,7 +88,7 @@ fun TopUI(modifier: Modifier) { ) } IconButton( - onClick = { /*TODO*/ }, + onClick = { viewModel.openStreamSelection(selectChapter = true) }, ) { Icon( imageVector = Icons.AutoMirrored.Filled.MenuBook, @@ -89,7 +96,7 @@ fun TopUI(modifier: Modifier) { ) } IconButton( - onClick = { /*TODO*/ }, + onClick = { viewModel.openStreamSelection(selectChapter = false) }, ) { Icon( imageVector = Icons.AutoMirrored.Filled.List, @@ -109,7 +116,7 @@ fun TopUI(modifier: Modifier) { fun VideoPlayerControllerTopUIPreview() { VideoPlayerTheme { Surface(color = Color.Black) { - TopUI(modifier = Modifier) + TopUI(modifier = Modifier, VideoPlayerViewModelDummy(), VideoPlayerUIState.DEFAULT) } } } \ No newline at end of file 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 a2dc000..957bef6 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 @@ -21,6 +21,7 @@ package net.newpipe.newplayer.ui.videoplayer.gesture_ui +import android.app.Activity import android.util.Log import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row @@ -35,11 +36,13 @@ 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.tooling.preview.Preview import net.newpipe.newplayer.model.VideoPlayerUIState import net.newpipe.newplayer.model.VideoPlayerViewModel import net.newpipe.newplayer.model.VideoPlayerViewModelDummy import net.newpipe.newplayer.ui.theme.VideoPlayerTheme +import net.newpipe.newplayer.utils.getEmbeddedUiConfig private const val TAG = "EmbeddedGestureUI" @@ -52,6 +55,8 @@ fun EmbeddedGestureUI( mutableStateOf(false) } + val embeddedUiConfig = getEmbeddedUiConfig(LocalContext.current as Activity) + val handleMovement = { movement: TouchedPosition -> Log.d(TAG, "${movement.x}:${movement.y}") if (0 < movement.y) { @@ -61,7 +66,7 @@ fun EmbeddedGestureUI( // this check is there to allow a temporary move up in the downward gesture if (downwardMovementMode == false) { - viewModel.switchToFullscreen() + viewModel.switchToFullscreen(embeddedUiConfig) } else { viewModel.embeddedDraggedDown(movement.y) } diff --git a/new-player/src/main/java/net/newpipe/newplayer/utils/utils.kt b/new-player/src/main/java/net/newpipe/newplayer/utils/utils.kt index c04be3a..c4d0281 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/utils/utils.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/utils/utils.kt @@ -31,7 +31,10 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView import androidx.core.os.ConfigurationCompat +import androidx.core.view.WindowCompat +import net.newpipe.newplayer.model.EmbeddedUiConfig import java.util.Locale @Composable @@ -44,14 +47,14 @@ fun LockScreenOrientation(orientation: Int) { } @SuppressLint("NewApi") -fun getDefaultBrightness(activity: Activity) : Float { +fun getDefaultBrightness(activity: Activity): Float { val window = activity.window val layout = window.attributes as WindowManager.LayoutParams - return if(layout.screenBrightness < 0) 0.5f else layout.screenBrightness + return if (layout.screenBrightness < 0) 0.5f else layout.screenBrightness } @SuppressLint("NewApi") -fun setScreenBrightness(value:Float, activity: Activity) { +fun setScreenBrightness(value: Float, activity: Activity) { val window = activity.window val layout = window.attributes as WindowManager.LayoutParams layout.screenBrightness = value @@ -72,6 +75,24 @@ fun getLocale(): Locale? { return ConfigurationCompat.getLocales(configuration).get(0) } +@Composable +@ReadOnlyComposable +fun getEmbeddedUiConfig(activity: Activity): EmbeddedUiConfig { + val window = activity.window + val view = LocalView.current + + val isLightStatusBar = WindowCompat.getInsetsController( + window, + view + ).isAppearanceLightStatusBars + val screenOrientation = activity.requestedOrientation + val defaultBrightness = getDefaultBrightness(activity) + return EmbeddedUiConfig( + systemBarInLightMode = isLightStatusBar, + brightness = defaultBrightness, + screenOrientation = screenOrientation + ) +} private const val HOURS_PER_DAY = 24 private const val MINUTES_PER_HOUR = 60