diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 42be2bd..0355152 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -17,7 +17,7 @@ android { applicationId = "tech.capullo.quantumcast" minSdk = 26 targetSdk = 36 - versionCode = 10 + versionCode = 12 versionName = "1.0" } diff --git a/app/src/main/java/tech/capullo/quantumcast/player/PlaybackService.kt b/app/src/main/java/tech/capullo/quantumcast/player/PlaybackService.kt index 551d190..c077623 100755 --- a/app/src/main/java/tech/capullo/quantumcast/player/PlaybackService.kt +++ b/app/src/main/java/tech/capullo/quantumcast/player/PlaybackService.kt @@ -66,6 +66,7 @@ import tech.capullo.audio.snapcast.SnapcontrolPlugin import tech.capullo.audio.snapcast.SnapserverPorts import tech.capullo.audio.snapcast.SnapserverProcess import tech.capullo.audio.snapcast.firstArtist +import tech.capullo.audio.snapcast.withoutReferenceTaps import tech.capullo.quantumcast.MainActivity import tech.capullo.quantumcast.data.settings.BroadcastMode import tech.capullo.quantumcast.data.settings.SettingsRepository @@ -340,7 +341,17 @@ class PlaybackService : Service() { ) return null } - val tap = tech.capullo.audio.snapcast.ReferenceTapProcess(this) + // Deregistering matters as much as killing it: snapserver keeps disconnected clients (the + // calibration read-back relies on that), so without Server.DeleteClient every run would + // leave a permanent phantom in the server's list and in server.json. + val tap = tech.capullo.audio.snapcast.ReferenceTapProcess(this) { tapId -> + scope.launch { + // Let the server notice the process is gone before removing the entry. + delay(TAP_DEREGISTER_DELAY_MS) + runCatching { snapcastControl?.sendDeleteClient(tapId) } + .onFailure { Log.w("SyncCalibrator", "tap deregister failed: ${it.message}") } + } + } val job = scope.launch { tap.start(st.snapclientHost, st.snapclientPort, ring) } calibrationTap = ring return { @@ -924,6 +935,12 @@ class PlaybackService : Service() { // Snapcast stream identity (the snapserver `name=`), shown in web players / to snapclients. // capullo-audio's SnapserverProcess defaults to "Capullo"; QuantumCast keeps its own name so // multiple capullo apps stay distinguishable on a LAN (was hardcoded in QC's SnapserverProcess). + + /** Grace before deleting the reference tap from the server. The process is already + * killed; this only gives snapserver time to register the disconnect, so the delete + * removes a client it agrees is gone rather than racing its own bookkeeping. */ + private const val TAP_DEREGISTER_DELAY_MS = 1_500L + private const val STREAM_NAME = "QuantumCast" } @@ -1949,7 +1966,10 @@ class PlaybackService : Service() { client.notifications.collect { notif -> when (notif) { is tech.capullo.audio.snapcast.ServerGetStatusResponse -> { - val groups = notif.result.server.groups + // Filtered here rather than at each list: a calibration reference tap is a + // connected client but not a speaker, and every consumer downstream (the + // control sheet, the device count, reset-all) wants it gone. + val groups = notif.result.server.groups.withoutReferenceTaps() val hostname = notif.result.server.server.host.name val displayName = if (_state.value.broadcastMode == BroadcastMode.SNAPCLIENT) { val serverHost = notif.result.server.server.host.name @@ -1991,7 +2011,7 @@ class PlaybackService : Service() { } } is tech.capullo.audio.snapcast.ServerOnUpdate -> { - val groups = notif.params.server.groups + val groups = notif.params.server.groups.withoutReferenceTaps() val displayName = if (_state.value.broadcastMode == BroadcastMode.SNAPCLIENT) { val serverHost = notif.params.server.server.host.name groups.flatMap { it.clients } diff --git a/gradle/pins.versions.toml b/gradle/pins.versions.toml index 3c7f8a5..e846d6f 100644 --- a/gradle/pins.versions.toml +++ b/gradle/pins.versions.toml @@ -7,7 +7,7 @@ # jitpack caches -SNAPSHOT unreliably). Composite build substitutes the sibling locally. capulloSourceRadiobrowser = "ce836da8fefc03f1e12f83910738217b1df24aa0" # capullo-audio - immutable jitpack commit; the engine + capullo-audio-ui share it. -capulloAudio = "42bc010136e2897f0eca6f4f53b3cb92f40468f3" +capulloAudio = "ba1ff9ae53c928f0e2b6ec22d23f6cf78c0ba924" # QC's own single-app deps (used by no other repo). media = "1.7.0" coil = "2.6.0"