From: Marcel van der Veldt Date: Sun, 22 Feb 2026 17:17:29 +0000 (+0100) Subject: Fix some edge cases with AirPlay DACP commands X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=2f9d136339b74103769d9f7bbc02d429b88133ac;p=music-assistant-server.git Fix some edge cases with AirPlay DACP commands --- diff --git a/music_assistant/providers/airplay/provider.py b/music_assistant/providers/airplay/provider.py index bccdae22..372087bc 100644 --- a/music_assistant/providers/airplay/provider.py +++ b/music_assistant/providers/airplay/provider.py @@ -280,11 +280,22 @@ class AirPlayProvider(PlayerProvider): await self.mass.player_queues.set_shuffle( active_queue.queue_id, not active_queue.shuffle_enabled ) - elif path in ("/ctrl-int/1/pause", "/ctrl-int/1/discrete-pause"): - # sometimes this request is sent by a device as confirmation of a play command - # we ignore this if the player is already playing - if player.playback_state == PlaybackState.PLAYING: + elif path == "/ctrl-int/1/pause": + if player.state.playback_state == PlaybackState.PLAYING: self.mass.create_task(self.mass.players.cmd_pause(player_id)) + elif path == "/ctrl-int/1/discrete-pause": + # Some devices send discrete-pause right before device-prevent-playback=1 + # when switching to another source. We debounce the pause to avoid + # unnecessary pause commands that would interfere with source switching + # so we only process the pause command if we don't receive a + # prevent-playback=1 within a short time window. + if player.state.playback_state == PlaybackState.PLAYING: + self.mass.call_later( + 1.0, + self.mass.players.cmd_pause, + player_id, + task_id=f"debounced_pause_{player_id}", + ) elif "dmcp.device-volume=" in path and not ignore_volume_report: # This is a bit annoying as this can be either the device confirming a new volume # we've sent or the device requesting a new volume itself. @@ -299,13 +310,21 @@ class AirPlayProvider(PlayerProvider): player.update_volume_from_device(volume) elif "device-prevent-playback=1" in path: # device switched to another source (or is powered off) + # Cancel any pending debounced pause since prevent-playback takes precedence + self.mass.cancel_timer(f"debounced_pause_{player_id}") # Ignore during stream transition (stale message from old CLI process) if player._transitioning: self.logger.debug("Ignoring prevent-playback during stream transition") elif stream := player.stream: stream.prevent_playback = True if stream.session: - self.mass.create_task(stream.session.remove_client(player)) + # send power off which will take care of stopping the stream + # and removing this player from any sync groups, etc. + self.logger.debug( + "received prevent-playback from %s, powering off player", + player.name, + ) + self.mass.create_task(self.mass.players.cmd_power(player_id, False)) elif "device-prevent-playback=0" in path: # device reports that its ready for playback again if stream := player.stream: