From ac7ac6bf4862b4f92f3d53a05d0cdb1866432330 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Mon, 23 Feb 2026 00:10:43 +0100 Subject: [PATCH] Auto translate commands directed at protocol player id to visible parent --- .../controllers/players/controller.py | 7 +++++-- .../controllers/players/helpers.py | 19 ++++++++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/music_assistant/controllers/players/controller.py b/music_assistant/controllers/players/controller.py index 0192bdae..9b8162fe 100644 --- a/music_assistant/controllers/players/controller.py +++ b/music_assistant/controllers/players/controller.py @@ -486,6 +486,7 @@ class PlayerController(ProtocolLinkingMixin, CoreController): await self._handle_cmd_resume(player_id, source, media) @api_command("players/cmd/seek") + @handle_player_command async def cmd_seek(self, player_id: str, position: int) -> None: """Handle SEEK command for given player. @@ -517,6 +518,7 @@ class PlayerController(ProtocolLinkingMixin, CoreController): await player.seek(position) @api_command("players/cmd/next") + @handle_player_command async def cmd_next_track(self, player_id: str) -> None: """Handle NEXT TRACK command for given player.""" player = self._get_player_with_redirect(player_id) @@ -545,6 +547,7 @@ class PlayerController(ProtocolLinkingMixin, CoreController): raise UnsupportedFeaturedException(msg) @api_command("players/cmd/previous") + @handle_player_command async def cmd_previous_track(self, player_id: str) -> None: """Handle PREVIOUS TRACK command for given player.""" player = self._get_player_with_redirect(player_id) @@ -2203,7 +2206,7 @@ class PlayerController(ProtocolLinkingMixin, CoreController): # Confirmed external source takeover self.logger.info( - "External source '%s' took over on %s while grouped via protocol %s - " + "External source '%s' took over on %s while playing via protocol %s - " "clearing active output protocol and ungrouping", new_source, player.display_name, @@ -2581,7 +2584,7 @@ class PlayerController(ProtocolLinkingMixin, CoreController): return # nothing to do # ungroup player at power off - player_was_synced = player.state.synced_to is not None + player_was_synced = bool(player.state.synced_to or player.group_members) if player.type == PlayerType.PLAYER and not powered: # ungroup player if it is synced (or is a sync leader itself) # NOTE: ungroup will be ignored if the player is not grouped or synced diff --git a/music_assistant/controllers/players/helpers.py b/music_assistant/controllers/players/helpers.py index 0fa3c209..beb9183d 100644 --- a/music_assistant/controllers/players/helpers.py +++ b/music_assistant/controllers/players/helpers.py @@ -82,6 +82,23 @@ def handle_player_command[PlayerControllerT: "PlayerController", **P, R]( ) return + # this should not happen, but in case a player_id of a protocol player is used, + # auto-resolve it to the parent player + if player.protocol_parent_id and ( + protocol_parent := self._players.get(player.protocol_parent_id) + ): + player = protocol_parent + if "player_id" in kwargs: + kwargs["player_id"] = protocol_parent.player_id + else: + args = (protocol_parent.player_id, *args[1:]) # type: ignore[assignment] + self.logger.info( + "Auto-resolved protocol player %s to linked parent %s for command %s", + player_id, + protocol_parent.player_id, + fn.__name__, + ) + current_user = get_current_user() if ( current_user @@ -101,7 +118,7 @@ def handle_player_command[PlayerControllerT: "PlayerController", **P, R]( ) async def execute() -> None: - async with self._player_throttlers[player_id]: + async with self._player_throttlers[player.player_id]: try: await fn(self, *args, **kwargs) except Exception as err: -- 2.34.1