mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Show loading indicator when video is buffering (#1441)
## Description Shows a loading indicator and slightly dim the screen when playback is buffering. Showing the indicator is delayed in case the buffering doesn't take very long. Dev note: the changes to `MpvPlayer` need to be ported to wholphin-extensions as well. ### Related issues Closes #1411 ### Testing Emulator ## Screenshots [buffering.webm](https://github.com/user-attachments/assets/dd848696-d6de-4f68-94e4-901bb5db1185) ## AI or LLM usage None
This commit is contained in:
parent
903c45dfb8
commit
8177700e23
4 changed files with 87 additions and 2 deletions
|
|
@ -38,7 +38,9 @@ fun LoadingPage(
|
|||
focusEnabled: Boolean = true,
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
if (focusEnabled) {
|
||||
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
||||
}
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier =
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
package com.github.damontecres.wholphin.ui.playback
|
||||
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.annotation.Dimension
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
|
@ -72,6 +75,7 @@ import com.github.damontecres.wholphin.preferences.AssPlaybackMode
|
|||
import com.github.damontecres.wholphin.preferences.PlayerBackend
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.preferences.skipBackOnResume
|
||||
import com.github.damontecres.wholphin.ui.AppColors
|
||||
import com.github.damontecres.wholphin.ui.AspectRatios
|
||||
import com.github.damontecres.wholphin.ui.LocalImageUrlService
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
|
|
@ -216,6 +220,18 @@ fun PlaybackPageContent(
|
|||
}
|
||||
|
||||
val presentationState = rememberPresentationState(player, false)
|
||||
val playbackState by rememberPlaybackState(player)
|
||||
var showBuffering by remember { mutableStateOf(false) }
|
||||
LaunchedEffect(playbackState) {
|
||||
if (playbackState == PlaybackState.BUFFERING) {
|
||||
// Delay before showing the loading indicator
|
||||
// So if buffering is quick, the UI won't flash
|
||||
delay(250)
|
||||
showBuffering = true
|
||||
} else {
|
||||
showBuffering = false
|
||||
}
|
||||
}
|
||||
val scaledModifier =
|
||||
Modifier.resizeWithContentScale(contentScale, presentationState.videoSizeDp)
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
|
@ -349,6 +365,21 @@ fun PlaybackPageContent(
|
|||
) {
|
||||
LoadingPage(focusEnabled = false)
|
||||
}
|
||||
} else {
|
||||
AnimatedVisibility(
|
||||
visible = showBuffering,
|
||||
enter = fadeIn(tween(easing = LinearEasing)),
|
||||
exit = fadeOut(tween(easing = LinearEasing)),
|
||||
modifier = Modifier.matchParentSize(),
|
||||
) {
|
||||
LoadingPage(
|
||||
focusEnabled = false,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.background(AppColors.TransparentBlack25),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// If D-pad skipping, show the amount skipped in an animation
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
package com.github.damontecres.wholphin.ui.playback
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.listenTo
|
||||
|
||||
/**
|
||||
* Remembers the [Player]'s state as it changes. Useful for changing UI if the player is buffering.
|
||||
*
|
||||
* @see Player.State
|
||||
* @see PlaybackState
|
||||
*/
|
||||
@Composable
|
||||
fun rememberPlaybackState(player: Player): State<PlaybackState> {
|
||||
val state = remember(player) { mutableStateOf(getPlaybackState(player.playbackState)) }
|
||||
LaunchedEffect(player) {
|
||||
player.listenTo(Player.EVENT_PLAYBACK_STATE_CHANGED) {
|
||||
state.value = getPlaybackState(player.playbackState)
|
||||
}
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
private fun getPlaybackState(
|
||||
@Player.State value: Int,
|
||||
): PlaybackState = PlaybackState.entries.first { it.value == value }
|
||||
|
||||
/**
|
||||
* Represents [Player.State] integers as an Enum
|
||||
*/
|
||||
enum class PlaybackState(
|
||||
@param:Player.State val value: Int,
|
||||
) {
|
||||
IDLE(Player.STATE_IDLE),
|
||||
BUFFERING(Player.STATE_BUFFERING),
|
||||
READY(Player.STATE_READY),
|
||||
ENDED(Player.STATE_ENDED),
|
||||
}
|
||||
|
|
@ -620,6 +620,16 @@ class MpvPlayer(
|
|||
}
|
||||
notifyListeners(EVENT_IS_PLAYING_CHANGED) { onIsPlayingChanged(!value) }
|
||||
}
|
||||
|
||||
MPVProperty.PAUSED_FOR_CACHE -> {
|
||||
Timber.v("paused-for-cache %s", value)
|
||||
playbackState.update {
|
||||
it.copy(
|
||||
state = if (value) STATE_BUFFERING else it.state,
|
||||
)
|
||||
}
|
||||
notifyListeners(EVENT_PLAYBACK_STATE_CHANGED) { onPlaybackStateChanged(playbackState.load().state) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue