Fix sync of slimproto/airplay players (#707)
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Mon, 12 Jun 2023 10:17:16 +0000 (12:17 +0200)
committerGitHub <noreply@github.com>
Mon, 12 Jun 2023 10:17:16 +0000 (12:17 +0200)
* Fix audio sync on slimproto (and airplay) players

* fix coordinated start of all clients

music_assistant/server/providers/slimproto/__init__.py
music_assistant/server/providers/slimproto/cli.py

index 55f3e02ba8044fa2144dfc210690a9b61829be31..654096a11440dd454208f3edce5a7973c2b463e3 100644 (file)
@@ -603,22 +603,21 @@ class SlimprotoProvider(PlayerProvider):
 
         # make sure client has loaded the same track as sync master
         client_item_id = client.current_metadata["item_id"] if client.current_metadata else None
-        prev_item_id = client._next_metadata["item_id"] if client._next_metadata else None
         master_item_id = (
             sync_master.current_metadata["item_id"] if sync_master.current_metadata else None
         )
         if client_item_id != master_item_id:
             return
-        if client_item_id and prev_item_id and client_item_id != prev_item_id:
-            # transitioning
-            sync_playpoints.clear()
+        # ignore sync when player is transitioning to a new track (next metadata is loaded)
+        next_item_id = client._next_metadata["item_id"] if client._next_metadata else None
+        if next_item_id and client_item_id != next_item_id:
             return
 
         last_playpoint = sync_playpoints[-1] if sync_playpoints else None
         if last_playpoint and (time.time() - last_playpoint.timestamp) > 10:
             # last playpoint is too old, invalidate
             sync_playpoints.clear()
-        if last_playpoint and last_playpoint.item_id != client.current_metadata["item_id"]:
+        if last_playpoint and last_playpoint.item_id != client_item_id:
             # item has changed, invalidate
             sync_playpoints.clear()
 
@@ -633,13 +632,13 @@ class SlimprotoProvider(PlayerProvider):
             return
 
         # we can now append the current playpoint to our list
-        sync_playpoints.append(SyncPlayPoint(time.time(), client.current_metadata["item_id"], diff))
+        sync_playpoints.append(SyncPlayPoint(time.time(), client_item_id, diff))
 
         if len(sync_playpoints) < MIN_REQ_PLAYPOINTS:
             return
 
         # get the average diff
-        avg_diff = statistics.fmean(sync_playpoints)
+        avg_diff = statistics.fmean(x.diff for x in sync_playpoints)
         delta = abs(avg_diff)
 
         if delta < MIN_DEVIATION_ADJUST:
@@ -691,7 +690,10 @@ class SlimprotoProvider(PlayerProvider):
                 break
             await asyncio.sleep(0.2)
         # all child's ready (or timeout) - start play
-        await self.cmd_play(player.player_id)
+        async with asyncio.TaskGroup() as tg:
+            for client in self._get_sync_clients(player.player_id):
+                timestamp = client.jiffies + 100
+                tg.create_task(client.send_strm(b"u", replay_gain=int(timestamp)))
 
     async def _handle_connected(self, client: SlimClient) -> None:
         """Handle a client connected event."""
index ad5e8fa1efa8cedf315a5f55af5a02f594871fde..06d09fd98e09f4747a75d1ee26b8e2e40c7c7f5e 100644 (file)
@@ -1285,7 +1285,7 @@ def dict_to_strings(source: dict) -> list[str]:
                 else:
                     result.append(str(subval))
         elif isinstance(value, dict):
-            result += dict_to_strings(subval)
+            result += dict_to_strings(value)
         else:
             result.append(f"{key}:{str(value)}")
     return result