voice: "pause" keeps the music paused after the mic lets go

This commit is contained in:
Seb3thehacker committed 2026-10-13 00:52:00 -07:00
1 parent 62b1835444
commit 01a41c01c8
3 files changed
+31 -5

No files matched your search

@@ -2,6 +2,7 @@ package app.seb3thehacker.gearslip.car
import android.content.Context import android.content.Context
import app.seb3thehacker.gearslip.media.CarMedia import app.seb3thehacker.gearslip.media.CarMedia
import app.seb3thehacker.gearslip.speech.VoiceFocus
import java.text.DateFormat import java.text.DateFormat
import java.util.Date import java.util.Date
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -215,7 +216,8 @@ object AssistantCommands {
val media = CarServices.media val media = CarServices.media
return when { return when {
"pause" in heard || heard.startsWith("stop") || heard == "quiet" || heard == "shut up" -> { "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." "Paused."
} }
"resume" in heard || "unpause" in heard || heard in RESUME_PHRASES || "resume" in heard || "unpause" in heard || heard in RESUME_PHRASES ||
@@ -433,6 +433,12 @@ class CarMedia(private val context: Context) {
val controls = controller?.transportControls ?: return val controls = controller?.transportControls ?: return
if (_now.value.playing) controls.pause() else controls.play() 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 next() = controller?.transportControls?.skipToNext()
fun previous() = controller?.transportControls?.skipToPrevious() fun previous() = controller?.transportControls?.skipToPrevious()
fun seek(ms: Long) = controller?.transportControls?.seekTo(ms) fun seek(ms: Long) = controller?.transportControls?.seekTo(ms)
@@ -19,17 +19,35 @@ import app.seb3thehacker.gearslip.GearslipLog
object VoiceFocus { object VoiceFocus {
private const val RELEASE_DELAY_MS = 1_500L private const val RELEASE_DELAY_MS = 1_500L
private const val AFTER_RELEASE_MS = 300L
private val main = Handler(Looper.getMainLooper()) private val main = Handler(Looper.getMainLooper())
private var request: AudioFocusRequest? = null private var request: AudioFocusRequest? = null
private var audio: AudioManager? = null private var audio: AudioManager? = null
private var holders = 0 private var holders = 0
private val afterRelease = mutableListOf<() -> Unit>()
private val releaseNow = Runnable { private val releaseNow = Runnable {
val r = request ?: return@Runnable request?.let { r ->
audio?.abandonAudioFocusRequest(r) audio?.abandonAudioFocusRequest(r)
request = null request = null
GearslipLog.i("voice: gave audio focus back") 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. */ /** Pauses the music until a matching [release]. Safe from any thread. */