From 0afd503b2ef76eebd27e5137b86905363815bedd Mon Sep 17 00:00:00 2001 From: Marvin Schenkel Date: Fri, 19 Dec 2025 15:29:58 +0100 Subject: [PATCH] Fix WiiM devices not starting as part of a group (#2855) --- .../providers/squeezelite/player.py | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/music_assistant/providers/squeezelite/player.py b/music_assistant/providers/squeezelite/player.py index b68ada64..9613fe83 100644 --- a/music_assistant/providers/squeezelite/player.py +++ b/music_assistant/providers/squeezelite/player.py @@ -282,6 +282,7 @@ class SqueezelitePlayer(Player): media=media, send_flush=True, auto_play=False, + is_group_playback=True, ) ) @@ -408,6 +409,7 @@ class SqueezelitePlayer(Player): enqueue: bool = False, send_flush: bool = True, auto_play: bool = False, + is_group_playback: bool = False, ) -> None: """Handle playback of an url on slimproto player(s).""" metadata = { @@ -435,6 +437,12 @@ class SqueezelitePlayer(Player): # to coordinate a start of multiple synced players autostart=auto_play, ) + # TODO: When we implement server clock sync, we can remove the pause here + # and rely on unpause_at + HEADROOM in the buffer_ready handler. LMS + # also does NOT use an explicit pause. For now, we pause here to avoid + # WiiM devices starting playback too early, causing huge initial drift. + if is_group_playback: + await slimplayer.pause() # if queue is set to single track repeat, # immediately set this track as the next # this prevents race conditions with super short audio clips (on single repeat) @@ -499,7 +507,7 @@ class SqueezelitePlayer(Player): # but I did not have any good results with that. # Instead just start playback on all players and let the sync logic work out # the delays etc. - tg.create_task(sync_client.pause_for(200)) + tg.create_task(pause_and_unpause(sync_client, 200)) async def _handle_player_cli_event(self, event: SlimEvent) -> None: """Process CLI Event.""" @@ -607,7 +615,7 @@ class SqueezelitePlayer(Player): # player lagging behind more than MAX_SKIP_AHEAD_MS, # we need to correct the sync_master self.logger.debug("%s resync: pauseFor %sms", sync_master.name, delta) - self.mass.create_task(sync_master.pause_for(delta)) + self.mass.create_task(pause_and_unpause(sync_master, delta)) elif avg_diff > 0: # handle player lagging behind, fix with skip_ahead self.logger.debug("%s resync: skipAhead %sms", self.display_name, delta) @@ -615,7 +623,7 @@ class SqueezelitePlayer(Player): else: # handle player is drifting too far ahead, use pause_for to adjust self.logger.debug("%s resync: pauseFor %sms", self.display_name, delta) - self.mass.create_task(self.client.pause_for(delta)) + self.mass.create_task(pause_and_unpause(self.client, delta)) async def _set_preset_items(self) -> None: """Set the presets for a player.""" @@ -678,6 +686,17 @@ class SqueezelitePlayer(Player): yield slimplayer +async def pause_and_unpause(slim_client: SlimClient, pause_duration_ms: int) -> None: + """Pause player and schedule unpause after specified duration. + + This is used instead of pause_for because WiiM devices + don't properly auto-unpause after pause_for interval. + """ + await slim_client.pause() + unpause_timestamp = slim_client.jiffies + pause_duration_ms + await slim_client.unpause_at(unpause_timestamp) + + async def _patched_send_strm( # noqa: PLR0913 self: SlimClient, command: bytes = b"q", -- 2.34.1