From b3f5c49c4dfedf07b8c210a00e123a6cd44ea62c Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 29 Oct 2026 03:05:57 +0100 Subject: [PATCH] Keep stream catalogue labels current and preserve viewer errors --- .../main/java/com/utun/chat/ui/screens/CaptureControls.kt | 5 +++-- .../main/java/com/utun/chat/ui/screens/StreamGridScreen.kt | 5 +++-- .../java/com/utun/chat/ui/screens/StreamViewerScreen.kt | 6 ++++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CaptureControls.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CaptureControls.kt index 0d166452..84113f25 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CaptureControls.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CaptureControls.kt @@ -36,7 +36,7 @@ import java.text.NumberFormat internal enum class CaptureIconShape { CAMERA, CHIP, GAUGE, SCREEN } -/** Короткое локализованное значение битрейта для шкалы в записи и трансляции. */ +/** Положительный bitrate в бит/с отображается в десятичных k/M; дробь до 10 M и разделитель берутся из locale. */ @Composable internal fun captureBitrateLabel(bitrate: Int): String { val mega = bitrate >= 1_000_000 @@ -157,7 +157,8 @@ internal fun CaptureOptionItem(label: String, active: Boolean, onClick: () -> Un trailingIcon = { if (active) Icon(Icons.Filled.Check, contentDescription = null) }, modifier = Modifier.semantics { selected = active }) } -/** Общий выбор ориентации фото, видеосообщений и трансляции; маркер показывает верх записываемого кадра. */ +/** Общий выбор AUTO/0/90/270° для фото, видеосообщений и трансляции; маркер показывает верх записываемого кадра. + * displayDegrees — 0/90/180/270°; front меняет направление компенсации. Изменение применяет вызывающий владелец. */ @Composable internal fun CaptureOrientationControl(orientation: StreamOrientation?, displayDegrees: Int, front: Boolean, enabled: Boolean, expanded: Boolean, properties: PopupProperties, diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamGridScreen.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamGridScreen.kt index a02011e2..ea4ca33a 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamGridScreen.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamGridScreen.kt @@ -40,7 +40,8 @@ import com.utun.chat.data.streamGridShape import kotlinx.coroutines.delay import kotlinx.coroutines.isActive -private class GridStream(val source: StreamSource) { +private class GridStream(source: StreamSource) { + var source by mutableStateOf(source) var status by mutableStateOf(R.string.stream_connecting) var attempt by mutableStateOf(0) var removeAt: Long? = null @@ -84,7 +85,7 @@ internal fun StreamGridScreen(group: Long, title: String, sources: List current[tile.source.id]?.let { it.epoch != tile.source.epoch } == true } for (tile in tiles) { - if (tile.source.id in current) tile.removeAt = null + if (tile.source.id in current) { tile.source = current.getValue(tile.source.id); tile.removeAt = null } else if (tile.removeAt == null) { tile.removeAt = now + if (tile.source.self) 0 else (tile.engine?.drainTimeoutMs ?: 2000) LogManager.addLog("INFO", "stream", "multiview drains source=${tile.source.id} deadline=${tile.removeAt}") diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamViewerScreen.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamViewerScreen.kt index c65ef8b7..61dc5621 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamViewerScreen.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamViewerScreen.kt @@ -130,8 +130,10 @@ internal fun StreamViewerPlayback(group: Long, source: Long, epoch: Long, onAspe modifier: Modifier, onEngine: (StreamPlayerEngine) -> Unit = {}, onTelemetry: (StreamPlaybackTelemetry) -> Unit = {}) { var engine by remember(group, source, epoch) { mutableStateOf(null) } + var startupError by remember(group, source, epoch) { mutableStateOf(false) } val aspect = engine?.aspect?.collectAsState()?.value ?: 0f - val status = engine?.status?.collectAsState()?.value ?: R.string.stream_connecting + val engineStatus = engine?.status?.collectAsState()?.value ?: R.string.stream_connecting + val status = if (startupError) R.string.stream_failed else engineStatus val telemetry = engine?.telemetry?.collectAsState()?.value ?: StreamPlaybackTelemetry() val resources = remember(group, source, epoch) { StreamViewerSurface() } fun releaseSurface(texture: SurfaceTexture? = null) { @@ -161,7 +163,7 @@ internal fun StreamViewerPlayback(group: Long, source: Long, epoch: Long, onAspe engine = player try { onEngine(player); player.start(target.surface) } catch (e: Exception) { LogManager.addLog("ERROR", "stream", "viewer UI start failed source=$source epoch=$epoch: $e") - releaseSurface(); onStatus(R.string.stream_failed) + startupError = true; releaseSurface() } } override fun onSurfaceTextureSizeChanged(texture: SurfaceTexture, width: Int, height: Int) {