restructure NewPlayerUI and VideoPlayerUI

This commit is contained in:
Christian Schabesberger 2024-09-21 13:21:35 +02:00
parent a222a7c9a0
commit 25593c8ed6
3 changed files with 132 additions and 68 deletions

View File

@ -53,7 +53,7 @@ import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import net.newpipe.newplayer.model.UIModeState
@ -62,6 +62,7 @@ import net.newpipe.newplayer.model.NewPlayerViewModelDummy
import net.newpipe.newplayer.ui.streamselect.StreamSelectUI
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
import net.newpipe.newplayer.utils.setScreenBrightness
@ -79,11 +80,6 @@ fun NewPlayerUI(
VideoPlayerLoadingPlaceholder(viewModel.uiState.collectAsState().value.embeddedUiRatio)
} else {
val uiState by viewModel.uiState.collectAsState()
val exoPlayer by viewModel.newPlayer?.exoPlayer!!.collectAsState()
var lifecycle by remember {
mutableStateOf(Lifecycle.Event.ON_CREATE)
}
val activity = LocalContext.current as Activity
val view = LocalView.current
@ -93,8 +89,6 @@ fun NewPlayerUI(
windowInsetsController.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
val lifecycleOwner = LocalLifecycleOwner.current
val defaultBrightness = getDefaultBrightness(activity)
// Setup fullscreen
@ -141,24 +135,6 @@ fun NewPlayerUI(
}
}
// Prepare stuff for the SurfaceView to which the video will be rendered
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
lifecycle = event
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
val displayMetrics = activity.resources.displayMetrics
val screenRatio =
displayMetrics.widthPixels.toFloat() / displayMetrics.heightPixels.toFloat()
LaunchedEffect(key1 = uiState.brightness) {
Log.d(TAG, "New Brightnes: ${uiState.brightness}")
setScreenBrightness(
@ -170,48 +146,7 @@ fun NewPlayerUI(
if (uiState.uiMode == UIModeState.PLACEHOLDER) {
VideoPlayerLoadingPlaceholder(uiState.embeddedUiRatio)
} else {
Surface(
modifier = Modifier.then(
if (uiState.uiMode.fullscreen) Modifier.fillMaxSize()
else Modifier
.fillMaxWidth()
.aspectRatio(uiState.embeddedUiRatio)
), color = Color.Black
) {
exoPlayer?.let { exoPlayer ->
Box(contentAlignment = Alignment.Center) {
PlaySurface(
player = exoPlayer,
lifecycle = lifecycle,
fitMode = uiState.contentFitMode,
uiRatio = if (uiState.uiMode.fullscreen) screenRatio
else uiState.embeddedUiRatio,
contentRatio = uiState.contentRatio
)
}
}
// 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)
}
}
VideoPlayerUi(viewModel = viewModel, uiState = uiState)
}
}
}

View File

@ -20,6 +20,7 @@
package net.newpipe.newplayer.ui.videoplayer
import androidx.annotation.OptIn
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.defaultMinSize
@ -38,6 +39,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.media3.common.util.UnstableApi
import net.newpipe.newplayer.model.NewPlayerUIState
import net.newpipe.newplayer.model.NewPlayerViewModel
import net.newpipe.newplayer.model.NewPlayerViewModelDummy
@ -50,6 +52,7 @@ import net.newpipe.newplayer.utils.getInsets
val CONTROLLER_UI_BACKGROUND_COLOR = Color(0x75000000)
val STREAMSELECT_UI_BACKGROUND_COLOR = Color(0xba000000)
@OptIn(UnstableApi::class)
@Composable
fun VideoPlayerControllerUI(
viewModel: NewPlayerViewModel, uiState: NewPlayerUIState
@ -136,6 +139,7 @@ fun PreviewBackgroundSurface(
// Preview
///////////////////////////////////////////////////////////////////
@OptIn(UnstableApi::class)
@Preview(device = "spec:width=1080px,height=600px,dpi=440,orientation=landscape")
@Composable
fun VideoPlayerControllerUIPreviewEmbedded() {
@ -146,6 +150,7 @@ fun VideoPlayerControllerUIPreviewEmbedded() {
}
}
@OptIn(UnstableApi::class)
@Preview(device = "spec:width=2340px,height=1080px,dpi=440,orientation=landscape")
@Composable
fun VideoPlayerControllerUIPreviewLandscape() {
@ -156,6 +161,7 @@ fun VideoPlayerControllerUIPreviewLandscape() {
}
}
@OptIn(UnstableApi::class)
@Preview(device = "spec:width=2340px,height=1080px,dpi=440,orientation=portrait")
@Composable
fun VideoPlayerControllerUIPreviewPortrait() {

View File

@ -0,0 +1,123 @@
/* NewPlayer
*
* @author Christian Schabesberger
*
* Copyright (C) NewPipe e.V. 2024 <code(at)newpipe-ev.de>
*
* NewPlayer is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPlayer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPlayer. If not, see <http://www.gnu.org/licenses/>.
*/
package net.newpipe.newplayer.ui.videoplayer
import android.app.Activity
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.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.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.lifecycle.Lifecycle
import androidx.media3.common.util.UnstableApi
import net.newpipe.newplayer.model.NewPlayerUIState
import net.newpipe.newplayer.model.NewPlayerViewModel
import net.newpipe.newplayer.ui.PlaySurface
import net.newpipe.newplayer.ui.streamselect.StreamSelectUI
import androidx.lifecycle.LifecycleEventObserver
@OptIn(UnstableApi::class)
@Composable
fun VideoPlayerUi(viewModel: NewPlayerViewModel, uiState: NewPlayerUIState) {
val exoPlayer by viewModel.newPlayer?.exoPlayer!!.collectAsState()
var lifecycle by remember {
mutableStateOf(Lifecycle.Event.ON_CREATE)
}
val activity = LocalContext.current as Activity
val displayMetrics = activity.resources.displayMetrics
val screenRatio =
displayMetrics.widthPixels.toFloat() / displayMetrics.heightPixels.toFloat()
val lifecycleOwner = LocalLifecycleOwner.current
// Prepare stuff for the SurfaceView to which the video will be rendered
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
lifecycle = event
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
Surface(
modifier = Modifier.then(
if (uiState.uiMode.fullscreen) Modifier.fillMaxSize()
else Modifier
.fillMaxWidth()
.aspectRatio(uiState.embeddedUiRatio)
), color = Color.Black
) {
exoPlayer?.let { exoPlayer ->
Box(contentAlignment = Alignment.Center) {
PlaySurface(
player = exoPlayer,
lifecycle = lifecycle,
fitMode = uiState.contentFitMode,
uiRatio = if (uiState.uiMode.fullscreen) screenRatio
else uiState.embeddedUiRatio,
contentRatio = uiState.contentRatio
)
}
}
// 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)
}
}
}