fix(android): debounce non-PLAYING poll to suppress UPnP track-change flicker
android / Build + lint + test (push) Successful in 4m13s

This commit is contained in:
2026-06-03 23:19:29 -04:00
parent 33285b53c6
commit 6a7958c921
@@ -53,6 +53,12 @@ class MinstrelForwardingPlayer(
private val handler = Handler(delegate.applicationLooper)
private var pollJob: Job? = null
// Tracks consecutive non-PLAYING poll observations so a single transient
// PAUSED_PLAYBACK / STOPPED tick during a Sonos track transition does not
// flip the play/pause button. Manual pause still feels instant because it
// bypasses the poll entirely via applyTransportPaused().
@Volatile private var nonPlayingPollStreak = 0
init {
scope.launch {
holder.active.collect { active -> onActiveChanged(active) }
@@ -227,6 +233,7 @@ class MinstrelForwardingPlayer(
private fun onActiveChanged(active: ActiveUpnp?) {
pollJob?.cancel()
nonPlayingPollStreak = 0
if (active != null) {
Timber.w("UPnP active: %s -- pollLoop starting", active.routeName)
// Pause the wrapped ExoPlayer so we are not playing local audio
@@ -278,14 +285,28 @@ class MinstrelForwardingPlayer(
)
val transport = active.avTransport.getTransportInfo()
when (transport.state) {
TransportState.PLAYING -> remoteState.applyTransportPlaying()
TransportState.PAUSED -> remoteState.applyTransportPaused()
TransportState.STOPPED -> remoteState.applyTransportStopped()
TransportState.PLAYING -> {
nonPlayingPollStreak = 0
remoteState.applyTransportPlaying()
}
TransportState.PAUSED -> {
nonPlayingPollStreak += 1
if (nonPlayingPollStreak >= NON_PLAYING_CONFIRM) {
remoteState.applyTransportPaused()
}
}
TransportState.STOPPED -> {
nonPlayingPollStreak += 1
if (nonPlayingPollStreak >= NON_PLAYING_CONFIRM) {
remoteState.applyTransportStopped()
}
}
TransportState.TRANSITIONING, TransportState.UNKNOWN -> Unit
}
}
private companion object {
const val POLL_INTERVAL_MS = 1_000L
const val NON_PLAYING_CONFIRM = 2
}
}