From 7bb245741ba13ac89d33289879ffb61f58bcd8f9 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Mon, 23 Feb 2026 21:21:41 +0100 Subject: [PATCH] Fix last small issues with syncgroup --- .../controllers/players/controller.py | 3 ++- .../providers/sync_group/player.py | 22 +++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/music_assistant/controllers/players/controller.py b/music_assistant/controllers/players/controller.py index 9b1e402c..a8379efb 100644 --- a/music_assistant/controllers/players/controller.py +++ b/music_assistant/controllers/players/controller.py @@ -1331,13 +1331,13 @@ class PlayerController(ProtocolLinkingMixin, CoreController): player = self._players.get(player_id) if player is None: return - await self._cleanup_player_memberships(player_id) del self._players[player_id] self.mass.player_queues.on_player_remove(player_id, permanent=permanent) await player.on_unload() if permanent: # player permanent removal: cleanup protocol links, delete config # and signal PLAYER_REMOVED event + await self._cleanup_player_memberships(player_id) self._cleanup_protocol_links(player) self.delete_player_config(player_id) self.logger.info("Player removed: %s", player.name) @@ -2592,6 +2592,7 @@ class PlayerController(ProtocolLinkingMixin, CoreController): # ungroup player if it is synced (or is a sync leader itself) # NOTE: ungroup will be ignored if the player is not grouped or synced await self.cmd_ungroup(player_id) + player.set_active_output_protocol(None) # also clear active protocol if any # always stop player at power off if ( diff --git a/music_assistant/providers/sync_group/player.py b/music_assistant/providers/sync_group/player.py index f5d441b0..72f44d5f 100644 --- a/music_assistant/providers/sync_group/player.py +++ b/music_assistant/providers/sync_group/player.py @@ -231,9 +231,9 @@ class SyncGroupPlayer(Player): if prev_leader and prev_leader.player_id in (player_ids_to_remove or []): # We're removing the current sync leader while the group is active # We need to select a new leader before we can handle the member changes - self.logger.debug( + self.logger.info( "Removing current sync leader %s from group %s while it is active, " - "selecting a new leader and dissolving the current syncgroup", + "dissolving the current syncgroup and will re-form it with a new leader", prev_leader.display_name, self.display_name, ) @@ -282,16 +282,15 @@ class SyncGroupPlayer(Player): if needs_restart: await self.play() return - if not was_playing: + if not was_playing or not cur_leader: # Don't need to do anything else if the group is not active # The syncing will be done once playback starts return - if cur_leader: - await self.mass.players.cmd_set_members( - cur_leader.player_id, - player_ids_to_add=final_players_to_add, - player_ids_to_remove=final_players_to_remove, - ) + await self.mass.players.cmd_set_members( + cur_leader.player_id, + player_ids_to_add=final_players_to_add, + player_ids_to_remove=final_players_to_remove, + ) async def _form_syncgroup(self) -> None: """Form syncgroup by syncing all (possible) members.""" @@ -314,6 +313,11 @@ class SyncGroupPlayer(Player): if x != self.sync_leader.player_id and x not in self.sync_leader.state.group_members ] if members_to_sync: + # If the sync leader is playing something independently, stop it first + # to prevent protocol switching from trying to resume the previous playback + # (we're about to start new playback on the syncgroup) + if self.sync_leader.state.playback_state == PlaybackState.PLAYING: + await self.mass.players._handle_cmd_stop(self.sync_leader.player_id) await self.mass.players.cmd_set_members(self.sync_leader.player_id, members_to_sync) async def _dissolve_syncgroup(self) -> None: -- 2.34.1