fix(android): hold UI patch until cursor sync lands; reanchor on track flip
android / Build + lint + test (push) Failing after 1m21s

This commit is contained in:
2026-06-04 00:37:34 -04:00
parent 2425a305eb
commit c245b1ef0b
@@ -411,6 +411,11 @@ class PlayerController @Inject constructor(
// Reset the per-item evaluation guard so the new
// item's STATE_READY transition gets a fresh check.
lastEvaluatedItemIndex = -1
// The track flipped -- re-anchor the position interpolator
// so the next polling tick treats the new track's
// remoteState.positionMs as fresh rather than carrying the
// old anchor + elapsed forward.
lastSeenRemotePositionMs = -1L
}
override fun onPlaybackStateChanged(playbackState: Int) {
@@ -493,6 +498,14 @@ class PlayerController @Inject constructor(
while (isActive) {
delay(POSITION_POLL_INTERVAL_MS)
val upnpActive = activeUpnpHolder.active.value != null
// Track-alignment gate: if Sonos's reported Track is past the
// wrapped player's currentMediaItemIndex, a forward cursor sync
// is in flight (pollOnce wrote new remoteState, handler.post'd
// delegate.seekTo; the seek hasn't run yet). Skipping the tick
// here lets onMediaItemTransition rebuild the full uiState
// atomically rather than patching position+duration while
// currentTrack/queueIndex still point at the old track.
if (upnpActive && isForwardCursorSyncPending(controller)) continue
val effectiveIsPlaying =
if (upnpActive) remoteState.isPlaying else controller.isPlaying
val effectivePosition = if (upnpActive) {
@@ -533,6 +546,12 @@ class PlayerController @Inject constructor(
@Volatile private var positionAnchorMs: Long = 0L
@Volatile private var positionAnchorAtRealtimeMs: Long = 0L
private fun isForwardCursorSyncPending(controller: MediaController): Boolean {
val sonosTrack = remoteState.trackNumber
if (sonosTrack <= 0) return false
return (sonosTrack - 1) > controller.currentMediaItemIndex
}
private fun interpolatedRemotePosition(isPlaying: Boolean): Long {
val raw = remoteState.positionMs
val now = SystemClock.elapsedRealtime()