From f750fa8b4bc66a10c6489a5caa655fe6b39dd5bf Mon Sep 17 00:00:00 2001 From: Christian Schabesberger Date: Mon, 23 Sep 2024 19:11:41 +0200 Subject: [PATCH] make progress update more smooth --- .../newplayer/model/NewPlayerViewModel.kt | 1 + .../model/NewPlayerViewModelDummy.kt | 1 + .../newplayer/model/NewPlayerViewModelImpl.kt | 21 ++++++++++---- .../net/newpipe/newplayer/ui/NewPlayerUI.kt | 9 ++++++ .../newplayer/ui/audioplayer/AudioPlayerUI.kt | 29 ++++++++++++++++++- .../ui/videoplayer/controller/BottomUI.kt | 13 +++++++-- .../java/net/newpipe/newplayer/utils/utils.kt | 15 ++++++---- 7 files changed, 76 insertions(+), 13 deletions(-) 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 ad56ba6..9f57a41 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 @@ -38,6 +38,7 @@ interface NewPlayerViewModel { var contentFitMode: ContentScale val embeddedPlayerDraggedDownBy: SharedFlow val onBackPressed: SharedFlow + var deviceInPowerSaveMode: Boolean fun initUIState(instanceState: Bundle) fun play() 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 9b94bce..35808b2 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 @@ -18,6 +18,7 @@ open class NewPlayerViewModelDummy : NewPlayerViewModel { override var contentFitMode = ContentScale.FIT_INSIDE override val embeddedPlayerDraggedDownBy = MutableSharedFlow().asSharedFlow() override val onBackPressed: SharedFlow = MutableSharedFlow().asSharedFlow() + override var deviceInPowerSaveMode: Boolean = false override fun initUIState(instanceState: Bundle) { println("dummy impl") 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 76c1175..422414f 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 @@ -49,6 +49,7 @@ import net.newpipe.newplayer.NewPlayer import net.newpipe.newplayer.NewPlayerException 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" @@ -136,6 +137,15 @@ class NewPlayerViewModelImpl @Inject constructor( private var mutableOnBackPressed = MutableSharedFlow() override val onBackPressed: SharedFlow = mutableOnBackPressed.asSharedFlow() + override var deviceInPowerSaveMode: Boolean = false + get() = field + set(value) { + field = value + if (progressUpdaterJob?.isActive == true) { + resetProgressUpdatePeriodicallyJob() + } + } + private fun installNewPlayer() { newPlayer?.let { newPlayer -> viewModelScope.launch { @@ -334,8 +344,9 @@ class NewPlayerViewModelImpl @Inject constructor( this.embeddedUiConfig = embeddedUiConfig } - if(!(newUiModeState == UIModeState.EMBEDDED_VIDEO_CONTROLLER_UI || - newUiModeState == UIModeState.FULLSCREEN_VIDEO_CONTROLLER_UI)) { + if (!(newUiModeState == UIModeState.EMBEDDED_VIDEO_CONTROLLER_UI || + newUiModeState == UIModeState.FULLSCREEN_VIDEO_CONTROLLER_UI) + ) { uiVisibilityJob?.cancel() } else { resetHideUiDelayedJob() @@ -368,10 +379,10 @@ class NewPlayerViewModelImpl @Inject constructor( } private fun resetHideUiDelayedJob() { - var ex:Exception? = null + var ex: Exception? = null try { throw Exception() - } catch(e: Exception) { + } catch (e: Exception) { ex = e } uiVisibilityJob?.cancel() @@ -387,7 +398,7 @@ class NewPlayerViewModelImpl @Inject constructor( progressUpdaterJob = viewModelScope.launch { while (true) { updateProgressOnce() - delay(1000) + delay(if (deviceInPowerSaveMode) 1000 else 1000 / 30/*fps*/) } } } 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 5935eae..426242b 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 @@ -22,6 +22,7 @@ package net.newpipe.newplayer.ui import android.app.Activity import android.content.pm.ActivityInfo +import android.os.Build import android.util.Log import android.view.SurfaceView import androidx.activity.compose.BackHandler @@ -54,6 +55,7 @@ import net.newpipe.newplayer.ui.theme.VideoPlayerTheme import net.newpipe.newplayer.ui.videoplayer.VideoPlayerUi import net.newpipe.newplayer.utils.LockScreenOrientation import net.newpipe.newplayer.utils.getDefaultBrightness +import net.newpipe.newplayer.utils.isInPowerSaveMode import net.newpipe.newplayer.utils.setScreenBrightness private const val TAG = "VideoPlayerUI" @@ -112,6 +114,13 @@ fun NewPlayerUI( } } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val isInPowerSaveMode = isInPowerSaveMode() + LaunchedEffect(key1 = isInPowerSaveMode) { + viewModel.deviceInPowerSaveMode = isInPowerSaveMode + } + } + if (uiState.uiMode.fitScreenRotation) { if (uiState.contentRatio < 1) { LockScreenOrientation(orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/audioplayer/AudioPlayerUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/audioplayer/AudioPlayerUI.kt index d367b63..df1a5ea 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/audioplayer/AudioPlayerUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/audioplayer/AudioPlayerUI.kt @@ -29,6 +29,7 @@ import androidx.compose.animation.fadeOut import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -59,6 +60,8 @@ import net.newpipe.newplayer.ui.selection_ui.StreamSelectUI import net.newpipe.newplayer.ui.theme.VideoPlayerTheme import net.newpipe.newplayer.utils.Thumbnail import net.newpipe.newplayer.utils.getInsets +import net.newpipe.newplayer.utils.getLocale +import net.newpipe.newplayer.utils.getTimeStringFromMs private val UI_ENTER_ANIMATION = fadeIn(tween(200)) @@ -75,6 +78,9 @@ fun lightAudioControlButtonColorScheme() = ButtonDefaults.buttonColors().copy( @Composable fun AudioPlayerUI(viewModel: NewPlayerViewModel, uiState: NewPlayerUIState) { val insets = getInsets() + + val locale = getLocale()!! + Box( modifier = Modifier .fillMaxSize() @@ -164,7 +170,25 @@ fun AudioPlayerUI(viewModel: NewPlayerViewModel, uiState: NewPlayerUIState) { .fillMaxSize() .weight(0.2f) ) + NewPlayerSeeker(viewModel = viewModel, uiState = uiState) + + Row() { + Text( + getTimeStringFromMs( + uiState.playbackPositionInMs, + getLocale() ?: locale + ) + ) + Box(modifier = Modifier.fillMaxWidth().weight(1f)) + Text( + getTimeStringFromMs( + uiState.durationInMs, + getLocale() ?: locale + ) + ) + } + Box( modifier = Modifier .fillMaxSize() @@ -195,6 +219,9 @@ fun AudioPlayerUI(viewModel: NewPlayerViewModel, uiState: NewPlayerUIState) { @Composable fun AudioPlayerUIPreview() { VideoPlayerTheme { - AudioPlayerUI(viewModel = NewPlayerViewModelDummy(), uiState = NewPlayerUIState.DUMMY) + AudioPlayerUI( + viewModel = NewPlayerViewModelDummy(), + uiState = NewPlayerUIState.DUMMY.copy(uiMode = UIModeState.FULLSCREEN_AUDIO) + ) } } \ No newline at end of file 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 f7d7214..5c1a567 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 @@ -23,7 +23,9 @@ package net.newpipe.newplayer.ui.videoplayer.controller import android.app.Activity import androidx.annotation.OptIn import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Fullscreen import androidx.compose.material.icons.filled.FullscreenExit @@ -38,6 +40,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import androidx.media3.common.util.UnstableApi import net.newpipe.newplayer.R import net.newpipe.newplayer.model.EmbeddedUiConfig @@ -67,7 +70,13 @@ fun BottomUI( val locale = getLocale()!! Text(getTimeStringFromMs(uiState.playbackPositionInMs, getLocale() ?: locale)) - NewPlayerSeeker(modifier = Modifier.weight(1F), viewModel = viewModel, uiState = uiState) + NewPlayerSeeker( + modifier = Modifier + .weight(1F) + .padding(start = 4.dp, end = 4.dp), + viewModel = viewModel, + uiState = uiState + ) Text(getTimeStringFromMs(uiState.durationInMs, getLocale() ?: locale)) @@ -112,7 +121,7 @@ fun VideoPlayerControllerBottomUIPreview() { viewModel = NewPlayerViewModelDummy(), uiState = NewPlayerUIState.DUMMY.copy( uiMode = UIModeState.FULLSCREEN_VIDEO_CONTROLLER_UI, - seekerPosition = 0.2f, + seekerPosition = 0.0f, playbackPositionInMs = 3 * 60 * 1000, bufferedPercentage = 0.4f ), 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 5053a81..b0c3c3f 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 @@ -24,25 +24,23 @@ import android.annotation.SuppressLint import android.app.Activity import android.content.Context import android.content.ContextWrapper -import android.graphics.drawable.shapes.Shape import android.net.Uri +import android.os.Build +import android.os.PowerManager import android.view.WindowManager import androidx.annotation.OptIn -import androidx.compose.animation.core.withInfiniteAnimationFrameMillis +import androidx.annotation.RequiresApi import androidx.compose.foundation.Image import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.displayCutout -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.union import androidx.compose.foundation.layout.waterfall import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView @@ -184,6 +182,13 @@ fun Thumbnail( } } +@RequiresApi(Build.VERSION_CODES.LOLLIPOP) +@Composable +fun isInPowerSaveMode() = + (LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager) + .isPowerSaveMode + + @OptIn(UnstableApi::class) fun getPlaylistDurationInMS(playlist: List) : Long { var duration = 0L