Fix really strange bug with airplay
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Thu, 26 Feb 2026 01:21:59 +0000 (02:21 +0100)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Thu, 26 Feb 2026 01:21:59 +0000 (02:21 +0100)
music_assistant/models/player.py
music_assistant/providers/airplay/protocols/_protocol.py

index 98e1c18ef5743abf38fc34698d19494fe8f25d01..801643a88ae67adfcd2c4c526c20ee9d74db5461 100644 (file)
@@ -1123,7 +1123,11 @@ class Player(ABC):
         changed_values = self.__calculate_player_state()
         if prev_media_checksum != self._get_player_media_checksum():
             # current media changed, call the media updated callback
-            self._on_player_media_updated()
+            # debounce the callback to avoid multiple calls when multiple
+            # state updates happen in a short time
+            self.mass.call_later(
+                1, self._on_player_media_updated, task_id=f"player_media_updated_{self.player_id}"
+            )
         # ignore some values that are not relevant for the state
         changed_values.pop("elapsed_time_last_updated", None)
         changed_values.pop("extra_attributes.seq_no", None)
index fe7dced64bedffa61af5aad38e964565f536bb43..791528c3c48faf6ee2456fa786c88340ff676389 100644 (file)
@@ -58,6 +58,8 @@ class AirPlayProtocol(ABC):
         self._total_bytes_sent = 0
         self._stream_bytes_sent = 0
         self._connected = asyncio.Event()
+        self._metadata_checksum = ""
+        self._last_metadata_sent: float = 0.0
 
     @property
     def running(self) -> bool:
@@ -80,6 +82,9 @@ class AirPlayProtocol(ABC):
         # to ignore it the first time
         # https://github.com/music-assistant/support/issues/3330
         self.mass.call_later(2, self.send_cli_command(f"VOLUME={self.player.volume_level}"))
+        # we also need to send the metadata after connection, because some players (e.g. Sonos)
+        # simply won't start playback until they receive the metadata ?!
+        self.mass.call_later(2, self.player._on_player_media_updated)
 
     async def stop(self, force: bool = False) -> None:
         """
@@ -121,8 +126,20 @@ class AirPlayProtocol(ABC):
             title = metadata.title or ""
             artist = metadata.artist or ""
             album = metadata.album or ""
+
+            metadata_checksum = f"{title}|{artist}|{album}|{duration}|{metadata.image_url}"
+            if (
+                metadata_checksum == self._metadata_checksum
+                and time.time() - self._last_metadata_sent <= 2
+            ):
+                # metadata has not changed since last time, skip sending to CLI
+                return
+            self._metadata_checksum = metadata_checksum
+            self._last_metadata_sent = time.time()
+
             cmd = f"TITLE={title}\nARTIST={artist}\nALBUM={album}\n"
             cmd += f"DURATION={duration}\nPROGRESS=0\nACTION=SENDMETA\n"
+
             await self.send_cli_command(cmd)
             # get image
             if metadata.image_url: