Player overlay v2: now/next with programme progress, audio track switching

- Overlay shows current programme (title, time range, EPG progress) and
  what's next; resurfaces briefly on programme change, hides after 5s.
- Audio tracks: listed from the stream, switchable via D-pad left/right
  on live streams (seek keeps left/right on seekable media), via chips
  in the overlay and from the phone remote (set_audio message).
- Main UI font switches to Inter; Space Grotesk stays for the wordmark.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
be-nj
2026-08-25 23:54:02 +02:00
parent e69c79061f
commit 0410eda9c9
16 changed files with 270 additions and 55 deletions

View File

@@ -38,6 +38,9 @@ class AppState(
var isLive by mutableStateOf(false)
var favoritesOnly by mutableStateOf(false)
var groupFilter by mutableStateOf<String?>(null)
var audioTracks by mutableStateOf<List<PlayerController.AudioTrack>>(emptyList())
private set
private var lastNowTitle: String? = null
/** Phone-first onboarding progress (ADR-0007). */
var welcomePhase by mutableStateOf(WelcomePhase.WAIT_PHONE)
@@ -158,13 +161,31 @@ class AppState(
}
fun syncFromPlayer() {
val previous = playerState
playerState = player.state
if (playerState != previous) overlayVisible = true
isLive = player.player.isCurrentMediaItemLive
positionMs = player.player.currentPosition.coerceAtLeast(0)
durationMs = player.player.duration.coerceAtLeast(0)
audioTracks = player.audioTracks()
// Briefly resurface the overlay when the running programme changes.
val nowTitle = currentChannel?.let { nowNext(it).now?.title }
if (playerVisible && nowTitle != null && lastNowTitle != null && nowTitle != lastNowTitle) {
overlayVisible = true
}
lastNowTitle = nowTitle
if (!player.hasMedia && playerVisible) {
playerVisible = false
currentChannel = null
}
}
/** Steps to the next audio track (D-pad friendly: one key, cycles). */
fun cycleAudio(direction: Int) {
val tracks = audioTracks
if (tracks.size < 2) return
val current = tracks.indexOfFirst { it.selected }.coerceAtLeast(0)
player.selectAudio((current + direction + tracks.size) % tracks.size)
audioTracks = player.audioTracks()
}
}