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 val handler = Handler(delegate.applicationLooper)
private var pollJob: Job? = null 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 { init {
scope.launch { scope.launch {
holder.active.collect { active -> onActiveChanged(active) } holder.active.collect { active -> onActiveChanged(active) }
@@ -227,6 +233,7 @@ class MinstrelForwardingPlayer(
private fun onActiveChanged(active: ActiveUpnp?) { private fun onActiveChanged(active: ActiveUpnp?) {
pollJob?.cancel() pollJob?.cancel()
nonPlayingPollStreak = 0
if (active != null) { if (active != null) {
Timber.w("UPnP active: %s -- pollLoop starting", active.routeName) Timber.w("UPnP active: %s -- pollLoop starting", active.routeName)
// Pause the wrapped ExoPlayer so we are not playing local audio // Pause the wrapped ExoPlayer so we are not playing local audio
@@ -278,14 +285,28 @@ class MinstrelForwardingPlayer(
) )
val transport = active.avTransport.getTransportInfo() val transport = active.avTransport.getTransportInfo()
when (transport.state) { when (transport.state) {
TransportState.PLAYING -> remoteState.applyTransportPlaying() TransportState.PLAYING -> {
TransportState.PAUSED -> remoteState.applyTransportPaused() nonPlayingPollStreak = 0
TransportState.STOPPED -> remoteState.applyTransportStopped() 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 TransportState.TRANSITIONING, TransportState.UNKNOWN -> Unit
} }
} }
private companion object { private companion object {
const val POLL_INTERVAL_MS = 1_000L const val POLL_INTERVAL_MS = 1_000L
const val NON_PLAYING_CONFIRM = 2
} }
} }