make progress update more smooth
This commit is contained in:
parent
642e5e78db
commit
f750fa8b4b
|
@ -38,6 +38,7 @@ interface NewPlayerViewModel {
|
||||||
var contentFitMode: ContentScale
|
var contentFitMode: ContentScale
|
||||||
val embeddedPlayerDraggedDownBy: SharedFlow<Float>
|
val embeddedPlayerDraggedDownBy: SharedFlow<Float>
|
||||||
val onBackPressed: SharedFlow<Unit>
|
val onBackPressed: SharedFlow<Unit>
|
||||||
|
var deviceInPowerSaveMode: Boolean
|
||||||
|
|
||||||
fun initUIState(instanceState: Bundle)
|
fun initUIState(instanceState: Bundle)
|
||||||
fun play()
|
fun play()
|
||||||
|
|
|
@ -18,6 +18,7 @@ open class NewPlayerViewModelDummy : NewPlayerViewModel {
|
||||||
override var contentFitMode = ContentScale.FIT_INSIDE
|
override var contentFitMode = ContentScale.FIT_INSIDE
|
||||||
override val embeddedPlayerDraggedDownBy = MutableSharedFlow<Float>().asSharedFlow()
|
override val embeddedPlayerDraggedDownBy = MutableSharedFlow<Float>().asSharedFlow()
|
||||||
override val onBackPressed: SharedFlow<Unit> = MutableSharedFlow<Unit>().asSharedFlow()
|
override val onBackPressed: SharedFlow<Unit> = MutableSharedFlow<Unit>().asSharedFlow()
|
||||||
|
override var deviceInPowerSaveMode: Boolean = false
|
||||||
|
|
||||||
override fun initUIState(instanceState: Bundle) {
|
override fun initUIState(instanceState: Bundle) {
|
||||||
println("dummy impl")
|
println("dummy impl")
|
||||||
|
|
|
@ -49,6 +49,7 @@ import net.newpipe.newplayer.NewPlayer
|
||||||
import net.newpipe.newplayer.NewPlayerException
|
import net.newpipe.newplayer.NewPlayerException
|
||||||
import net.newpipe.newplayer.RepeatMode
|
import net.newpipe.newplayer.RepeatMode
|
||||||
import net.newpipe.newplayer.ui.ContentScale
|
import net.newpipe.newplayer.ui.ContentScale
|
||||||
|
import net.newpipe.newplayer.utils.isInPowerSaveMode
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
|
|
||||||
val VIDEOPLAYER_UI_STATE = "video_player_ui_state"
|
val VIDEOPLAYER_UI_STATE = "video_player_ui_state"
|
||||||
|
@ -136,6 +137,15 @@ class NewPlayerViewModelImpl @Inject constructor(
|
||||||
private var mutableOnBackPressed = MutableSharedFlow<Unit>()
|
private var mutableOnBackPressed = MutableSharedFlow<Unit>()
|
||||||
override val onBackPressed: SharedFlow<Unit> = mutableOnBackPressed.asSharedFlow()
|
override val onBackPressed: SharedFlow<Unit> = mutableOnBackPressed.asSharedFlow()
|
||||||
|
|
||||||
|
override var deviceInPowerSaveMode: Boolean = false
|
||||||
|
get() = field
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
if (progressUpdaterJob?.isActive == true) {
|
||||||
|
resetProgressUpdatePeriodicallyJob()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun installNewPlayer() {
|
private fun installNewPlayer() {
|
||||||
newPlayer?.let { newPlayer ->
|
newPlayer?.let { newPlayer ->
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
@ -334,8 +344,9 @@ class NewPlayerViewModelImpl @Inject constructor(
|
||||||
this.embeddedUiConfig = embeddedUiConfig
|
this.embeddedUiConfig = embeddedUiConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!(newUiModeState == UIModeState.EMBEDDED_VIDEO_CONTROLLER_UI ||
|
if (!(newUiModeState == UIModeState.EMBEDDED_VIDEO_CONTROLLER_UI ||
|
||||||
newUiModeState == UIModeState.FULLSCREEN_VIDEO_CONTROLLER_UI)) {
|
newUiModeState == UIModeState.FULLSCREEN_VIDEO_CONTROLLER_UI)
|
||||||
|
) {
|
||||||
uiVisibilityJob?.cancel()
|
uiVisibilityJob?.cancel()
|
||||||
} else {
|
} else {
|
||||||
resetHideUiDelayedJob()
|
resetHideUiDelayedJob()
|
||||||
|
@ -368,10 +379,10 @@ class NewPlayerViewModelImpl @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resetHideUiDelayedJob() {
|
private fun resetHideUiDelayedJob() {
|
||||||
var ex:Exception? = null
|
var ex: Exception? = null
|
||||||
try {
|
try {
|
||||||
throw Exception()
|
throw Exception()
|
||||||
} catch(e: Exception) {
|
} catch (e: Exception) {
|
||||||
ex = e
|
ex = e
|
||||||
}
|
}
|
||||||
uiVisibilityJob?.cancel()
|
uiVisibilityJob?.cancel()
|
||||||
|
@ -387,7 +398,7 @@ class NewPlayerViewModelImpl @Inject constructor(
|
||||||
progressUpdaterJob = viewModelScope.launch {
|
progressUpdaterJob = viewModelScope.launch {
|
||||||
while (true) {
|
while (true) {
|
||||||
updateProgressOnce()
|
updateProgressOnce()
|
||||||
delay(1000)
|
delay(if (deviceInPowerSaveMode) 1000 else 1000 / 30/*fps*/)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ package net.newpipe.newplayer.ui
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.SurfaceView
|
import android.view.SurfaceView
|
||||||
import androidx.activity.compose.BackHandler
|
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.ui.videoplayer.VideoPlayerUi
|
||||||
import net.newpipe.newplayer.utils.LockScreenOrientation
|
import net.newpipe.newplayer.utils.LockScreenOrientation
|
||||||
import net.newpipe.newplayer.utils.getDefaultBrightness
|
import net.newpipe.newplayer.utils.getDefaultBrightness
|
||||||
|
import net.newpipe.newplayer.utils.isInPowerSaveMode
|
||||||
import net.newpipe.newplayer.utils.setScreenBrightness
|
import net.newpipe.newplayer.utils.setScreenBrightness
|
||||||
|
|
||||||
private const val TAG = "VideoPlayerUI"
|
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.uiMode.fitScreenRotation) {
|
||||||
if (uiState.contentRatio < 1) {
|
if (uiState.contentRatio < 1) {
|
||||||
LockScreenOrientation(orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
|
LockScreenOrientation(orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
|
||||||
|
|
|
@ -29,6 +29,7 @@ import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
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.ui.theme.VideoPlayerTheme
|
||||||
import net.newpipe.newplayer.utils.Thumbnail
|
import net.newpipe.newplayer.utils.Thumbnail
|
||||||
import net.newpipe.newplayer.utils.getInsets
|
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))
|
private val UI_ENTER_ANIMATION = fadeIn(tween(200))
|
||||||
|
@ -75,6 +78,9 @@ fun lightAudioControlButtonColorScheme() = ButtonDefaults.buttonColors().copy(
|
||||||
@Composable
|
@Composable
|
||||||
fun AudioPlayerUI(viewModel: NewPlayerViewModel, uiState: NewPlayerUIState) {
|
fun AudioPlayerUI(viewModel: NewPlayerViewModel, uiState: NewPlayerUIState) {
|
||||||
val insets = getInsets()
|
val insets = getInsets()
|
||||||
|
|
||||||
|
val locale = getLocale()!!
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
@ -164,7 +170,25 @@ fun AudioPlayerUI(viewModel: NewPlayerViewModel, uiState: NewPlayerUIState) {
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.weight(0.2f)
|
.weight(0.2f)
|
||||||
)
|
)
|
||||||
|
|
||||||
NewPlayerSeeker(viewModel = viewModel, uiState = uiState)
|
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(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
@ -195,6 +219,9 @@ fun AudioPlayerUI(viewModel: NewPlayerViewModel, uiState: NewPlayerUIState) {
|
||||||
@Composable
|
@Composable
|
||||||
fun AudioPlayerUIPreview() {
|
fun AudioPlayerUIPreview() {
|
||||||
VideoPlayerTheme {
|
VideoPlayerTheme {
|
||||||
AudioPlayerUI(viewModel = NewPlayerViewModelDummy(), uiState = NewPlayerUIState.DUMMY)
|
AudioPlayerUI(
|
||||||
|
viewModel = NewPlayerViewModelDummy(),
|
||||||
|
uiState = NewPlayerUIState.DUMMY.copy(uiMode = UIModeState.FULLSCREEN_AUDIO)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -23,7 +23,9 @@ package net.newpipe.newplayer.ui.videoplayer.controller
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Fullscreen
|
import androidx.compose.material.icons.filled.Fullscreen
|
||||||
import androidx.compose.material.icons.filled.FullscreenExit
|
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.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import net.newpipe.newplayer.R
|
import net.newpipe.newplayer.R
|
||||||
import net.newpipe.newplayer.model.EmbeddedUiConfig
|
import net.newpipe.newplayer.model.EmbeddedUiConfig
|
||||||
|
@ -67,7 +70,13 @@ fun BottomUI(
|
||||||
val locale = getLocale()!!
|
val locale = getLocale()!!
|
||||||
Text(getTimeStringFromMs(uiState.playbackPositionInMs, getLocale() ?: locale))
|
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))
|
Text(getTimeStringFromMs(uiState.durationInMs, getLocale() ?: locale))
|
||||||
|
|
||||||
|
@ -112,7 +121,7 @@ fun VideoPlayerControllerBottomUIPreview() {
|
||||||
viewModel = NewPlayerViewModelDummy(),
|
viewModel = NewPlayerViewModelDummy(),
|
||||||
uiState = NewPlayerUIState.DUMMY.copy(
|
uiState = NewPlayerUIState.DUMMY.copy(
|
||||||
uiMode = UIModeState.FULLSCREEN_VIDEO_CONTROLLER_UI,
|
uiMode = UIModeState.FULLSCREEN_VIDEO_CONTROLLER_UI,
|
||||||
seekerPosition = 0.2f,
|
seekerPosition = 0.0f,
|
||||||
playbackPositionInMs = 3 * 60 * 1000,
|
playbackPositionInMs = 3 * 60 * 1000,
|
||||||
bufferedPercentage = 0.4f
|
bufferedPercentage = 0.4f
|
||||||
),
|
),
|
||||||
|
|
|
@ -24,25 +24,23 @@ import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.graphics.drawable.shapes.Shape
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.PowerManager
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.compose.animation.core.withInfiniteAnimationFrameMillis
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.displayCutout
|
import androidx.compose.foundation.layout.displayCutout
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.systemBars
|
import androidx.compose.foundation.layout.systemBars
|
||||||
import androidx.compose.foundation.layout.union
|
import androidx.compose.foundation.layout.union
|
||||||
import androidx.compose.foundation.layout.waterfall
|
import androidx.compose.foundation.layout.waterfall
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.layout.ContentScale
|
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalView
|
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)
|
@OptIn(UnstableApi::class)
|
||||||
fun getPlaylistDurationInMS(playlist: List<MediaItem>) : Long {
|
fun getPlaylistDurationInMS(playlist: List<MediaItem>) : Long {
|
||||||
var duration = 0L
|
var duration = 0L
|
||||||
|
|
Loading…
Reference in New Issue