From 33285b53c6acc6ddad816f33e7c688a3c6bb4eed Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 3 Jun 2026 23:08:32 -0400 Subject: [PATCH] fix(android): refresh uiState.isPlaying from remoteState during UPnP --- .../minstrel/player/PlayerController.kt | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 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 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, ) } }