Fix sync group member playing out of sync after concurrent group changes#4189
Merged
Conversation
… changes A leader change dissolves and re-forms the group, promoting a new leader and starting it playing. play()/play_media() returned as soon as the play command was dispatched - before the device actually started - so the group's playback lock was released while the start was still in flight. A closely-following command (e.g. HA powering off the old leader while ungrouping another member) could then race it: the play landed after the stop, leaving that player streaming while the group's logical state had moved on without it - same audio, seconds out of sync, not shown as grouped. Hold the lock until the leader confirms playback so any queued command observes a settled state, mirroring the existing stop/dissolve side.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes a race in sync group leader (re)starts where the group playback lock could be released before the leader actually begins playing, allowing near-simultaneous (un)group operations to “strand” a member playing out of sync and outside the group’s logical/UI state.
Changes:
- Adds a
_await_leader_playback()async context manager to keep the group playback lock held until the sync leader reportsPlaybackState.PLAYING(or times out). - Introduces
PLAYBACK_START_TIMEOUTto bound the wait duration. - Adds regression tests verifying
play()andplay_media()wrap the leader start/resume with the wait, plus a no-leader no-op case.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
music_assistant/providers/sync_group/player.py |
Wraps resume/play_media with a leader playback wait context manager to prevent start/stop races during concurrent group changes. |
music_assistant/providers/sync_group/constants.py |
Adds a dedicated timeout constant for awaiting leader playback start confirmation. |
tests/providers/test_sync_group.py |
Adds tests asserting the leader playback wait is armed before issuing start commands and awaited before returning. |
marcelveldt
added a commit
that referenced
this pull request
Jun 13, 2026
anatosun
pushed a commit
to anatosun/music-assistant-server
that referenced
this pull request
Jun 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this implement/fix?
A sync group member could end up playing the group's content on its own — the same audio on the speaker but several seconds out of sync, and not shown as part of the group in the UI.
A leader change dissolves and re-forms the group, which promotes a new leader and starts it playing.
play()/play_media()returned as soon as the play command was dispatched to the device — before it actually started — so the group's playback lock was released while the start was still in flight. When a second membership command arrives almost simultaneously (e.g. Home Assistant powering off the current leader while ungrouping another member), it acquires the lock next and dissolves the group, racing the in-flight start: the play lands at the device after the stop, stranding that player streaming while the group's logical state has already moved on without it. Re-joining the player resyncs it.The fix makes the (re)start synchronous within the lock, mirroring the stop/dissolve side which already waits for the player to settle.
Related issue (if applicable):
Changes
_await_leader_playback()context manager;play()andplay_media()wrap the leader's resume/play in it, so the group's playback lock stays held until the sync leader actually reports playing (or a short timeout elapses).PLAYBACK_START_TIMEOUTconstant for that wait.Types of changes
bugfixnew-featureenhancementnew-providerbreaking-changerefactordocumentationmaintenancecidependenciesChecklist
pre-commit run --all-filespasses.pytestpasses, and tests have been added/updated undertests/where applicable.music-assistant/modelsis linked.music-assistant/frontendis linked.