From 907e2722a282d93df74cc1452a467fa53078158a Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Sat, 4 Oct 2025 16:33:07 +0200 Subject: [PATCH] Some small fixes for issues found in beta testing (#2482) --- music_assistant/controllers/streams.py | 16 ++++++--- music_assistant/helpers/smart_fades.py | 5 +++ music_assistant/providers/sonos/player.py | 42 +++++++++++++---------- 3 files changed, 40 insertions(+), 23 deletions(-) diff --git a/music_assistant/controllers/streams.py b/music_assistant/controllers/streams.py index ec06a4ca..a8b6483d 100644 --- a/music_assistant/controllers/streams.py +++ b/music_assistant/controllers/streams.py @@ -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: diff --git a/music_assistant/helpers/smart_fades.py b/music_assistant/helpers/smart_fades.py index a13d072d..13ae8ed1 100644 --- a/music_assistant/helpers/smart_fades.py +++ b/music_assistant/helpers/smart_fades.py @@ -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( diff --git a/music_assistant/providers/sonos/player.py b/music_assistant/providers/sonos/player.py index a847062d..a001ed8e 100644 --- a/music_assistant/providers/sonos/player.py +++ b/music_assistant/providers/sonos/player.py @@ -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, -- 2.34.1