From 456dda897e9f652bd4509e33e1e4e1ea7af887ec Mon Sep 17 00:00:00 2001 From: Seb3thehacker Date: Fri, 23 Oct 2026 13:21:33 -0700 Subject: [PATCH] car audio: send Start once, even when the car answers the focus request while it's starting Starting the stream at once and the car's focus answer could both get through, so the car got Start twice and two AAC encoders ran. A 2014 aftermarket head unit dropped the connection on the first audio. Directions now also log why a turn went unspoken. --- .../seb3thehacker/gearslip/audio/AudioLink.kt | 16 ++++++++++++++++ .../gearslip/car/DirectionsVoice.kt | 16 +++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/audio/AudioLink.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/audio/AudioLink.kt index 675ab79..51f73fc 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/audio/AudioLink.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/audio/AudioLink.kt @@ -233,7 +233,19 @@ class AudioLink( startStream() } + /** + * Guards starting and stopping. [start] starts the stream at once for head units that never + * answer the focus request, and the car's answer arrives on another thread and starts it + * too: without this both got through, so the car got Start twice and two AAC encoders, and a + * 2014 head unit dropped the connection on the first audio. + */ + private val startLock = Any() + private fun startStream() { + synchronized(startLock) { startStreamLocked() } + } + + private fun startStreamLocked() { if (!ready || started) return sendOnChannel( MSG_START, @@ -252,6 +264,10 @@ class AudioLink( } override fun stop() { + synchronized(startLock) { stopLocked() } + } + + private fun stopLocked() { if (!started) return started = false synchronized(ackLock) { ackLock.notifyAll() } diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/DirectionsVoice.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/DirectionsVoice.kt index 5e5960d..4aaeda0 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/DirectionsVoice.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/DirectionsVoice.kt @@ -87,16 +87,26 @@ object DirectionsVoice { running = false } + /** Why the last turn wasn't spoken, so the log says it once rather than every tick. */ + private var silentBecause: String? = null + + private fun quiet(reason: String) { + if (silentBecause == reason) return + silentBecause = reason + GearslipLog.i("guidance: not speaking - $reason") + } + private fun tick() { - if (!CarSettings.speakDirections.value) return val turn = NavTurn.current() if (turn == null) { turnKey = null return } - if (turn.rerouting) return + if (!CarSettings.speakDirections.value) return quiet("Spoken directions is off in the car's Settings") + if (turn.rerouting) return quiet("the map app is rerouting") val step = turn.step - val instruction = instruction(step) ?: return + val instruction = instruction(step) ?: return quiet("the map app gave this turn no words") + silentBecause = null val meters = turn.distance?.let(NavTurn::toMeters) val close = (turn.seconds != null && turn.seconds <= NEAR_SECONDS) || (meters != null && meters <= NEAR_METERS)