mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Don't show full playback overlay when pausing (#1144)
## Description Instead of displaying the full playback overlay when pausing via a button press, just show a short pause image in the center of the screen. Any button press that will pause triggers this including a remote Pause, Pause/Play, or one-click pause Enter. I decided to make this the standard behavior instead of a toggle because I think if the user intends to do something with the overlay (change subtitles, audio, etc), it's still straightforward to open the overlay, click the pause button (which is focused first), and then move to the desired option versus assuming click-to-pause will open the overlay so the user can move to the desired option. ### Related issues Closes #990 ### Testing Emulator ## Screenshots [pause_osd.webm](https://github.com/user-attachments/assets/8f908164-2a50-43f9-9e13-a463d2914e42) ## AI or LLM usage None
This commit is contained in:
parent
8f9b9813b7
commit
2d863e9c32
5 changed files with 113 additions and 8 deletions
|
|
@ -1,23 +1,23 @@
|
|||
package com.github.damontecres.wholphin.ui.playback
|
||||
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.ForwardingSimpleBasePlayer
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.github.damontecres.wholphin.preferences.PlaybackPreferences
|
||||
import com.github.damontecres.wholphin.preferences.skipBackOnResume
|
||||
import com.github.damontecres.wholphin.ui.seekBack
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import timber.log.Timber
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
class MediaSessionPlayer(
|
||||
player: Player,
|
||||
private val controllerViewState: ControllerViewState,
|
||||
private val playbackPreferences: PlaybackPreferences,
|
||||
) : ForwardingSimpleBasePlayer(player) {
|
||||
override fun handleSetPlayWhenReady(playWhenReady: Boolean): ListenableFuture<*> {
|
||||
Timber.v("handleSetPlayWhenReady: playWhenReady=$playWhenReady")
|
||||
if (!playWhenReady && player.isPlaying) {
|
||||
controllerViewState.showControls()
|
||||
} else if (playWhenReady) {
|
||||
if (playWhenReady) {
|
||||
playbackPreferences.skipBackOnResume?.let {
|
||||
player.seekBack(it)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
package com.github.damontecres.wholphin.ui.playback
|
||||
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.scaleIn
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.ui.compose.state.observeState
|
||||
import com.github.damontecres.wholphin.R
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
/**
|
||||
* Show an animated "pause" image whenever the player is paused
|
||||
*/
|
||||
@Composable
|
||||
fun PauseIndicator(
|
||||
player: Player,
|
||||
modifier: Modifier = Modifier,
|
||||
duration: Duration = 300.milliseconds,
|
||||
) {
|
||||
val state = rememberPauseState(player)
|
||||
var visible by remember { mutableStateOf(false) }
|
||||
LaunchedEffect(state.isPaused) {
|
||||
if (state.isPaused) visible = true
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter =
|
||||
scaleIn(
|
||||
animationSpec =
|
||||
tween(
|
||||
durationMillis = duration.inWholeMilliseconds.toInt(),
|
||||
),
|
||||
),
|
||||
exit = fadeOut(spring(stiffness = Spring.StiffnessMediumLow)),
|
||||
modifier = modifier,
|
||||
) {
|
||||
LaunchedEffect(Unit) {
|
||||
delay(duration)
|
||||
delay(50)
|
||||
visible = false
|
||||
}
|
||||
Image(
|
||||
modifier = Modifier.size(64.dp, 64.dp),
|
||||
painter = painterResource(id = R.drawable.baseline_pause_24),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remember when a player is paused
|
||||
*/
|
||||
@Composable
|
||||
fun rememberPauseState(player: Player): PauseState {
|
||||
val state = remember(player) { PauseState(player) }
|
||||
LaunchedEffect(player) { state.observe() }
|
||||
return state
|
||||
}
|
||||
|
||||
class PauseState(
|
||||
private val player: Player,
|
||||
) {
|
||||
var isPaused by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
internal suspend fun observe() {
|
||||
player
|
||||
.observeState(
|
||||
Player.EVENT_PLAYBACK_STATE_CHANGED,
|
||||
Player.EVENT_PLAY_WHEN_READY_CHANGED,
|
||||
Player.EVENT_AVAILABLE_COMMANDS_CHANGED,
|
||||
) {
|
||||
// Timber.v("isPaused=$isPaused, playWhenReady=${player.playWhenReady}, playbackState=${player.playbackState}")
|
||||
isPaused = !isPaused && // Not already paused, don't want to trigger more than once
|
||||
!player.playWhenReady && // Player is actually paused
|
||||
// Player could play if it was not paused, ie it is not stopped
|
||||
player.playbackState.let { it == Player.STATE_READY || it == Player.STATE_BUFFERING }
|
||||
}.observe()
|
||||
}
|
||||
}
|
||||
|
|
@ -55,9 +55,7 @@ class PlaybackKeyHandler(
|
|||
} else if (oneClickPause && isEnterKey(it)) {
|
||||
val wasPlaying = player.isPlaying
|
||||
Util.handlePlayPauseButtonAction(player)
|
||||
if (wasPlaying) {
|
||||
controllerViewState.showControls()
|
||||
} else {
|
||||
if (!wasPlaying) {
|
||||
skipBackOnResume?.let {
|
||||
player.seekBack(it)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -361,6 +361,15 @@ fun PlaybackPageContent(
|
|||
}
|
||||
}
|
||||
|
||||
if (!controllerViewState.controlsVisible && skipIndicatorDuration == 0L) {
|
||||
PauseIndicator(
|
||||
player = player,
|
||||
modifier =
|
||||
Modifier
|
||||
.align(Alignment.Center),
|
||||
)
|
||||
}
|
||||
|
||||
// The playback controls
|
||||
|
||||
PlaybackOverlay(
|
||||
|
|
|
|||
|
|
@ -260,7 +260,6 @@ class PlaybackViewModel
|
|||
val sessionPlayer =
|
||||
MediaSessionPlayer(
|
||||
player,
|
||||
controllerViewState,
|
||||
preferences.appPreferences.playbackPreferences,
|
||||
)
|
||||
mediaSession =
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue