Files
ha-nvidia-shield/docs/RESEARCH_IDEAS.md
T

25 KiB
Raw Blame History

Research ideas for ha-nvidia-shield (features, patterns, TiviMate)

Status: COMPLETE. Companion docs: MVP_PLAN.md, RESEARCH_SHIELD.md. Method: primary sources first — library/integration source code (androidtvremote2, adb_shell, HA core), HA core GitHub issues/PRs, home-assistant.io integration docs, home-assistant.io action docs, and community threads. Citations are inline markdown links. Note: HA's Action classes (MediaPlayerEntityFeature.*) are what we set in code; HA's Actions (media_player.play_media etc.) are what users call.


1. What users actually request (from HA core issues, community threads, library issues)

# Request Source Take for our MVP
U1 Absolute volume set on Android TV players — media_player.volume_set repeatedly requested; HA core androidtv_remote closed it not_planned core#176505 High demand, but the protocol v2 proto has no usable volume-set primitive shipped (it reports volume; see C-VOL). Our Phase 2 now ships `VOLUME_STEP
U2 Launching any installed app (not just deep-linkable ones) and retrieving the installed-app list androidtvremote2#17 The official NVIDIA Shield app does retrieve the app list over another service; androidtvremote2 accepted market://launch?id=<pkg> as the app-launch workaround (now built into send_launch_app_command, see C-LAUNCH). App-list retrieval is not implementable through this client's protocol surface — a data-driven, options-defined app map is the right MVP substitute (already in plan)
U3 Text/IME entry (typing into search boxes); behavior depends on the app and on whether the on-screen keyboard is open (one-at-a-time chars get replaced in Spotify; whole-string send works) androidtvremote2#32 Library supports send_text but behavior is app-dependent. Keep the IME option default off (as planned) and document the qualifier rather than trying to be clever
U4 Voice search / voice commands androidtvremote2#28 — closed; the library now ships start_voice()/VoiceStream, gated by enable_voice and device feature support Implemented in the lib (PCM 16-bit mono 8 kHz streamed as RemoteVoiceBegin/Payload/End). Post-MVP only; note exists so Phase 4 does not re-research
U5 Deep-link launching of apps into specific sections (community guide table with ~60 known app URLs, plus how to discover one via adb shell cmd package dump PACKAGE and manifest string resolvers) deep-link guide Directly validates Phase-4 deep links; send_launch_app_command accepts either a deep link or a bare package id (see C-LAUNCH). The guide's discovery recipe (cmd package dump <pkg>, grep <data, resolve @string/deeplink_schema) is worth copying into our docs/contributor workflow
U6 BrowseMedia containing installed apps / activity list so users can launch apps from the media panel instead of scripts core#117126 (androidtv_remote: "Implement Android TV Remote browse media with apps and activity list") Validates Phase-4 BrowseMedia app directory; can be built from our own running_apps data or an options-defined list (no protocol app-list needed)
U7 Channel-by-number play_media + channel up/down keys on streaming players (Roku, Bravia) Roku docs, Bravia docs play_media(media_content_type: channel) is the canonical way every TV integration does tuning (Roku, Bravia); our §7 digit path matches. Bonus Roku/Bravia idea: expose channel_up/channel_down as remote commands (protocol KEYCODE_CHANNEL_UP=166 / KEYCODE_CHANNEL_DOWN=167 — in the proto, no new work)
U8 TiviMate: start it and switch to a specific channel from an HA automation community thread (2025, active) Exactly our §7 play_media(channel) requirement. The working recipe there: remote.turn_on with activity: ar.tvplayer.tv → delay ~7 s (app cold-start) → media_player.play_media with media_content_type: channel / media_content_id: "1" — note the wait; our play_media should not promise cold-start-to-channel in one shot (we don't own app launch speed). Follow-up request in the thread shows demand for tuning VOD/series pages too (unresolved anywhere)
U9 Shield app-launcher cards for wall-panel remotes (Firemote adds TiviMate/SmartTube shortcuts); Harmony-style activity switching HA-Firemote#48, Shield launcher blueprint QoL signal for Phase-4 apps editor + source_list; also shows app-name→icon mapping is what users iterate on (supports data-driven app map + icon support)
U10 Firemote / universal-remote-card users must hand-define every app shortcut because the Shield integration exposes no app directory universal-remote-card discussion Independent confirmation of U6/U9 demand; TiviMate + SmartTube shortcut request

Also verified from an independent direction: MediaSession2MQTT#4 confirms TiviMate "doesn't capture anything" for notification-based playback detection on the Shield — consistent with our R2 no-media-session finding, and a reminder that projects tried and failed to get sessions out of TiviMate (see §5).

2. androidtvremote2 capabilities we are not using (verified from library source)

Source of truth read: androidtv_remote.py, remotemessage.proto, TvKeys.txt.

Capability API MVP relevance
Key codes + length variants send_key_command(key, direction) — SHORT/START_LONG/END_LONG; accepts int or "KEYCODE_..."/"POWER" strings; any code from the 300-strong proto enum Remote entity port (Phase 2) matches core's SHORT/START_LONG/END_LONG design
Text / IME send_text(text); also send_key_command("text:<message>") routes into send_text Phase-2 IME option (default off). Known quirk: append-vs-replace inconsistency (#32) — document, don't ship cleverness
App launch send_launch_app_command(app_link_or_app_id) — auto-prefixes market://launch?id= when the value has no URL scheme (C-LAUNCH) play_media(url)/play_media(app) (Phase 4): bare package names work with zero extra code; deep links like plex:// pass through unchanged
Push callbacks add_is_on_updated_callback, add_current_app_updated_callback, add_volume_info_updated_callback, add_is_available_updated_callback Exactly the manager-listener pattern Phase 1/2 needs; current_app push is the Phase-3 primary foreground signal
Volume info (read-only) volume_info → (level, max, muted) pushed on change Real mute state + numeric level readout in Phase 2 — but no volume_set; see C-VOL
Reconnect + reauth distinction keep_reconnecting(invalid_auth_callback=<fn>) with internal exponential backoff 0.1 s → 30 s cap; raises InvalidAuth when certs are rejected (device hard reset) Feeds the transport design; one owner per transport — don't build a competing watchdog for the protocol socket; InvalidAuth → reauth flow, not retry storm
Device info device_info (model, vendor, package_name, app_version) Free diagnostics data for Phase 4 (and a nice setup sanity check)
App-list push Not implemented and not in the protocol this client speaks (#17). Verified against the full proto: the only app-ish messages are RemoteAppInfo (IME context) and RemoteAppLinkLaunchRequest — no app-list leafs Confirms: options-defined app name/icon map is the honest substitute; do not promise app-list discovery
IME sensitivity Constructor enable_ime=True — "Needed for getting current_app; disable for devices that show 'Use keyboard on mobile device screen'" If we make IME configurable, document that disabling it can break the current_app push (which Phase 3 depends on)
Voice start_voice() → VoiceStream (PCM 16-bit mono 8 kHz, 20 KB chunks), enable_voice=True Post-MVP
RemoteSetVolumeLevel exists in the proto but is sent by noone (see C-VOL) — Do not plan on it for VOLUME_SET

3. Patterns worth borrowing from other HA integrations (docs verified)

All four integration docs read in full (Roku, Apple TV, Bravia, androidtv).

Integration Mechanism What to borrow Effort for us
Roku play_media(media_content_type: channel) → tune by number (e.g. 5.1); select_source accepts app name or app id; BrowseMedia for apps; channel tuning for Roku TV devices; media_content_type: app with extra.content_id/extra.media_type for content deeplinks; remote commands incl. channel_up/channel_down; active app + active app id sensors; media_content_type: url + extra{format,name,thumbnail,artist_name} for PlayOnRoku direct playback; obtains app list via a published XML endpoint (/query/apps) Channel tuning semantic — exactly our channel port (validated). App deep link envelope shape: media_content_type: app with extra.content_id/extra.media_type is a richer pattern than raw URLs if we later want per-app content launches — record it but don't build it for MVP. Source list built from apps matches our select_source = launch app. Channel up/down as remote commands — bolt channel_up/channel_down onto Phase-2 remote command set (keycodes 166/167, free) Low–medium
Apple TV play_media(media_content_type: url) with youtube://..., https://www.netflix.com/title/... "Deep Links" (canonical how-to + link-discovery tips via iOS share sheets); select_source + BrowseMedia "Apps" folder for launching apps; keyboard entity (binary_sensor "keyboard focused") + three text actions (set/append/clear_keyboard_text) driven by keyboard-focus push; remote entity with num_repeats/delay_secs/hold_secs play_media(url) deep-link semantics for Phase 4, direct port (mediatype url → send_launch_app_command). "Apps folder" validation for BrowseMedia. Keyboard-focus push → typed-text entry is Apple TV's answer to #32's append/replace ambiguity — a keyboard entity would need an IME-push equivalent the Shield protocol does not give us cheaply, so for MVP ship send_text only when IME is on (nothing fancier). Remote entity parameters (num_repeats/delay_secs/hold_secs) are the shape we already planned Medium (only if we chase keyboard); Low for the rest
Sony Bravia play_media matches channel number, exact or partial app/channel name, or URI string; BrowseMedia lists installed apps + TV channels; remote commands incl. Num0..Num9 (digit-typing built into the remote platform); documented real-world limitation that TVs running Android/Google TV lose in-app playback info unless you pair with Google Cast (they recommend Universal Media Player with two children — exactly the problem our all-in-one design solves natively) Fuzzy matching on media_content_id (number OR exact/partial name) is a small and very forgiving UX win for channel — accept a numeric string strictly, but also accept exact channel/app names from an options-defined map if cheap. The Bravia doc explicitly documents the Cast+Universal-Media-Player workaround — citable pattern that our native ladder replaces for Shield users Low (for the simple variant)
androidtv (core, ADB/Fire TV) select_source = launch app; ! prefix closes app (!com.netflix.ninja); options editors for apps list, exclude unknown, arbitrary ADB turn_on/turn_off commands, per-app state_detection_rules; screen-capture album art option; adb_command/upload/download actions; learn_sendevent helper ! close-app prefix is a tiny, delightful, zero-new-dep feature — implement for free alongside our Phase-4 launch path; document it as an androidtv compatibility nicety. Arbitrary turn_on/turn_off commands as options is worth matching (we already have shell; gives users a knob without code). state_detection_rules (per-app playing/paused conditions over media_session_state, audio_state, wake_lock_size) is the blueprint for our last-known cache tier to become configurable if users need app-specific truth — out of ADB/MVP scope but record it. Screen-capture album art is the precedent for the screencap idea in §4 (Roku/Bravia do not need it; we do) Low

Canonical patterns (all four): select_source = launch app with source_list of apps; BrowseMedia with a flat "Apps" directory; play_media channel digits or named matches; per-app icon/name map; documented media_content_type vocabulary (channel, app, url, music, video) — we should mirror this vocabulary exactly in Phase 2/4.

4. adb_shell capabilities we are not using (verified from adb_shell source)

Source of truth read: adb_shell/adb_device.py (the full file) and README.rst.

Capability API MVP relevance
Streaming shell AdbDevice.streaming_shell(command, decode=True) — a generator that yields lines as they arrive, until the connection is closed ⚠️ Plan correction. MVP_PLAN.md §2 currently says "adb_shell's shell() is request→full-response; there is no incremental/streaming read API". That is no longer accurate on master — streaming_shell exists as a documented generator on AdbDevice. Two caveats before acting: (a) verify it is present in the resolved version we pin (repo last pushed 2024‑10; check release ≥ ours covers it — the version is old enough that the feature set may differ slightly); (b) verify the async device class exposing it (the source read was the sync AdbDevice; the async subclass typically mirrors). If both check out, MVP_PLAN's "Phase-4 optional: logcat streaming behind an option" becomes much cheaper than hacking adb_shell internals — and the deferred logcat snapshot approach gains a real path. Rank as the single highest-value adb_shell finding
FileSync push(), pull(device_path, local_path, ...), list(device_path) — full FileSync protocol Enables clean pull-based screencap -p /local && pull work flows (crisper than shell screencap -p stdout parsing) for album art, debugging, or future thumbnails; zero new deps
Lazy shell decode decode=False on shell Micro-win for binary/inspection commands
USB AdbDeviceUsb (experimental, [usb] extra) Not for MVP (cradle-cable setup); note for future reference only
Long-lived TCP connect AdbDeviceTcp(host, port, default_transport_timeout_s=...) + connect(rsa_keys=[signer], auth_timeout_s=...) with per-call timeouts What Phase 1 already relies on; the per-transaction timeout fields (transport_timeout_s/read_timeout_s/timeout_s) are worth being explicit about in the manager's health-check design rather than default-None everywhere

5. TiviMate specifics

What TiviMate gives us: essentially nothing at the device level.

  • Package ar.tvplayer.tv; no media session (R2, verified twice — before/after a zap produced byte-identical output) and appears only in the "Audio playback" section of dumpsys media_session.
  • MediaSession2MQTT#4: independent confirmation that TiviMate produces nothing captureable by notification/session bridges on the Shield.
  • Channel-switch automation thread: the community's only working channel-select recipe is the digit-keyevent path we already planned (plus a ~7 s delay for app cold start).
  • No deep link published by users in the deep-linking guide or elsewhere for ar.tvplayer.tv — treat as "no shortcut"; if ever one surfaces, am start with extras via ADB is the Phase-4 "apps the protocol can't launch" seam.
  • Channel list export: TiviMate does not expose a device-side API for the channel list the community can use — the ecosystem gets channels from the upstream M3U/XMLTV sources that TiviMate loads, not from the app. Community projects around TiviMate (playlists, EPG (EPGTalk), playlist auto-updaters) take their data from those links. MVP implication: if we ever want "channel N = " metadata, the data side belongs to the user's playlist/EPG pipeline (e.g. options field to store a channel list file), not to the integration scraping the TV.
  • Channel/zap-change detection while watching: no project has reliable on-device telemetry (the R2 capture shows the dump is zap-invariant). Realistic best signals remain the ones we have chosen: A "Audio playback" presence (identity) and last_channel cache (what we last asked for). The Phase-1 optional logcat-on-a-device process channel may eventually reveal app-internal logs, but no public, stable, IPTV-app-level pattern exists to borrow.

Conclusion: our current design (digits via keys, foreground fallback + audio-playback hint + last-known cache) is at parity with what the community does; the only additions worth recording are the ~7 s app-cold-start note in automation docs and the "channel list data belongs outside the integration" decision.

6. Ranked ideas

Ranking by: (a) fits locked MVP decisions, (b) cost, (c) user-verifiable payoff. "Phase" is where it should land in the current plan.

Rank Idea MVP fit Effort Phase
1 channel_up / channel_down on the remote+media_player command sets — free with the protocol's KEYCODE_CHANNEL_UP(166)/KEYCODE_CHANNEL_DOWN(167); Roku/Bravia precedent for making tuning feel native; zero new logic since we already send keycodes for digits High Trivial (hours) 2 (fall-out)
2 ! app-close prefix on select_source/play_media(app) — androidtv precedent (!com.netflix.ninja); one-stop fix for "app won't die" automation scripts; zero new deps or surfaces High Small (half day incl. tests) 4
3 play_media(media_content_type: url) deep links via send_launch_app_command with a cloud-less data-driven deeplink map option (validated against the community-wide guide; the library accepts either raw deep link or bare package id) — plus the guide's adb shell cmd package dump trick documented for contributors to discover links per app High Small–medium (~1 day incl. options editor) 4
4 BrowseMedia "Apps" directory from running_apps + options-defined app map (androidtv_remote/Apple TV/Bravia precedent) — the single most-requested QoL pattern in this space and the reason users hit external cards (#117126, HA-Firemote#48) High Medium (~1–2 days incl. icons + tests) 4
5 Volume readout hardening in Phase 2 — volume_info push sourced real volume_level and mute, with media_player.volume_set deliberately unsupported (C-VOL correction, already adopted); if we hear repeated user demand later, offer an ADB-step-loop helper service documented as such (not fake features) Medium (already handled) done (plan note only) 2
6 adb_shell streaming_shell probe — spike: confirm streaming_shell exists at our pinned adb-shell version and on the async class; if yes, MVP_PLAN's deferred logcat-on-ADB idea is feasible via a documented generator rather than internal hacks; if no, keeps the snapshot-only plan honest. Cheap spike, high leverage on a currently-"impossible" feature Medium Small (half-day spike) 4 (optional path)
7 Bravia-style tolerant media_content_id matching for channel (accept 5.1-style floats and exact-name matches from the app/channel map) — small UX win, safe with a strict numeric path retained Medium Small 3/4
8 Screen-capture album art (adb_shell screencap/pull) — androidtv precedent supplies the pattern; useful only for our PlayOnRoku-missing, Jellyfin-cached apps; keep it as a documented option like androidtv does, since it costs extra ADB shell round trips when playing and is privacy-scope-sensitive Medium Medium (~1 day + options slosh) Post-MVP
9 IME text entry surfaced as a remote/service integration — possible now that the library has send_text/text: syntax, but per #32 behavior is inconsistent; Apple TV solves this with a keyboard-focus push that the Shield protocol doesn't give us cheaply; keep IME an options toggle, not an entity Low for MVP Medium 4 (option only)
10 state_detection_rules-style per-app configurable state ladder (androidtv pattern) — only if/when users ask for app-specific truth beyond ADB sessions (e.g. IPTV VOD pages); nice future seam; don't build pre-emptively Low for MVP Medium–large Post-MVP
11 Voice (start_voice) — available in the library, enable_voice=True and device-feature-gated; not in our MVP scope, recorded so it's not re-researched Low Medium Post-MVP

Key references for the rankings (primary, not forum threads only): core#176505 · #117126 · #17 · #32 · #28 · androidtvremote2 source · proto · adb_shell device · Roku · Apple TV · Bravia · androidtv · deep linking guide · community TiviMate thread · Firemote#48 · MediaSession2MQTT#4


7. Explicit MVP-plan corrections proposed (for Phase-3/4 lands)

  1. C-VOL (already adopted by captain). volume_set is not protocol-native — the supported features in Phase 2 stay VOLUME_STEP|MUTE, with real numeric volume_level and is_volume_muted readouts from the protocol's volume_info push. Absolute-volume demand, if it ever recurs, is addressed honestly via an ADB input keyevent KEYCODE_VOLUME_* helper script, documented as such, not as a fake supported feature.
  2. C-STREAM (new). MVP_PLAN §2's statement that adb_shell offers no incremental/streaming read should be softened: streaming_shell exists upstream (at least on master) and might make the deferred Phase-4 logcat idea a one-day spike instead of "needs new dependency". Verify our pinned version carries it and whether the async device class exposes it, then either upgrade to a "cheap Phase-4 item" or keep the deferred note with accurate wording.
  3. C-LAUNCH (new, informational). send_launch_app_command already accepts a bare package id by auto-prefixing market://launch?id=; Phase 4 needs no extra branch for "app" launches, only for deep links (where the value already has a scheme) and !-close via ADB (only when the protocol path is not enough).
  4. C-TIVI (new). TiviMate has no usable on-device channel metadata or export surface; the honest MVP display is "on, in TiviMate" + last_channel cache from our own play_media calls. Channel-name metadata is a user-supplied playlist/EPG concern, out of scope (and out of ADB's reach).