diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoBitrateRange.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoBitrateRange.kt new file mode 100644 index 00000000..d8b3d181 --- /dev/null +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallVideoBitrateRange.kt @@ -0,0 +1,19 @@ +package com.utun.chat.data + +/** Обе границы хранятся одной настройкой в десятичных кбит/с. */ +data class CallVideoBitrateRange(val minimum: Int = 300, val maximum: Int = 4000) { + init { require(minimum in 300..4000 && maximum in minimum..4000) } + + fun encode(): String = "$minimum:$maximum" + + companion object { + fun parse(value: String): CallVideoBitrateRange? { + val parts = value.split(':') + if (parts.size != 2 || parts.any { it.isEmpty() || it.any { digit -> digit !in '0'..'9' } }) return null + val minimum = parts[0].toIntOrNull() ?: return null + val maximum = parts[1].toIntOrNull() ?: return null + if (minimum !in 300..4000 || maximum !in minimum..4000) return null + return CallVideoBitrateRange(minimum, maximum) + } + } +} diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt index ba38ffd4..26081346 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt @@ -55,6 +55,7 @@ class ConfigProvider(private val context: Context) { private val chatStorageMaxsizeGb = intPreferencesKey("chat_storage_maxsize_gb") private val chatOpusCodecPreset = intPreferencesKey("chat_opus_codec_preset") private val callJitterMaxReserveMs = intPreferencesKey("call_jitter_max_reserve_ms") + private val callVideoBitrateRange = stringPreferencesKey("call_video_bitrate_range_kbps") private val chatCompressorEnabled = intPreferencesKey("chat_compressor_enabled") private val chatCompressorMaxGainDb = intPreferencesKey("chat_compressor_max_gain_db") private val chatCompressorRiseRate = intPreferencesKey("chat_compressor_rise_rate") @@ -141,6 +142,19 @@ class ConfigProvider(private val context: Context) { ) } + fun getCallVideoBitrateRange(): CallVideoBitrateRange = runBlocking { + val value = context.dataStore.data.first()[callVideoBitrateRange] ?: return@runBlocking CallVideoBitrateRange() + CallVideoBitrateRange.parse(value) ?: run { + LogManager.addLog("WARN", "CallSettings", "Invalid video bitrate range '$value'; using 300:4000") + CallVideoBitrateRange() + } + } + + suspend fun saveCallVideoBitrateRange(range: CallVideoBitrateRange) { + context.dataStore.edit { it[callVideoBitrateRange] = range.encode() } + LogManager.addLog("INFO", "CallSettings", "Video bitrate range saved: ${range.encode()} kbps") + } + fun getString(key: String): String? = runBlocking { when { key == "node.private_key" -> readKeysFromFile()?.second ?: context.dataStore.data.first()[nodePrivateKey] @@ -669,6 +683,7 @@ class ConfigProvider(private val context: Context) { appendLine("radio_compressor_enabled=$radioCompEnabled") appendLine("radio_compressor_max_gain_db=$radioCompMaxGain") appendLine("call_jitter_max_reserve_ms=${getInt("chat.call_jitter_max_reserve_ms")}") + appendLine("call_video_bitrate_range_kbps=${getCallVideoBitrateRange().encode()}") appendLine("compressor_enabled=$chatCompEnabled") appendLine("compressor_max_gain_db=$chatCompMaxGain") appendLine("compressor_rise_rate=$chatCompRiseRate") diff --git a/tools/chatgui-android/app/src/test/java/com/utun/chat/data/CallVideoBitrateRangeTest.kt b/tools/chatgui-android/app/src/test/java/com/utun/chat/data/CallVideoBitrateRangeTest.kt new file mode 100644 index 00000000..53a128a1 --- /dev/null +++ b/tools/chatgui-android/app/src/test/java/com/utun/chat/data/CallVideoBitrateRangeTest.kt @@ -0,0 +1,20 @@ +package com.utun.chat.data + +import org.junit.Assert.* +import org.junit.Test + +class CallVideoBitrateRangeTest { + @Test fun defaultsAndRoundTrip() { + assertEquals("300:4000", CallVideoBitrateRange().encode()) + for (range in listOf(CallVideoBitrateRange(), CallVideoBitrateRange(500, 1500), CallVideoBitrateRange(300, 300))) { + assertEquals(range, CallVideoBitrateRange.parse(range.encode())) + } + } + + @Test fun invalidPersistedRangeIsRejected() { + for (value in listOf("", "299:4000", "300:4001", "4000:300", "300", "300:4000x", + "-300:4000", "300:+4000", "300: 4000", "999999999999999999999999:4000")) { + assertNull(value, CallVideoBitrateRange.parse(value)) + } + } +}