From dc7018f3947c6f487e88e5b2a799f6ce7c18f466 Mon Sep 17 00:00:00 2001 From: Seb3thehacker Date: Sat, 24 Oct 2026 16:25:58 -0700 Subject: [PATCH] video: adaptive bitrate is an experimental feature, off until it's been tried in a car --- .../app/seb3thehacker/gearslip/GearslipRunner.kt | 5 +++++ .../app/seb3thehacker/gearslip/car/CarSettings.kt | 14 ++++++++++++++ .../gearslip/car/CarSettingsScreen.kt | 11 +++++++++-- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/GearslipRunner.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/GearslipRunner.kt index f90b27e..a784799 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/GearslipRunner.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/GearslipRunner.kt @@ -1094,6 +1094,8 @@ class GearslipRunner( smoothWindows = 0 lastCut = null decoderBound = false + adaptiveVideo = CarSettings.experimentalFeaturesEnabled.value && CarSettings.adaptiveVideoEnabled.value + if (adaptiveVideo) log.i("video: adaptive bitrate on (experimental)") videoSource = VideoSource( width = width, height = height, @@ -1407,6 +1409,8 @@ class GearslipRunner( private var smoothWindows = 0 /** The bitrate and frame rate before the last cut, to see whether cutting helped. */ private var lastCut: Pair? = null + /** Experimental: read once per stream, so flipping the switch mid-drive changes nothing. */ + @Volatile private var adaptiveVideo = false /** Set once a cut didn't help: this car's decoder, not the link, sets the pace. */ private var decoderBound = false @@ -1417,6 +1421,7 @@ class GearslipRunner( * where the stream started. A still screen (few frames, no waiting) changes nothing. */ private fun adaptBitRate(fps: Double, waitedMs: Long) { + if (!adaptiveVideo) return val source = videoSource ?: return val target = videoFrameRate.toDouble() val behind = waitedMs > ADAPT_WINDOW_MS / 2 && fps < target * 0.8 diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarSettings.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarSettings.kt index 128e062..a597672 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarSettings.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarSettings.kt @@ -71,6 +71,7 @@ private const val KEY_LOCATION_WARNING = "car_location_warning" private const val KEY_EXPERIMENTAL_FEATURES = "car_experimental_features" private const val KEY_SYSTEM_SPEECH = "car_system_speech" private const val KEY_CAR_SENSORS = "car_sensors_enabled" + private const val KEY_ADAPTIVE_VIDEO = "adaptive_video_enabled" private const val KEY_SEEN_SAFETY_WARNING = "car_seen_safety_warning" private const val KEY_UNITS = "car_units" private const val KEY_NAV_BAR = "car_nav_bar" @@ -113,6 +114,7 @@ private const val KEY_LOCATION_WARNING = "car_location_warning" private val experimentalFeaturesFlow = MutableStateFlow(false) private val systemSpeechFlow = MutableStateFlow(false) private val carSensorsFlow = MutableStateFlow(false) + private val adaptiveVideoFlow = MutableStateFlow(false) private val seenSafetyWarningFlow = MutableStateFlow(false) private val unitsFlow = MutableStateFlow(Units.AUTO) @@ -272,6 +274,12 @@ private const val KEY_LOCATION_WARNING = "car_location_warning" */ val carSensorsEnabled: StateFlow = carSensorsFlow + /** + * Video bitrate that backs off when the car, or the Wi-Fi to it, falls behind. Takes effect + * only while [experimentalFeaturesEnabled] is on too; untested in a car so far. + */ + val adaptiveVideoEnabled: StateFlow = adaptiveVideoFlow + /** * Whether the car screen's own "drive safely" warning has already been shown and dismissed * once, from this same install. Separate from the phone side's own compat/safety dialogs - @@ -314,6 +322,7 @@ private const val KEY_LOCATION_WARNING = "car_location_warning" experimentalFeaturesFlow.value = AppSettings.getString(app, KEY_EXPERIMENTAL_FEATURES, "false") == "true" systemSpeechFlow.value = AppSettings.getString(app, KEY_SYSTEM_SPEECH, "false") == "true" carSensorsFlow.value = AppSettings.getString(app, KEY_CAR_SENSORS, "false") == "true" + adaptiveVideoFlow.value = AppSettings.getString(app, KEY_ADAPTIVE_VIDEO, "false") == "true" seenSafetyWarningFlow.value = AppSettings.getString(app, KEY_SEEN_SAFETY_WARNING, "false") == "true" unitsFlow.value = runCatching { Units.valueOf(AppSettings.getString(app, KEY_UNITS, Units.AUTO.name)) @@ -416,6 +425,11 @@ private const val KEY_LOCATION_WARNING = "car_location_warning" AppSettings.putString(app, KEY_SYSTEM_SPEECH, on.toString()) } + fun setAdaptiveVideoEnabled(on: Boolean) { + adaptiveVideoFlow.value = on + AppSettings.putString(app, KEY_ADAPTIVE_VIDEO, on.toString()) + } + fun setCarSensorsEnabled(on: Boolean) { carSensorsFlow.value = on AppSettings.putString(app, KEY_CAR_SENSORS, on.toString()) diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarSettingsScreen.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarSettingsScreen.kt index 257bf19..4b7493c 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarSettingsScreen.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarSettingsScreen.kt @@ -69,6 +69,7 @@ fun CarSettingsScreen() { val uiTheme by CarSettings.uiTheme.collectAsState() val experimentalFeatures by CarSettings.experimentalFeaturesEnabled.collectAsState() val carSensors by CarSettings.carSensorsEnabled.collectAsState() + val adaptiveVideo by CarSettings.adaptiveVideoEnabled.collectAsState() val systemSpeech by CarSettings.systemSpeech.collectAsState() val display by CarEnvironment.display.collectAsState() val vehicle by CarEnvironment.vehicle.collectAsState() @@ -278,7 +279,7 @@ fun CarSettingsScreen() { } if (experimentalFeatures) item { - ExperimentalFeaturesList(carSensors) + ExperimentalFeaturesList(carSensors, adaptiveVideo) } item { @@ -368,7 +369,7 @@ private fun ChoiceButtons(options: List>, selected: T, onSel * so the list reads fine whether there's one entry or several. */ @Composable -private fun ExperimentalFeaturesList(carSensors: Boolean) { +private fun ExperimentalFeaturesList(carSensors: Boolean, adaptiveVideo: Boolean) { Column( Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(10.dp), @@ -381,6 +382,12 @@ private fun ExperimentalFeaturesList(carSensors: Boolean) { "guess, not a fact, until you've watched one change for yourself.", checked = carSensors, ) { CarSettings.setCarSensorsEnabled(it) } + ExperimentalFeatureRow( + title = "Adaptive video quality", + description = "Lowers the picture's quality when the car, or its Wi-Fi, can't keep " + + "up, and raises it again once it can. Takes effect on the next connection.", + checked = adaptiveVideo, + ) { CarSettings.setAdaptiveVideoEnabled(it) } } }