fix(media_player): D1 — browse builds real BrowseMedia nodes, not BrowseMediaSource (t37)
`homeassistant.components.media_player` has not exported `BrowseMediaSource` for years: the class lives in `homeassistant.components.media_source.models`, where it exists to build `media_source://<domain>` ids and is the only place a `domain` parameter belongs (t30 G18's note was about THAT class). The local import therefore raised ImportError, and with it every real browse failed — while the suite stayed green because the harness stubbed a `BrowseMediaSource` that real HA does not expose from there. `async_browse_media` now builds the standard tree exactly as core's own androidtv_remote/media_player.py does: `BrowseMedia` imported from homeassistant.components.media_player at module scope, every node carrying an explicit media_content_id/media_content_type (MediaType.APP/APPS), `children_media_class=MediaClass.APP` on the directory, and no `domain`. Verified against a real install: the old import raises ImportError, the old `domain=` kwarg raises TypeError, and the new node constructs. The harness is now faithful so this cannot hide again: * `entity_stublib` exposes a `BrowseMedia` with the real keyword-only signature, `as_dict()` and `calculate_children_class()`, plus `MediaType`, and deliberately NOT `BrowseMediaSource` — so both the drifted import and the drifted kwarg fail in-suite; * the app-directory test asserts the standard shape (classes, explicit ids and types, children_media_class, the serialised payload) instead of the removed `domain` attribute; * TestFaithfulHaStubs pins the harness contract on the AST (no production module imports BrowseMediaSource) rather than on prose. Verified: 284 passed, 2 skipped (D1 stage); ruff clean. Full gate re-run on the completed branch: 289 passed / 2 skipped and 290 passed / 1 skipped.
This commit is contained in:
1 parent
5ede4fe722
commit
37b2accfda
3 files changed
+160
-28
No files matched your search
@@ -26,10 +26,13 @@ import time
|
||||
|
||||
import voluptuous as vol
|
||||
from homeassistant.components.media_player import (
|
||||
BrowseMedia,
|
||||
MediaClass,
|
||||
MediaPlayerDeviceClass,
|
||||
MediaPlayerEntity,
|
||||
MediaPlayerEntityFeature,
|
||||
MediaPlayerState,
|
||||
MediaType,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import STATE_PAUSED, STATE_PLAYING
|
||||
@@ -961,36 +964,40 @@ class NvidiaShieldMediaPlayer(MediaPlayerEntity):
|
||||
# ── BrowseMedia app directory (P4-2c) ──
|
||||
|
||||
async def async_browse_media(self, media_content_type=None, media_content_id=None):
|
||||
"""Root lists the apps map (or the curated default) → play_media(app)."""
|
||||
from homeassistant.components.media_player import (
|
||||
BrowseMediaSource,
|
||||
MediaClass,
|
||||
)
|
||||
"""Root lists the apps map (or the curated default) → play_media(app).
|
||||
|
||||
t37 D1: the standard media_player tree, exactly as core's own
|
||||
androidtv_remote/media_player.py builds it — ``BrowseMedia`` from
|
||||
homeassistant.components.media_player, every node carrying an explicit
|
||||
media_content_id/media_content_type, and the directory naming the
|
||||
class of its children. ``BrowseMediaSource`` only ever existed in
|
||||
homeassistant.components.media_source.models (t30 G18's domain
|
||||
parameter is ITS contract: it builds ``media_source://<domain>`` ids),
|
||||
so an app-directory node must never claim one — and the import above
|
||||
removed a name media_player has not exported for a long time, which
|
||||
made every real browse raise.
|
||||
"""
|
||||
app_map = self._entry.options.get(CONF_APP_NAME_MAP) or DEFAULT_APP_DIRECTORY
|
||||
# G18 (2026-05-20 blog): BrowseMediaSource must carry the domain
|
||||
# parameter so the frontend can attribute the media tree.
|
||||
children = [
|
||||
BrowseMediaSource(
|
||||
BrowseMedia(
|
||||
title=name,
|
||||
media_class=MediaClass.APP,
|
||||
media_content_type="app",
|
||||
media_content_type=MediaType.APP,
|
||||
media_content_id=pkg,
|
||||
can_play=True,
|
||||
can_expand=False,
|
||||
domain=DOMAIN,
|
||||
)
|
||||
for pkg, name in app_map.items()
|
||||
]
|
||||
return BrowseMediaSource(
|
||||
return BrowseMedia(
|
||||
title="Apps",
|
||||
media_class=MediaClass.DIRECTORY,
|
||||
media_content_type="apps",
|
||||
media_content_type=MediaType.APPS,
|
||||
media_content_id="apps",
|
||||
children_media_class=MediaClass.APP,
|
||||
can_play=False,
|
||||
can_expand=True,
|
||||
children=children,
|
||||
domain=DOMAIN,
|
||||
)
|
||||
|
||||
# ── play_media (§7): channel digits, app/url launch ──
|
||||
|
||||
Reference in new issue
Block a user