From 4d0a0b8e09546af520bff0e0e49208fdfb00c466 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Thu, 4 Jun 2026 11:03:55 -0400 Subject: [PATCH] fix(android): throttle UPnP extend with 50ms delay per successful AddURIToQueue Closes Scribe #611. The 2026-06-04 logcat showed 33 consecutive AddURIToQueue failures clustered at ~10ms intervals once the burst hit offset 39 -- characteristic of Sonos's burst-add rate-limit. 50ms between successful adds adds ~5s to the 100-track background extension but eliminates the burst rejection. Next reproduction with the SOAP fault detail logging (audit commit c5b326c6) will confirm the fault code if any tracks still fail. --- .../minstrel/player/output/OutputPickerController.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/android/app/src/main/java/com/fabledsword/minstrel/player/output/OutputPickerController.kt b/android/app/src/main/java/com/fabledsword/minstrel/player/output/OutputPickerController.kt index 48fdc0de..f1f9307c 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/player/output/OutputPickerController.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/player/output/OutputPickerController.kt @@ -19,6 +19,7 @@ import com.fabledsword.minstrel.player.output.upnp.UpnpDiscoveryController import com.fabledsword.minstrel.player.output.upnp.bareUdn import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -372,6 +373,13 @@ class OutputPickerController @Inject constructor( if (outcome.isSuccess) { consecutiveFailures = 0 succeeded += 1 + // Throttle the burst so we don't tickle Sonos's burst-add + // rejection -- logcat 2026-06-04 showed 33 consecutive + // failures clustered at ~10ms intervals once offset 39 was + // reached, which looks like a rate-limit kicking in. The + // delay is small enough that extending 100 tracks adds + // only ~5s to background work that's already async. + delay(EXTEND_THROTTLE_MS) } else { consecutiveFailures += 1 val e = outcome.exceptionOrNull() @@ -423,5 +431,6 @@ class OutputPickerController @Inject constructor( private companion object { const val EXTEND_ABORT_AFTER_FAILURES = 3 + const val EXTEND_THROTTLE_MS = 50L } }