Some small fixes for issues found in beta testing (#2482)
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Sat, 4 Oct 2025 14:33:07 +0000 (16:33 +0200)
committerGitHub <noreply@github.com>
Sat, 4 Oct 2025 14:33:07 +0000 (16:33 +0200)
music_assistant/controllers/streams.py
music_assistant/helpers/smart_fades.py
music_assistant/providers/sonos/player.py

index ec06a4caafeb36e68954a2ceab8958d150c0cf50..a8b6483d1f8890d8865ff3d959176d5606d37211 100644 (file)
@@ -910,9 +910,7 @@ class StreamsController(CoreController):
                         )
                     # send crossfade_part (as one big chunk)
                     yield crossfade_part
-
                     del crossfade_part
-
                     # also write the leftover bytes from the crossfade action
                     if remaining_bytes:
                         yield remaining_bytes
@@ -935,7 +933,9 @@ class StreamsController(CoreController):
                 yield last_fadeout_part
                 bytes_written += len(last_fadeout_part)
                 last_fadeout_part = b""
-            if self._crossfade_allowed(queue_track, flow_mode=True):
+            if self._crossfade_allowed(
+                queue_track, smart_fades_mode=smart_fades_mode, flow_mode=True
+            ):
                 # if crossfade is enabled, save fadeout part to pickup for next track
                 last_fadeout_part = buffer[-crossfade_size:]
                 last_streamdetails = queue_track.streamdetails
@@ -1204,7 +1204,9 @@ class StreamsController(CoreController):
 
         #### HANDLE END OF TRACK
 
-        if not self._crossfade_allowed(queue_item, flow_mode=False):
+        if not self._crossfade_allowed(
+            queue_item, smart_fades_mode=smart_fades_mode, flow_mode=False
+        ):
             # no crossfade enabled/allowed, just yield the buffer last part
             bytes_written += len(buffer)
             yield buffer
@@ -1381,8 +1383,12 @@ class StreamsController(CoreController):
         # reschedule self
         self.mass.call_later(3600, self._clean_audio_cache)
 
-    def _crossfade_allowed(self, queue_item: QueueItem, flow_mode: bool = False) -> bool:
+    def _crossfade_allowed(
+        self, queue_item: QueueItem, smart_fades_mode: SmartFadesMode, flow_mode: bool = False
+    ) -> bool:
         """Get the crossfade config for a queue item."""
+        if smart_fades_mode == SmartFadesMode.DISABLED:
+            return False
         if not (queue_player := self.mass.players.get(queue_item.queue_id)):
             return False  # just a guard
         if queue_item.media_type != MediaType.TRACK:
index a13d072d88466ed9f9da7208a92def2f399ab08d..13ae8ed172834cc3ac87f32d05b87ea25e2ac415 100644 (file)
@@ -253,6 +253,11 @@ class SmartFadesMixer:
         mode: SmartFadesMode = SmartFadesMode.SMART_FADES,
     ) -> bytes:
         """Apply crossfade with internal state management and smart/standard fallback logic."""
+        if mode == SmartFadesMode.DISABLED:
+            # No crossfade, just concatenate
+            # Note that this should not happen since we check this before calling mix()
+            # but just to be sure...
+            return fade_out_part + fade_in_part
         if mode == SmartFadesMode.STANDARD_CROSSFADE:
             # crossfade with standard crossfade
             return await self._default_crossfade(
index a847062d8ec92ed105d9f2f951fbfbd1f5ba3e6e..a001ed8ef11762e3a95102aec966d1dda3c53afb 100644 (file)
@@ -480,21 +480,20 @@ class SonosPlayer(Player):
         if airplay_player := self.get_linked_airplay_player(False):
             # if airplay mode is enabled, we could possibly receive child player id's that are
             # not Sonos players, but AirPlay players. We redirect those.
-            airplay_player_ids_to_add = [x for x in player_ids_to_add if x.startswith("ap")]
-            player_ids_to_add = [x for x in player_ids_to_add if x not in airplay_player_ids_to_add]
-            airplay_player_ids_to_remove = [x for x in player_ids_to_remove if x.startswith("ap")]
-            player_ids_to_remove = [
-                x for x in player_ids_to_remove if x not in airplay_player_ids_to_remove
-            ]
+            airplay_player_ids_to_add = {x for x in player_ids_to_add if x.startswith("ap")}
+            airplay_player_ids_to_remove = {x for x in player_ids_to_remove if x.startswith("ap")}
             if airplay_player_ids_to_add or airplay_player_ids_to_remove:
                 await self.mass.players.cmd_set_members(
                     airplay_player.player_id,
-                    player_ids_to_add=airplay_player_ids_to_add,
-                    player_ids_to_remove=airplay_player_ids_to_remove,
+                    player_ids_to_add=list(airplay_player_ids_to_add),
+                    player_ids_to_remove=list(airplay_player_ids_to_remove),
                 )
-        if player_ids_to_add or player_ids_to_remove:
+        sonos_player_ids_to_add = {x for x in player_ids_to_add if not x.startswith("ap")}
+        sonos_player_ids_to_remove = {x for x in player_ids_to_remove if not x.startswith("ap")}
+        if sonos_player_ids_to_add or sonos_player_ids_to_remove:
             await self.client.player.group.modify_group_members(
-                player_ids_to_add=player_ids_to_add, player_ids_to_remove=player_ids_to_remove
+                player_ids_to_add=list(sonos_player_ids_to_add),
+                player_ids_to_remove=list(sonos_player_ids_to_remove),
             )
 
     async def ungroup(self) -> None:
@@ -835,15 +834,22 @@ class SonosPlayer(Player):
         # Sonos has an annoying bug (for years already, and they dont seem to care),
         # where it looses its sync childs when airplay playback is (re)started.
         # Try to handle it here with this workaround.
-        group_childs = [x for x in self.client.player.group.player_ids if x != player_id]
-        if group_childs:
-            await self.mass.players.cmd_ungroup_many(group_childs)
+        org_group_childs = {x for x in self.client.player.group.player_ids if x != player_id}
+        if org_group_childs:
+            # ungroup all childs first
+            await self.client.player.group.modify_group_members(
+                player_ids_to_add=[], player_ids_to_remove=list(org_group_childs)
+            )
+        # start playback on the airplay player
         await self.mass.players.play_media(airplay_player.player_id, media)
-        if group_childs:
-            # ensure master player is first in the list
-            group_childs = [self.player_id, *group_childs]
-            await asyncio.sleep(5)
-            await self.client.player.group.set_group_members(group_childs)
+        # re-add the original group childs to the sonos player if needed
+        if org_group_childs:
+            # wait a bit to let the airplay playback start
+            await asyncio.sleep(3)
+            await self.client.player.group.modify_group_members(
+                player_ids_to_add=list(org_group_childs),
+                player_ids_to_remove=[],
+            )
 
     async def _play_media_legacy(
         self,