update tv ux, fix configuration-cache issues

This commit is contained in:
2026-07-21 14:38:02 +03:00
parent a264db284d
commit a062427d0d
11 changed files with 50 additions and 79 deletions
@@ -30,6 +30,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jetbrains.compose.resources.getString
import org.koin.core.annotation.Factory
import kotlin.time.Duration.Companion.milliseconds
@Factory
class AndroidPlayerController(context: Context) : PlayerController {
@@ -114,9 +115,7 @@ class AndroidPlayerController(context: Context) : PlayerController {
)
}
}.flatten()
audioTracks.value = list
if (!isTrackInitialized && list.isNotEmpty()) {
isTrackInitialized = true
list.firstOrNull()?.let { selectAudioTrack(it.id) }
@@ -151,6 +150,7 @@ class AndroidPlayerController(context: Context) : PlayerController {
override suspend fun load(url: String, startPositionMs: Long): Unit =
withContext(Dispatchers.Main) {
isTrackInitialized = false
bindListenerIfNeeded(true)
val mediaItem = MediaItem.fromUri(url)
exoPlayer.setMediaItem(mediaItem)
@@ -199,7 +199,7 @@ class AndroidPlayerController(context: Context) : PlayerController {
progressJob = scope.launch {
while (isActive) {
updateState()
delay(200)
delay(200.milliseconds)
}
}
}
@@ -59,6 +59,7 @@ import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.painterResource
import ru.shadowsparky.ui.components.isTv
import ru.shadowsparky.videoplayer.AudioTrackInfo
import ru.shadowsparky.videoplayer.ControlsState
import ru.shadowsparky.videoplayer.PlayerController
@@ -74,6 +75,7 @@ fun DefaultControls(
controller: PlayerController,
modifier: Modifier = Modifier
) {
val isTv = isTv()
val scope = rememberCoroutineScope()
val audioTracksState by controller.audioTracks.collectAsState()
@@ -102,8 +104,8 @@ fun DefaultControls(
}
)
}
LockUiButton(focus, isUiLocked.value, audioTracksState.isNotEmpty()) {
isUiLocked.value = it
if (!isTv) {
LockUiButton(focus, isUiLocked.value) { isUiLocked.value = it }
}
}
if (!isUiLocked.value) {
@@ -154,8 +156,8 @@ fun DefaultControls(
Key.DirectionRight -> {
scope.launch { controller.seek(false) }
}
Key.DirectionUp -> focus.next.requestFocus()
Key.DirectionDown -> focus.lock.requestFocus()
Key.DirectionUp -> focus.play.requestFocus()
Key.DirectionDown -> focus.audioTrack.requestFocus()
else -> return@onPreviewKeyEvent false
}
}
@@ -171,7 +173,6 @@ fun DefaultControls(
private fun LockUiButton(
focusContainer: FocusContainer,
state: Boolean,
hasAudioTrack: Boolean,
onChanged: (Boolean) -> Unit
) {
IconButton(
@@ -215,7 +216,7 @@ private fun AudioTracksButton(
onDismissRequest = { expanded = false },
modifier = Modifier.focusable()
) {
for (i in 0 until tracks.size) {
for (i in tracks.indices) {
val (track, requester) = tracks[i]
DropdownMenuItem(
text = {
@@ -280,6 +281,7 @@ private fun BoxScope.CenterControls(
controller: PlayerController
) {
val scope = rememberCoroutineScope()
var canRequestFocus by remember { mutableStateOf(false) }
Box(
modifier = Modifier.align(Alignment.Center)
.focusGroup()
@@ -287,9 +289,7 @@ private fun BoxScope.CenterControls(
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
when {
state.isBuffering -> {
CircularWavyProgressIndicator()
}
state.isBuffering -> CircularWavyProgressIndicator()
state.isEnded -> {
IconButton(
onClick = {
@@ -297,7 +297,7 @@ private fun BoxScope.CenterControls(
controller.seekTo(0)
controller.play()
}
}, modifier = Modifier.focusableWithBorder(focusContainer.next)
}
) {
Icon(
painter = painterResource(Res.drawable.replay_24px),
@@ -312,18 +312,6 @@ private fun BoxScope.CenterControls(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
IconButton(
onClick = {
scope.launch { controller.seek(true) }
}, modifier = Modifier.focusableWithBorder(focusContainer.prev)
) {
Text(
text = "-$DEF_SEEK_TIME",
color = Color.White,
style = MaterialTheme.typography.bodyMedium
)
}
IconButton(
onClick = {
scope.launch {
@@ -343,25 +331,17 @@ private fun BoxScope.CenterControls(
tint = Color.White
)
}
IconButton(
onClick = {
scope.launch { controller.seek(false) }
}, modifier = Modifier.focusableWithBorder(focusContainer.next)
) {
Text(
text = "+$DEF_SEEK_TIME",
color = Color.White,
style = MaterialTheme.typography.bodyMedium
)
}
}
canRequestFocus = true
}
}
}
LaunchedEffect(null) {
withFrameNanos {}
focusContainer.play.requestFocus()
LaunchedEffect(state.videoUrl) { canRequestFocus = false }
LaunchedEffect(canRequestFocus) {
if (canRequestFocus) {
withFrameNanos {}
focusContainer.play.requestFocus()
}
}
}
@@ -399,9 +379,7 @@ private fun formatDuration(millis: Long): String {
private class FocusContainer(
val audioTrack: FocusRequester = FocusRequester(),
val lock: FocusRequester = FocusRequester(),
val prev: FocusRequester = FocusRequester(),
val play: FocusRequester = FocusRequester(),
val next: FocusRequester = FocusRequester(),
val slider: FocusRequester = FocusRequester()
)