Fix last small issues with syncgroup
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Mon, 23 Feb 2026 20:21:41 +0000 (21:21 +0100)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Mon, 23 Feb 2026 20:21:41 +0000 (21:21 +0100)
music_assistant/controllers/players/controller.py
music_assistant/providers/sync_group/player.py

index 9b1e402c748794b09fdcd19bea16770cede07f33..a8379efbc503a67d80fe4c360779c16fc2f2b086 100644 (file)
@@ -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 (
index f5d441b05f51e49e22f449127e7907990cfd4a78..72f44d5f9934042a7b019e38d7a8a449156df1c9 100644 (file)
@@ -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: