From 88b161193d7e01e396efa14a957986f4266861c4 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Thu, 4 Jun 2026 00:03:48 -0400 Subject: [PATCH] fix(android): TrackRef.durationSec is final fallback so duration is never 0 --- .../minstrel/player/PlayerController.kt | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/android/app/src/main/java/com/fabledsword/minstrel/player/PlayerController.kt b/android/app/src/main/java/com/fabledsword/minstrel/player/PlayerController.kt index 4275da1c..ffa3d021 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/player/PlayerController.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/player/PlayerController.kt @@ -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 } }