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 180e85b3..3db7a6e5 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 @@ -63,6 +63,7 @@ class PlayerController @Inject constructor( private val radio: RadioController, private val playerFactory: PlayerFactory, private val activeUpnpHolder: com.fabledsword.minstrel.player.output.ActiveUpnpHolder, + private val remoteState: RemotePlayerState, ) { /** @@ -471,10 +472,31 @@ class PlayerController @Inject constructor( scope.launch(Dispatchers.Main.immediate) { while (isActive) { delay(POSITION_POLL_INTERVAL_MS) - if (!controller.isPlaying) continue - uiStateInternal.value = uiStateInternal.value.copy( - positionMs = controller.currentPosition.coerceAtLeast(0), - bufferedPositionMs = controller.bufferedPosition.coerceAtLeast(0), + // When UPnP is active, MediaController's cached state is stale -- + // remoteState.applyTransportPlaying() doesn't fire a Player.Listener + // event, so onEvents never refreshes isPlaying / positionMs from + // the wrapped player. Read directly from remoteState here so the + // play/pause toggle and scrubber reflect Sonos's actual state. + val upnpActive = activeUpnpHolder.active.value != null + val effectiveIsPlaying = + if (upnpActive) remoteState.isPlaying else controller.isPlaying + val effectivePosition = + if (upnpActive) remoteState.positionMs else controller.currentPosition + val effectiveDuration = + if (upnpActive) remoteState.durationMs else controller.duration + val current = uiStateInternal.value + val newPos = effectivePosition.coerceAtLeast(0) + val newDur = effectiveDuration.coerceAtLeast(0) + val newBuf = controller.bufferedPosition.coerceAtLeast(0) + val changed = current.isPlaying != effectiveIsPlaying || + current.positionMs != newPos || + current.durationMs != newDur + if (!changed) continue + uiStateInternal.value = current.copy( + isPlaying = effectiveIsPlaying, + positionMs = newPos, + durationMs = newDur, + bufferedPositionMs = newBuf, ) } }