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,
|
focusEnabled: Boolean = true,
|
||||||
) {
|
) {
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
if (focusEnabled) {
|
||||||
|
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
||||||
|
}
|
||||||
Box(
|
Box(
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
modifier =
|
modifier =
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
package com.github.damontecres.wholphin.ui.playback
|
package com.github.damontecres.wholphin.ui.playback
|
||||||
|
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.annotation.Dimension
|
import androidx.annotation.Dimension
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.core.LinearEasing
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
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.background
|
||||||
import androidx.compose.foundation.focusable
|
import androidx.compose.foundation.focusable
|
||||||
import androidx.compose.foundation.layout.Box
|
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.PlayerBackend
|
||||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.wholphin.preferences.skipBackOnResume
|
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.AspectRatios
|
||||||
import com.github.damontecres.wholphin.ui.LocalImageUrlService
|
import com.github.damontecres.wholphin.ui.LocalImageUrlService
|
||||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||||
|
|
@ -216,6 +220,18 @@ fun PlaybackPageContent(
|
||||||
}
|
}
|
||||||
|
|
||||||
val presentationState = rememberPresentationState(player, false)
|
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 =
|
val scaledModifier =
|
||||||
Modifier.resizeWithContentScale(contentScale, presentationState.videoSizeDp)
|
Modifier.resizeWithContentScale(contentScale, presentationState.videoSizeDp)
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
@ -349,6 +365,21 @@ fun PlaybackPageContent(
|
||||||
) {
|
) {
|
||||||
LoadingPage(focusEnabled = false)
|
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
|
// 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) }
|
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