fix(android): TrackRef.durationSec is final fallback so duration is never 0
android / Build + lint + test (push) Successful in 5m54s

This commit is contained in:
2026-06-04 00:03:48 -04:00
parent 9628ed1749
commit 88b161193d
@@ -581,19 +581,27 @@ class PlayerController @Inject constructor(
Bundle().apply { putString(MINSTREL_SOURCE_KEY, source) }
/**
* Duration to surface to the UI. When UPnP is active the remote's
* reported duration wins -- but if Sonos hasn't reported one yet
* (pre-first-poll window after activation, or transient 0 during a
* track-change buffer), fall back to the wrapped ExoPlayer's value
* for that MediaItem so the scrubber doesn't blink to zero.
* Duration to surface to the UI. Priority: Sonos's reported duration
* (only when UPnP active and non-zero) -> wrapped ExoPlayer's value
* (only valid once it's probed the stream) -> TrackRef.durationSec
* (always known from the server response). The third tier is what
* keeps the scrubber populated when the user taps Sonos before the
* wrapped player has had time to probe its own duration -- without
* it, both top tiers report 0/TIME_UNSET and the field reads empty
* until the first SOAP poll lands.
*/
@Suppress("ReturnCount") // 3-tier fallback reads cleanest as a ladder of early returns
private fun effectiveDuration(upnpActive: Boolean, remoteMs: Long, localMs: Long): Long {
val pick = if (upnpActive && remoteMs > 0) remoteMs else localMs
return pick.coerceAtLeast(0)
if (upnpActive && remoteMs > 0) return remoteMs
if (localMs > 0) return localMs
val idx = mediaController?.currentMediaItemIndex ?: 0
val ref = queueRefs.getOrNull(idx) ?: return 0
return ref.durationSec.toLong() * MS_PER_SECOND
}
companion object {
const val MINSTREL_SOURCE_KEY: String = "minstrel_source"
private const val MS_PER_SECOND = 1_000L
}
}