Skip to content

fix: resolve post-reconnection state desync#51

Merged
chrisuthe merged 1 commit intomainfrom
task/fix-reconnect-state-desync
Mar 7, 2026
Merged

fix: resolve post-reconnection state desync#51
chrisuthe merged 1 commit intomainfrom
task/fix-reconnect-state-desync

Conversation

@chrisuthe
Copy link
Copy Markdown
Owner

Summary

Fixes a bug where audio continues playing but the UI shows "stopped" after a network reconnection (e.g., switching from cellular to WiFi).

Root cause: After reconnection, the server pushes a "stopped" state (because the player briefly disconnected). The client's onStateChanged handler had no DRAINING protection, so it would unconditionally set playWhenReady=false and clear the buffer -- even while audio was still playing from the reconnection buffer. A timing race with exitDraining() meant the DRAINING check in onGroupUpdate also couldn't fire in time.

Four fixes:

  • Add DRAINING check to onStateChanged (previously only onGroupUpdate had it)
  • Defer exitDraining() until after the first state/group message is processed (fixes mainHandler ordering race)
  • Sync playWhenReady in updateStateFromPlayer() when audio is physically PLAYING/DRAINING (safety net)
  • Move updatePlayWhenReadyFromServer() into per-state branches so it's skipped during DRAINING+STOPPED

Test plan

  • New ReconnectStateSyncTest with 13 tests covering all 4 fixes
  • All existing unit tests pass
  • Manual test: play audio, switch from cell to WiFi -- verify UI stays in sync
  • Manual test: play audio, toggle airplane mode briefly -- verify playback resumes with correct UI state

@chrisuthe chrisuthe force-pushed the task/fix-reconnect-state-desync branch from 8b84f0f to 41f524e Compare March 7, 2026 20:20
… stopped)

Four related fixes for state synchronization after network reconnection
(e.g., cell to WiFi transition):

1. Add DRAINING check to onStateChanged - previously only onGroupUpdate
   had this protection. A server/state "stopped" message during DRAINING
   now sends play() to resume instead of clearing the buffer.

2. Defer exitDraining() until after the first state/group message is
   processed. Previously, onReconnected posted exitDraining() to the
   mainHandler before onStateChanged/onGroupUpdate, so the DRAINING
   checks would see PLAYING instead of DRAINING.

3. Sync playWhenReady in updateStateFromPlayer() when audio is physically
   PLAYING/DRAINING. If playWhenReady was stale (false from an earlier
   server "stopped"), the UI would show "not playing" even though audio
   was audible.

4. Move updatePlayWhenReadyFromServer() into per-state branches in both
   handlers. During DRAINING+STOPPED, playWhenReady is not set to false,
   preventing the UI from briefly flipping to "stopped" before the server
   resumes playback.
@chrisuthe chrisuthe merged commit 3ee6603 into main Mar 7, 2026
@chrisuthe chrisuthe deleted the task/fix-reconnect-state-desync branch March 8, 2026 02:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant