From: Maxim Raznatovski Date: Sun, 16 Feb 2025 16:59:34 +0000 (+0100) Subject: Fix: Double grouped players with Universal Groups (#1959) X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=54ea95fce1d29b66067ba843c5a3781554a6049c;p=music-assistant-server.git Fix: Double grouped players with Universal Groups (#1959) * fix: don't allow grouping if already grouped * fix: periodically check if no subgroups are in a universal group * feat: auto ungroup when adding to a universal group * fix: restart playback if a player was automatically ungrouped from a invalid subgroup * refactor: early return --- diff --git a/music_assistant/providers/player_group/__init__.py b/music_assistant/providers/player_group/__init__.py index 3aa2eeca..c022b7c3 100644 --- a/music_assistant/providers/player_group/__init__.py +++ b/music_assistant/providers/player_group/__init__.py @@ -551,6 +551,7 @@ class PlayerGroupProvider(PlayerProvider): """ if group_player := self.mass.players.get(player_id): self._update_attributes(group_player) + await self._ungroup_subgroups_if_found(group_player) async def create_group( self, group_type: str, name: str, members: list[str], dynamic: bool = False @@ -629,6 +630,9 @@ class PlayerGroupProvider(PlayerProvider): ) group_player.group_childs.append(player_id) + # Ensure that all player are just in this group and not in any other group + await self._ungroup_subgroups_if_found(group_player) + # handle resync/resume if group player was already playing if group_player.state == PlayerState.PLAYING and group_type == GROUP_TYPE_UNIVERSAL: child_player_provider = self.mass.players.get_player_provider(player_id) @@ -893,6 +897,36 @@ class PlayerGroupProvider(PlayerProvider): player.can_group_with = can_group_with self.mass.players.update(player.player_id) + async def _ungroup_subgroups_if_found(self, player: Player) -> None: + """Verify that no player is part of a separate group.""" + group_type = self.mass.config.get_raw_player_config_value( + player.player_id, CONF_ENTRY_GROUP_TYPE.key, CONF_ENTRY_GROUP_TYPE.default_value + ) + if group_type != GROUP_TYPE_UNIVERSAL: + return + + changed = False + # Verify that no player is part of a separate group + for child_player_id in player.group_childs: + child_player = self.mass.players.get(child_player_id) + if PlayerFeature.SET_MEMBERS not in child_player.supported_features: + continue + if child_player.group_childs: + # This is a leader in another group + player_provider = self.mass.players.get_player_provider(child_player_id) + for sync_child_id in child_player.group_childs: + if sync_child_id == child_player_id: + continue + await player_provider.cmd_ungroup(sync_child_id) + changed = True + if child_player.synced_to: + # This is a member of another group + await self.cmd_ungroup_member(child_player.player_id, child_player.synced_to) + changed = True + if changed and player.state == PlayerState.PLAYING: + # Restart playback to ensure all members play the same content + await self.mass.player_queues.resume(player.player_id) + async def _serve_ugp_stream(self, request: web.Request) -> web.Response: """Serve the UGP (multi-client) flow stream audio to a player.""" ugp_player_id = request.path.rsplit(".")[0].rsplit("/")[-1]