From 76485927863a5066734fbea0419d4ffd7d12ddd4 Mon Sep 17 00:00:00 2001 From: Maxim Raznatovski Date: Fri, 5 Sep 2025 12:22:27 +0200 Subject: [PATCH] Fix snapcast grouping after the player model refactor (#2372) --- music_assistant/providers/snapcast/player.py | 44 +++++++------------ .../providers/snapcast/provider.py | 3 +- 2 files changed, 17 insertions(+), 30 deletions(-) diff --git a/music_assistant/providers/snapcast/player.py b/music_assistant/providers/snapcast/player.py index b4d81754..06b6f38b 100644 --- a/music_assistant/providers/snapcast/player.py +++ b/music_assistant/providers/snapcast/player.py @@ -119,6 +119,7 @@ class SnapCastPlayer(Player): self._stream_task.cancel() with suppress(asyncio.CancelledError): await self._stream_task + self._stream_task = None async def volume_mute(self, muted: bool) -> None: """Send MUTE command to given player.""" @@ -137,39 +138,25 @@ class SnapCastPlayer(Player): assert group is not None # for type checking # handle client additions for player_id in player_ids_to_add or []: - if player_id not in group.clients: - snapcast_id = self.provider._get_snapclient_id(player_id) + snapcast_id = self.provider._get_snapclient_id(player_id) + if snapcast_id not in group.clients: await group.add_client(snapcast_id) - self._attr_group_members.append(player_id) + if player_id not in self._attr_group_members: + self._attr_group_members.append(player_id) # handle client removals for player_id in player_ids_to_remove or []: - if player_id in group.clients: - snapcast_id = self.provider._get_snapclient_id(player_id) + snapcast_id = self.provider._get_snapclient_id(player_id) + if snapcast_id in group.clients: await group.remove_client(snapcast_id) - self._attr_group_members.remove(player_id) + if player_id in self._attr_group_members: + self._attr_group_members.remove(player_id) + # Set default stream and stop ungrouped players + removed_snapclient = self.provider._snapserver.client(snapcast_id) + await removed_snapclient.group.set_stream("default") + if removed_player := self.mass.players.get(player_id): + await removed_player.stop() self.update_state() - async def ungroup(self) -> None: - """Ungroup.""" - if self.synced_to is None: - for mass_child_id in list(self.group_members): - if mass_child_id != self.player_id: - if child_player := self.mass.players.get(mass_child_id): - await child_player.ungroup() - return - mass_sync_master_player = self.mass.players.get(self.synced_to) - assert mass_sync_master_player is not None # for type checking - mass_sync_master_player._attr_group_members.remove(self.player_id) - group = self._get_snapgroup() - assert group is not None # for type checking - await group.remove_client(self.snap_client_id) - # assign default/empty stream to the player - await group.set_stream("default") - await self.stop() - # make sure that the player manager gets an update - self.update_state() - mass_sync_master_player.update_state() - async def play_media(self, media: PlayerMedia) -> None: """Handle PLAY MEDIA on given player.""" # ruff: noqa: PLR0915 @@ -183,6 +170,7 @@ class SnapCastPlayer(Player): self._stream_task.cancel() with suppress(asyncio.CancelledError): await self._stream_task + self._stream_task = None # get stream or create new one stream_name = self._get_stream_name(SnapCastStreamType.MUSIC) @@ -391,7 +379,7 @@ class SnapCastPlayer(Player): 2. Easily identify which stream belongs to which player, for instance to be able to delete a music stream even when it is not active due to an announcement. """ - safe_name = create_safe_string(self.display_name, replace_space=True) + safe_name = create_safe_string(self.player_id, replace_space=True) stream_name = f"{MASS_STREAM_PREFIX}{safe_name}" if stream_type == SnapCastStreamType.ANNOUNCEMENT: stream_name += MASS_ANNOUNCEMENT_POSTFIX diff --git a/music_assistant/providers/snapcast/provider.py b/music_assistant/providers/snapcast/provider.py index 05e08b53..714bdeae 100644 --- a/music_assistant/providers/snapcast/provider.py +++ b/music_assistant/providers/snapcast/provider.py @@ -82,7 +82,6 @@ class SnapCastProvider(PlayerProvider): str(self.config.get_value(CONF_SERVER_CONTROL_PORT)) ) self._snapcast_stream_idle_threshold = self.config.get_value(CONF_STREAM_IDLE_THRESHOLD) - self._stream_tasks = {} self._ids_map = bidict({}) if self._use_builtin_server: @@ -277,7 +276,7 @@ class SnapCastProvider(PlayerProvider): for snap_client in self._snapserver.clients: if ma_player := self.mass.players.get(self._get_ma_id(snap_client.identifier)): assert isinstance(ma_player, SnapCastPlayer) # for type checking - snap_client.set_callback(ma_player._handle_player_update) + ma_player._handle_player_update(snap_client) def _handle_disconnect(self, exc: Exception) -> None: """Handle disconnect callback from snapserver.""" -- 2.34.1