)
# 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
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
#### 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
# 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:
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(
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:
# 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,