feat(media-player): a switch Receiver is ensured on, and apps leave the browser (t61)
Two halves of the same live-run report.
RECEIVER AS A SWITCH. The user: "it would be nice if a receiver could just be a
switch, I don't need my volume scripts then because the receiver portion could
just make sure the receiver is on if it's a switch". `_ensure_receiver_on()`
runs at the top of the three playback entry points — turn_on, play_media,
select_source — and calls switch.turn_on only when the configured switch
receiver reports `off`: an `on` switch gets no call (idempotent), an
unavailable/unknown/missing state is left alone with a debug line rather than
guessed at, and a failing service call is logged, never raised — a receiver that
cannot be switched on must not stop the Shield from playing. Nothing else about
the switch is touched (no volume, no toggle), and nothing switches it from a
state read, a poll or a listener, so an off receiver stays off while the Shield
is idle. A media_player receiver keeps its behaviour exactly. select_source now
dispatches through the private `_dispatch_play_media`, so one user action looks
at the same switch once; `play_media` keeps its public signature and behaviour.
The Receiver field already accepted switch.* at the validation/strings layer
(t56, pinned by the real-HA tests); the strings copy now says what playback does
with it.
MEDIA BROWSER. The user: "Media Browser shows Apps. To me it should probably be
integrated with the home assistant media display, keep select sources for apps".
`async_browse_media` no longer publishes the app directory: it returns a valid,
standard BrowseMedia root (device title, DIRECTORY class, no content id, no
children) instead of a second, inconsistent copy of the list. Apps stay reachable
through source_list/source/select_source and play_media("app", <package>), which
is unchanged and complete; nothing else on this entity is browsable, so there is
no dead entry to select, and the drift gate's t37 D1 browse assertions still hold.
Tests: tests/test_receiver_switch.py pins all four promised states — off →
exactly one switch.turn_on; on → none; unavailable/unknown/missing/failing →
playback continues and nothing raises; media_player receiver → never switched —
plus the idle-Shield rule. The browse pins move from the removed tree to the
surviving surfaces (source_list, _source_package, _app_directory), and the harness
gains the real hass.states shape (FakeStates) and the real switch state constants.
README documents both changes; both strings files stay byte-identical.
This commit is contained in:
1 parent
c884c7e1ab
commit
431b7cd8f4
8 files changed
+448
-133
No files matched your search
@@ -352,8 +352,15 @@ list). A malformed row — no `=`, or an empty name — is rejected with an erro
|
||||
page rather than being half-saved.
|
||||
|
||||
A receiver may be a **`switch.*`** as well as a `media_player.*`: a soundbar or
|
||||
speaker that only knows on and off is a valid receiver. (Driving that switch during
|
||||
playback is a separate, later change.)
|
||||
speaker that only knows on and off is a valid receiver. Playback then **ensures
|
||||
that switch is on**: `turn_on`, `play_media` and `select_source` call
|
||||
`switch.turn_on` when — and only when — it reports `off`, and touch nothing else
|
||||
about it (never volume, never a toggle). An already-on switch gets no call at all,
|
||||
a switch whose state cannot be read (`unavailable`/`unknown`) is left alone rather
|
||||
than guessed at, and a receiver that is off while the Shield is idle stays off.
|
||||
That is what makes receiver **volume scripts unnecessary** for this shape: there is
|
||||
no volume to script, only an on/off state to ensure. A `media_player.*` receiver
|
||||
keeps its own behaviour — input selection and attribute enrichment.
|
||||
|
||||
- **Jellyfin Media Player** — auto-detected or manually selected
|
||||
- **Receiver + Input** — for media-zone coordination; `media_player` or `switch`
|
||||
@@ -497,13 +504,22 @@ sweep could not run), while the Configure row forces one.
|
||||
|
||||
### Sources (the app list in a dropdown)
|
||||
|
||||
The media player's source list is your **Apps** map — the same map the Browse tree and
|
||||
app-name resolution use. With no map configured it falls back to a small built-in list
|
||||
The media player's source list is your **Apps** map — the same map app-name resolution
|
||||
uses. With no map configured it falls back to a small built-in list
|
||||
(TiviMate, Wholphin, Netflix, YouTube), so a universal-media-player card always has
|
||||
something to show. Discovery fills the **Configure** table, not this fallback: the
|
||||
dropdown stays exactly the map you saved, so what a card offers is always what you
|
||||
chose. An app that discovery found and you want in the dropdown is one row to add.
|
||||
|
||||
**Apps are sources, not browser media.** This integration no longer publishes the app
|
||||
directory through HA's media **Browser**: the Browse dialog shows an **empty tree** for
|
||||
this entity (the device name, no rows) instead of a second, inconsistent copy of the
|
||||
same list — the user report was *"Media Browser shows Apps. To me it should probably be
|
||||
integrated with the home assistant media display, keep select sources for apps"*.
|
||||
Everything an app row could do lives in `source_list` and `media_player.select_source`
|
||||
below, and nothing else on this entity is browsable, so there is no dead entry to click
|
||||
and nothing else the browser could show.
|
||||
|
||||
```yaml
|
||||
# a media-player card, or anything that reads source_list
|
||||
type: media-control
|
||||
@@ -516,7 +532,7 @@ entity: media_player.living_room_shield
|
||||
sensor: your map first, then the cached device label, then the raw package.
|
||||
- **`media_player.select_source`** — picks one **by name** and launches it over the
|
||||
protocol, the same path `play_media` uses for apps. A raw package id works too (that
|
||||
is what the Browse tree publishes), names match case-insensitively, and a name that
|
||||
holds the package id), names match case-insensitively, and a name that
|
||||
is not in the list is an error rather than a silent no-op:
|
||||
|
||||
```yaml
|
||||
@@ -537,10 +553,9 @@ The map is your own override, so two shapes are accepted rather than papered ove
|
||||
|
||||
- **Names differing only by case** (`com.a=Dup`, `com.b=dup`) offer **one** dropdown
|
||||
row — the first spelling — because name matching is case-insensitive. The shadowed
|
||||
app is not lost: the Browse tree still lists it under its own package id, and
|
||||
`media_player.select_source` accepts that id.
|
||||
app is not lost: `media_player.select_source` accepts its own package id.
|
||||
- A **padded value** typed by hand (`com.x= Netflix `) is shown cleaned in the
|
||||
dropdown and the Browse tree, but `app_name`, `source` and the Current App sensor
|
||||
dropdown, but `app_name`, `source` and the Current App sensor
|
||||
display the map value **verbatim** — so `source` can be a string that is not in
|
||||
`source_list`. A blank value falls through to the raw package instead.
|
||||
|
||||
|
||||
Reference in new issue
Block a user