feat: Implement auto-hide delay for TV player controls

This commit is contained in:
2026-04-08 20:19:04 +02:00
parent 98c936aea6
commit b41a9c24a2
2 changed files with 56 additions and 28 deletions

View File

@@ -45,6 +45,8 @@ import hu.bbara.purefin.tv.player.components.TvPlayerQueuePanel
import hu.bbara.purefin.tv.player.components.TvTrackPanelType
import hu.bbara.purefin.tv.player.components.TvTrackSelectionPanel
private const val TV_CONTROLS_AUTO_HIDE_MS = 5_000L
@OptIn(UnstableApi::class)
@Composable
fun TvPlayerScreen(
@@ -82,12 +84,36 @@ fun TvPlayerScreen(
LaunchedEffect(uiState.isPlaying) {
if (uiState.isPlaying && controlsVisible) {
viewModel.toggleControlsVisibility()
viewModel.showControls(TV_CONTROLS_AUTO_HIDE_MS)
}
}
val hiddenControlFocusRequester = remember { FocusRequester() }
val controlsFocusRequester = remember { FocusRequester() }
val showTvControls: () -> Unit = {
viewModel.showControls(TV_CONTROLS_AUTO_HIDE_MS)
}
val togglePlayPauseAndShowControls: () -> Unit = {
viewModel.togglePlayPause(TV_CONTROLS_AUTO_HIDE_MS)
}
val seekAndShowControls: (Long) -> Unit = { positionMs ->
viewModel.seekTo(positionMs)
showTvControls()
}
val seekByAndShowControls: (Long) -> Unit = { deltaMs ->
viewModel.seekBy(deltaMs)
showTvControls()
}
val seekToLiveEdgeAndShowControls: () -> Unit = {
viewModel.seekToLiveEdge()
showTvControls()
}
val nextAndShowControls: () -> Unit = {
viewModel.next(TV_CONTROLS_AUTO_HIDE_MS)
}
val previousAndShowControls: () -> Unit = {
viewModel.previous(TV_CONTROLS_AUTO_HIDE_MS)
}
LaunchedEffect(controlsVisible) {
if (controlsVisible) {
@@ -106,23 +132,22 @@ fun TvPlayerScreen(
if (!controlsVisible && event.type == KeyEventType.KeyDown) {
when (event.key) {
Key.DirectionLeft -> {
viewModel.seekBy(-10_000)
seekByAndShowControls(-10_000)
true
}
Key.DirectionRight -> {
viewModel.seekBy(10_000)
seekByAndShowControls(10_000)
true
}
Key.DirectionUp, Key.DirectionDown -> {
viewModel.showControls()
showTvControls()
true
}
Key.DirectionCenter, Key.Enter -> {
viewModel.togglePlayPause()
viewModel.showControls()
togglePlayPauseAndShowControls()
true
}
@@ -157,12 +182,12 @@ fun TvPlayerScreen(
modifier = Modifier.fillMaxSize(),
uiState = uiState,
focusRequester = controlsFocusRequester,
onPlayPause = { viewModel.togglePlayPause() },
onSeek = { viewModel.seekTo(it) },
onSeekRelative = { viewModel.seekBy(it) },
onSeekLiveEdge = { viewModel.seekToLiveEdge() },
onNext = { viewModel.next() },
onPrevious = { viewModel.previous() },
onPlayPause = togglePlayPauseAndShowControls,
onSeek = seekAndShowControls,
onSeekRelative = seekByAndShowControls,
onSeekLiveEdge = seekToLiveEdgeAndShowControls,
onNext = nextAndShowControls,
onPrevious = previousAndShowControls,
onOpenAudioPanel = { trackPanelType = TvTrackPanelType.AUDIO },
onOpenSubtitlesPanel = { trackPanelType = TvTrackPanelType.SUBTITLES },
onOpenQualityPanel = { trackPanelType = TvTrackPanelType.QUALITY },
@@ -174,10 +199,10 @@ fun TvPlayerScreen(
modifier = Modifier.align(Alignment.Center),
uiState = uiState,
onRetry = { viewModel.retry() },
onNext = { viewModel.next() },
onNext = nextAndShowControls,
onReplay = {
viewModel.seekTo(0L)
viewModel.togglePlayPause()
togglePlayPauseAndShowControls()
},
onDismissError = { viewModel.clearError() }
)
@@ -209,7 +234,7 @@ fun TvPlayerScreen(
TvPlayerQueuePanel(
uiState = uiState,
onSelect = { id ->
viewModel.playQueueItem(id)
viewModel.playQueueItem(id, TV_CONTROLS_AUTO_HIDE_MS)
showQueuePanel = false
},
onClose = { showQueuePanel = false },

View File

@@ -29,6 +29,9 @@ class PlayerViewModel @Inject constructor(
private val mediaRepository: MediaRepository,
private val progressManager: ProgressManager
) : ViewModel() {
companion object {
private const val DEFAULT_CONTROLS_AUTO_HIDE_MS = 3_500L
}
val player get() = playerManager.player
@@ -176,9 +179,9 @@ class PlayerViewModel @Inject constructor(
}
}
fun togglePlayPause() {
fun togglePlayPause(autoHideDelayMs: Long = DEFAULT_CONTROLS_AUTO_HIDE_MS) {
playerManager.togglePlayPause()
showControls()
showControls(autoHideDelayMs)
}
fun seekTo(positionMs: Long) {
@@ -193,33 +196,33 @@ class PlayerViewModel @Inject constructor(
playerManager.seekToLiveEdge()
}
fun showControls() {
fun showControls(autoHideDelayMs: Long = DEFAULT_CONTROLS_AUTO_HIDE_MS) {
_controlsVisible.value = true
scheduleAutoHide()
scheduleAutoHide(autoHideDelayMs)
}
fun toggleControlsVisibility() {
_controlsVisible.value = !_controlsVisible.value
if (_controlsVisible.value) scheduleAutoHide()
if (_controlsVisible.value) scheduleAutoHide(DEFAULT_CONTROLS_AUTO_HIDE_MS)
}
private fun scheduleAutoHide() {
private fun scheduleAutoHide(autoHideDelayMs: Long) {
autoHideJob?.cancel()
if (!player.isPlaying) return
autoHideJob = viewModelScope.launch {
delay(3500)
delay(autoHideDelayMs)
_controlsVisible.value = false
}
}
fun next() {
fun next(autoHideDelayMs: Long = DEFAULT_CONTROLS_AUTO_HIDE_MS) {
playerManager.next()
showControls()
showControls(autoHideDelayMs)
}
fun previous() {
fun previous(autoHideDelayMs: Long = DEFAULT_CONTROLS_AUTO_HIDE_MS) {
playerManager.previous()
showControls()
showControls(autoHideDelayMs)
}
fun selectTrack(option: TrackOption) {
@@ -235,9 +238,9 @@ class PlayerViewModel @Inject constructor(
playerManager.retry()
}
fun playQueueItem(id: String) {
fun playQueueItem(id: String, autoHideDelayMs: Long = DEFAULT_CONTROLS_AUTO_HIDE_MS) {
playerManager.playQueueItem(id)
showControls()
showControls(autoHideDelayMs)
}
fun clearError() {