From 01a41c01c83cc32a7e226637fd92af8a3bf2f174 Mon Sep 17 00:00:00 2001 From: Seb3thehacker Date: Tue, 13 Oct 2026 00:50:15 -0700 Subject: [PATCH] voice: "pause" keeps the music paused after the mic lets go --- .../gearslip/car/AssistantCommands.kt | 4 ++- .../seb3thehacker/gearslip/media/CarMedia.kt | 6 +++++ .../gearslip/speech/VoiceFocus.kt | 26 ++++++++++++++++--- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/AssistantCommands.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/AssistantCommands.kt index d7b0063..43e6ca2 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/AssistantCommands.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/AssistantCommands.kt @@ -2,6 +2,7 @@ package app.seb3thehacker.gearslip.car import android.content.Context import app.seb3thehacker.gearslip.media.CarMedia +import app.seb3thehacker.gearslip.speech.VoiceFocus import java.text.DateFormat import java.util.Date import kotlinx.coroutines.Dispatchers @@ -215,7 +216,8 @@ object AssistantCommands { val media = CarServices.media return when { "pause" in heard || heard.startsWith("stop") || heard == "quiet" || heard == "shut up" -> { - if (media.now.value.playing) media.togglePlay() + media.pause() + VoiceFocus.afterRelease { media.pause() } "Paused." } "resume" in heard || "unpause" in heard || heard in RESUME_PHRASES || diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/media/CarMedia.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/media/CarMedia.kt index eaa9006..4f77dca 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/media/CarMedia.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/media/CarMedia.kt @@ -433,6 +433,12 @@ class CarMedia(private val context: Context) { val controls = controller?.transportControls ?: return if (_now.value.playing) controls.pause() else controls.play() } + /** + * Pauses whatever the app thinks it's doing. Voice pauses the music to listen, so by the time + * "pause" is heard the app already looks stopped; a toggle did nothing and the song came back + * as soon as the microphone let go. A real pause tells the app to stay stopped. + */ + fun pause() = controller?.transportControls?.pause() fun next() = controller?.transportControls?.skipToNext() fun previous() = controller?.transportControls?.skipToPrevious() fun seek(ms: Long) = controller?.transportControls?.seekTo(ms) diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/speech/VoiceFocus.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/speech/VoiceFocus.kt index e79f2e5..1b16b8f 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/speech/VoiceFocus.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/speech/VoiceFocus.kt @@ -19,17 +19,35 @@ import app.seb3thehacker.gearslip.GearslipLog object VoiceFocus { private const val RELEASE_DELAY_MS = 1_500L + private const val AFTER_RELEASE_MS = 300L private val main = Handler(Looper.getMainLooper()) private var request: AudioFocusRequest? = null private var audio: AudioManager? = null private var holders = 0 + private val afterRelease = mutableListOf<() -> Unit>() private val releaseNow = Runnable { - val r = request ?: return@Runnable - audio?.abandonAudioFocusRequest(r) - request = null - GearslipLog.i("voice: gave audio focus back") + request?.let { r -> + audio?.abandonAudioFocusRequest(r) + request = null + GearslipLog.i("voice: gave audio focus back") + } + val pending = afterRelease.toList() + afterRelease.clear() + // Just after the music app has had its focus back and acted on it. + if (pending.isNotEmpty()) main.postDelayed({ pending.forEach { it() } }, AFTER_RELEASE_MS) + } + + /** + * Runs [action] once the music has its focus back, or now if Gearslip isn't holding it. + * Metrolist takes a pause sent while it's held as nothing and resumes when focus returns, + * so "pause" sends it again from here. + */ + fun afterRelease(action: () -> Unit) { + main.post { + if (request == null && holders == 0) action() else afterRelease += action + } } /** Pauses the music until a matching [release]. Safe from any thread. */