From f58be9ed0535b9c560d98b499fef3b7e5baa5c75 Mon Sep 17 00:00:00 2001 From: James Mackay Date: Thu, 8 Oct 2026 10:46:32 +0100 Subject: [PATCH] docs: state ladder + fallback semantics in README; mediasession2mqtt retirement + universal-player glue appendix in RESEARCH_SHIELD; TiviMate as the documented no-session case (P3-3d) --- README.md | 37 +++++++++++++++++++++++++++++++++++++ docs/RESEARCH_SHIELD.md | 27 +++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/README.md b/README.md index 4271cfb..f9e5812 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,43 @@ Changes inherited from upstream still apply in 0.1.x code: polling is timer-base the ADB connection lives inside the media player entity, and fallback app state arrives in Phase 3. +## State ladder and fallback semantics (Phase 3) + +The media player state is decided top-down; each tier serves only when the +ones above it are silent. Both transports live in the entry's +`ShieldConnectionManager` (`connection.py`) — ADB is the state source, the +remote protocol is the fast push/command path: + +1. **Plug off** — the power switch says `off` → `off`. +2. **Protocol power push** (`is_on`) — `False` → `off`; `None` falls through. +3. **ADB wake** (exact `mWakefulness` parse) — asleep → `idle` (standby). +4. **Intent** — a just-sent command's target state, self-healing against + reality within 30 s (retry goes over the transport that served it). +5. **Jellyfin** — `playing` / `paused` while a Jellyfin session is active. +6. **ADB media session** — `playing` / `paused` for apps that publish a + session (foreground app preferred). +7. **Foreground fallback** — an app is open (`current_app` protocol push, + then the ADB `mResumedActivity` parse) but publishes no media session → + `on`. Its identity comes from the TTL-capped last-known-state cache, the + last channel entered, or the zero-cost "audio playback" hint + (`media recently active`) — never resurrecting `playing` past the 60 s + state TTL. +8. **On** — powered, awake, nothing more specific. + +TiviMate (`ar.tvplayer.tv`) is the documented no-session case: it publishes +no MediaSession on Shield Android 11 (RESEARCH_SHIELD R2), so tiers 6–7 plus +the audio hint are what keep it visible. + +### Migration from mediasession2mqtt + universal-player glue + +The `mediasession2mqtt` bridge sensors and the universal media player YAML +templates the fork replaces are retired by the ladder above: the media player +entity already carries the DAG attributes (`media_dag_running_apps` with +foreground/last-known enrichment, `media_dag_now_playing` with its source +tag) that media-zone orchestration consumed from those sensors. See the +appendix in [`docs/RESEARCH_SHIELD.md`](docs/RESEARCH_SHIELD.md) for the +retirement mapping and the universal-player glue it replaces. + ## Releases - **Date-based versioning**: `vYYYY.MM.DD` (e.g. `v2026.10.08`). diff --git a/docs/RESEARCH_SHIELD.md b/docs/RESEARCH_SHIELD.md index 2cf2850..a9ba903 100644 --- a/docs/RESEARCH_SHIELD.md +++ b/docs/RESEARCH_SHIELD.md @@ -394,3 +394,30 @@ media_player: 9. Migration note: retire mediasession2mqtt sensors + universal-player YAML once Phase 3 lands (documented in README). 10. Netflix stale-session fixture pins "inactive no-state sessions ignored". + +--- + +## Appendix — mediasession2mqtt retirement + universal-player glue (Phase 3) + +The fork retires the surrounding glue once the state ladder (README, Phase 3) +covers what the external pieces provided: + +| External piece | What it provided | Now native (file) | +|---|---|---| +| `mediasession2mqtt` bridge sensors | per-app now-playing pushed over MQTT from the Shield's notification listener | ADB media-session polling + the foreground tier (`connection.py`, `media_player.py`); the `media_dag_now_playing` attribute carries `source: jellyfin \| media_session \| app` | +| Universal media player `media_dag_running_apps` template | app switcher list with playback states | `running_apps_from_tasks(..., last_known=...)` (`ha_adb_common/running_apps.py`) — enriched with `foreground`/`state`/`last_known_state`/`last_known_title` | +| Universal player "currently in app X" template | fallback state for no-session apps | the foreground ladder tier (`media_player.py` state property, tier 3.5) | +| External androidtv_remote entity for commands | remote keys | the protocol-backed `remote` entity (Phase 2) — the external entity remains only as the transitional last command tier, removed in Phase 4 | +| Last-channel / "recently played" sensors | what was on when the session vanished | the TTL-capped last-known-state cache + `_last_channel` + the audio-playback hint (`connection.py`) | + +The migration path: point the automations/scripts at the integration's DAG +attributes (`media_dag_*`) instead of the MQTT sensors, then remove +`mediasession2mqtt` and the universal-player config entries. No YAML glue is +needed for the MVP feature set; the appendix exists so retirement can be +staged per household. + +**TiviMate (`ar.tvplayer.tv`)** is the documented no-session case (R2): it +publishes no MediaSession on Shield Android 11. Its visibility comes from the +foreground tier (tier 3.5) with the audio-playback hint and the +last-known-state cache — never from a session, never resurrected past the +state TTL.