From c815d06ab0ef9e01aaa820608c7022bb3c54213d Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 14 Oct 2026 01:13:28 +0200 Subject: [PATCH] Add video codec and bitrate controls with aggregate send buffer indicators --- .../com/utun/chat/ui/screens/StreamScreen.kt | 67 +++++++++++++++++-- .../app/src/main/res/values-ru/strings.xml | 5 ++ .../app/src/main/res/values/strings.xml | 5 ++ 3 files changed, 71 insertions(+), 6 deletions(-) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamScreen.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamScreen.kt index 32c701d2..25a43281 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamScreen.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/StreamScreen.kt @@ -71,7 +71,8 @@ fun StreamScreen(channelId: String, channelName: String, sources: List(null) } var orientation by rememberSaveable { mutableStateOf(StreamOrientation.UPRIGHT) } - val config = mode?.let { StreamCaptureConfig(it, orientation) } + var videoBitrate by rememberSaveable { mutableStateOf(null) } + val config = mode?.let { StreamCaptureConfig(it, orientation, videoBitrate) } var cameras by remember { mutableStateOf>(emptyList()) } var loadingModes by remember { mutableStateOf(true) } LaunchedEffect(Unit) { @@ -114,6 +115,7 @@ fun StreamScreen(channelId: String, channelName: String, sources: List R.string.stream_live StreamPhase.STOPPING -> R.string.stream_stopping @@ -133,7 +136,7 @@ fun StreamScreen(channelId: String, channelName: String, sources: List, config: StreamCaptureConfig?, enabled: Boolean, onChange: (StreamCaptureConfig) -> Unit) { @@ -170,12 +186,51 @@ private fun CaptureOptions(cameras: List, config: StreamCaptureCon Row(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(8.dp)) { cameras.forEach { camera -> FilterChip(selected = camera.id == mode?.camera, enabled = enabled, onClick = { - StreamCameraModes.choose(camera.modes, mode)?.let { onChange(StreamCaptureConfig(it, config?.orientation ?: StreamOrientation.UPRIGHT)) } + StreamCameraModes.choose(camera.modes, mode)?.let { onChange(config?.withMode(it) ?: StreamCaptureConfig(it)) } }, label = { Text("${camera.lens.label} ${camera.lens.mm}".trim()) }) } } val camera = cameras.firstOrNull { it.id == mode?.camera } - val modes = camera?.modes.orEmpty() + val allModes = camera?.modes.orEmpty() + val modes = allModes.filter { it.videoCodec == mode?.videoCodec } + var codecsExpanded by remember { mutableStateOf(false) } + var bitrateExpanded by remember { mutableStateOf(false) } + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Box(Modifier.weight(1f)) { + OutlinedButton(onClick = { codecsExpanded = true }, enabled = enabled && config != null, modifier = Modifier.fillMaxWidth()) { + Text(stringResource(R.string.stream_video_codec, mode?.videoCodec?.label.orEmpty())) + } + DropdownMenu(expanded = codecsExpanded && enabled, onDismissRequest = { codecsExpanded = false }) { + allModes.map { it.videoCodec }.distinct().sortedBy { it.id }.forEach { codec -> + DropdownMenuItem(text = { Text(codec.label) }, onClick = { + codecsExpanded = false + StreamCameraModes.choose(allModes.filter { it.videoCodec == codec }, mode)?.let { next -> + config?.let { onChange(it.withMode(next)) } + } + }) + } + } + } + Box(Modifier.weight(1f)) { + OutlinedButton(onClick = { bitrateExpanded = true }, enabled = enabled && config != null, modifier = Modifier.fillMaxWidth()) { + Text(if (config?.videoBitrate == null) stringResource(R.string.stream_bitrate_auto) + else stringResource(R.string.stream_bitrate_value, config.bitrate / 1_000_000.0)) + } + DropdownMenu(expanded = bitrateExpanded && enabled, onDismissRequest = { bitrateExpanded = false }) { + DropdownMenuItem(text = { Text(stringResource(R.string.stream_bitrate_auto)) }, onClick = { + bitrateExpanded = false; config?.let { onChange(it.copy(videoBitrate = null)) } + }) + mode?.let { m -> + listOf(256000, 512000, 1000000, 2000000, 4000000, 6000000, 8000000, 12000000, 16000000, 24000000, 32000000, 48000000, 64000000) + .map { it.coerceIn(m.minimumBitrate, m.maximumBitrate) }.distinct().forEach { value -> + DropdownMenuItem(text = { Text(stringResource(R.string.stream_bitrate_value, value / 1_000_000.0)) }, onClick = { + bitrateExpanded = false; config?.let { onChange(it.copy(videoBitrate = value)) } + }) + } + } + } + } + } val context = LocalContext.current val configuration = LocalConfiguration.current val display = remember(configuration) { ((context as? Activity)?.windowManager?.defaultDisplay?.rotation ?: 0) * 90 } @@ -185,7 +240,7 @@ private fun CaptureOptions(cameras: List, config: StreamCaptureCon OutlinedButton(onClick = { expanded = true }, enabled = enabled && modes.isNotEmpty()) { Text(mode?.label.orEmpty()) } DropdownMenu(expanded = expanded && enabled, onDismissRequest = { expanded = false }) { modes.forEach { option -> DropdownMenuItem(text = { Text(option.label) }, onClick = { - expanded = false; config?.let { onChange(it.copy(mode = option)) } + expanded = false; config?.let { onChange(it.withMode(option)) } }) } } } diff --git a/tools/chatgui-android/app/src/main/res/values-ru/strings.xml b/tools/chatgui-android/app/src/main/res/values-ru/strings.xml index 822794ce..c0a9d21c 100644 --- a/tools/chatgui-android/app/src/main/res/values-ru/strings.xml +++ b/tools/chatgui-android/app/src/main/res/values-ru/strings.xml @@ -1,5 +1,10 @@ + Кодек: %1$s + Битрейт видео: Авто + %1$.2f Мбит/с + Очередь видео: %1$.1f КиБ · Неподтверждено: %2$.1f КиБ + Буфер отправки: %1$.1f КиБ · ACK / 1 с: %2$.1f КиБ (%3$.2f Мбит/с) Трансляции Трансляции: %1$d Выберите трансляцию diff --git a/tools/chatgui-android/app/src/main/res/values/strings.xml b/tools/chatgui-android/app/src/main/res/values/strings.xml index baf806d8..b07a560d 100644 --- a/tools/chatgui-android/app/src/main/res/values/strings.xml +++ b/tools/chatgui-android/app/src/main/res/values/strings.xml @@ -1,5 +1,10 @@ + Codec: %1$s + Video bitrate: Auto + %1$.2f Mbps + Video queue: %1$.1f KiB · Unacknowledged: %2$.1f KiB + Send buffer: %1$.1f KiB · ACK / 1 s: %2$.1f KiB (%3$.2f Mbps) Live streams Live streams: %1$d Choose a stream