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)

This commit is contained in:
James Mackay committed 2026-10-08 10:46:32 +01:00
1 parent bc457baae7
commit f58be9ed05
2 files changed
+64

No files matched your search

+37
View File
@@ -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 the ADB connection lives inside the media player entity, and fallback app state
arrives in Phase 3. 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 ## Releases
- **Date-based versioning**: `vYYYY.MM.DD` (e.g. `v2026.10.08`). - **Date-based versioning**: `vYYYY.MM.DD` (e.g. `v2026.10.08`).
+27
View File
@@ -394,3 +394,30 @@ media_player:
9. Migration note: retire mediasession2mqtt sensors + universal-player YAML once 9. Migration note: retire mediasession2mqtt sensors + universal-player YAML once
Phase 3 lands (documented in README). Phase 3 lands (documented in README).
10. Netflix stale-session fixture pins "inactive no-state sessions ignored". 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.