From 359b96ef9c61be329adde5df4f5f23dc80a3402a Mon Sep 17 00:00:00 2001 From: James Mackay Date: Thu, 8 Oct 2026 07:39:53 +0100 Subject: [PATCH] fix: retry playback intents over the command path that originally served them (ADB broadcasts annotate the intent, self-heal retry goes back over ADB) --- .../nvidia_shield/ha_adb_common/intent.py | 11 +++++++++- .../nvidia_shield/media_player.py | 22 ++++++++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/custom_components/nvidia_shield/ha_adb_common/intent.py b/custom_components/nvidia_shield/ha_adb_common/intent.py index 2b324db..92d3836 100644 --- a/custom_components/nvidia_shield/ha_adb_common/intent.py +++ b/custom_components/nvidia_shield/ha_adb_common/intent.py @@ -13,10 +13,15 @@ class PlaybackIntent: Attributes: target_state: The MediaPlayerState we expect after the command. - command: The service/command name (for retries and logging). + command: The command/service name (for retries and logging). created_at: Monotonic timestamp when the intent was created. prior_state: The source state before the intent (for detecting external overrides). retry_count: Number of retries attempted so far. + via_adb: True when the command was successfully served by the ADB + keyevent path — the retry must then go over ADB too, not back to + the remote that did not serve it. + adb_command: The ADB keycode command name matching the intent + (play/pause/stop/next/previous); set together with via_adb. """ target_state: str # MediaPlayerState value (e.g. "playing", "paused") @@ -24,6 +29,8 @@ class PlaybackIntent: created_at: float prior_state: str | None = None retry_count: int = 0 + via_adb: bool = False + adb_command: str | None = None @classmethod def create(cls, target_state: str, command: str, prior_state: str | None = None) -> "PlaybackIntent": @@ -45,4 +52,6 @@ class PlaybackIntent: "command": self.command, "retries": self.retry_count, "age_s": self.age_seconds, + "via_adb": self.via_adb, + "adb_command": self.adb_command, } diff --git a/custom_components/nvidia_shield/media_player.py b/custom_components/nvidia_shield/media_player.py index c3a3b34..dd68dac 100644 --- a/custom_components/nvidia_shield/media_player.py +++ b/custom_components/nvidia_shield/media_player.py @@ -243,9 +243,20 @@ class NvidiaShieldMediaPlayer(MediaPlayerEntity): "Intent %s not confirmed (JF=%s, want=%s), retrying (attempt %d)", intent.command, jf_state, intent.target_state, intent.retry_count, ) - self.hass.async_create_task( - self._cmd(intent.command) - ) + if ( + intent.via_adb + and intent.adb_command + and self._adb_poller is not None + and self._adb_poller.connected + ): + # The command was originally served by ADB — retry over ADB. + self.hass.async_create_task( + self._adb_poller.send_media_command(intent.adb_command) + ) + else: + self.hass.async_create_task( + self._cmd(intent.command) + ) self._intent_timer_unsub = async_call_later( self.hass, INTENT_TIMEOUT, self._on_intent_timeout ) @@ -707,6 +718,11 @@ class NvidiaShieldMediaPlayer(MediaPlayerEntity): if self._adb_poller and self._adb_poller.connected and not jf_active: success = await self._adb_poller.send_media_command(adb_command) if success: + # The ADB path served this command — the self-heal retry must + # also go over ADB, not back to the remote. + if self._intent is not None: + self._intent.via_adb = True + self._intent.adb_command = adb_command return # Fall back to Android TV Remote