core/homeassistant/components/lovelace/cast.py

209 lines
5.7 KiB
Python

"""Home Assistant Cast platform."""
from __future__ import annotations
from pychromecast import Chromecast
from pychromecast.const import CAST_TYPE_CHROMECAST
from homeassistant.components.cast import DOMAIN as CAST_DOMAIN
from homeassistant.components.cast.home_assistant_cast import (
ATTR_URL_PATH,
ATTR_VIEW_PATH,
NO_URL_AVAILABLE_ERROR,
SERVICE_SHOW_VIEW,
)
from homeassistant.components.media_player import (
BrowseError,
BrowseMedia,
MediaClass,
MediaType,
)
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.network import NoURLAvailableError, get_url
from .const import DOMAIN, ConfigNotFound
from .dashboard import LovelaceConfig
DEFAULT_DASHBOARD = "_default_"
async def async_get_media_browser_root_object(
hass: HomeAssistant, cast_type: str
) -> list[BrowseMedia]:
"""Create a root object for media browsing."""
if cast_type != CAST_TYPE_CHROMECAST:
return []
return [
BrowseMedia(
title="Dashboards",
media_class=MediaClass.APP,
media_content_id="",
media_content_type=DOMAIN,
thumbnail="https://brands.home-assistant.io/_/lovelace/logo.png",
can_play=False,
can_expand=True,
)
]
async def async_browse_media(
hass: HomeAssistant,
media_content_type: MediaType | str,
media_content_id: str,
cast_type: str,
) -> BrowseMedia | None:
"""Browse media."""
if media_content_type != DOMAIN:
return None
try:
get_url(hass, require_ssl=True, prefer_external=True)
except NoURLAvailableError as err:
raise BrowseError(NO_URL_AVAILABLE_ERROR) from err
# List dashboards.
if not media_content_id:
children = [
BrowseMedia(
title="Default",
media_class=MediaClass.APP,
media_content_id=DEFAULT_DASHBOARD,
media_content_type=DOMAIN,
thumbnail="https://brands.home-assistant.io/_/lovelace/logo.png",
can_play=True,
can_expand=False,
)
]
for url_path in hass.data[DOMAIN]["dashboards"]:
if url_path is None:
continue
info = await _get_dashboard_info(hass, url_path)
children.append(_item_from_info(info))
root = (await async_get_media_browser_root_object(hass, CAST_TYPE_CHROMECAST))[
0
]
root.children = children
return root
try:
info = await _get_dashboard_info(hass, media_content_id)
except ValueError as err:
raise BrowseError(f"Dashboard {media_content_id} not found") from err
children = []
for view in info["views"]:
children.append(
BrowseMedia(
title=view["title"],
media_class=MediaClass.APP,
media_content_id=f'{info["url_path"]}/{view["path"]}',
media_content_type=DOMAIN,
thumbnail="https://brands.home-assistant.io/_/lovelace/logo.png",
can_play=True,
can_expand=False,
)
)
root = _item_from_info(info)
root.children = children
return root
async def async_play_media(
hass: HomeAssistant,
cast_entity_id: str,
chromecast: Chromecast,
media_type: MediaType | str,
media_id: str,
) -> bool:
"""Play media."""
if media_type != DOMAIN:
return False
if "/" in media_id:
url_path, view_path = media_id.split("/", 1)
else:
url_path = media_id
try:
info = await _get_dashboard_info(hass, media_id)
except ValueError as err:
raise HomeAssistantError(f"Invalid dashboard {media_id} specified") from err
view_path = info["views"][0]["path"] if info["views"] else "0"
data = {
ATTR_ENTITY_ID: cast_entity_id,
ATTR_VIEW_PATH: view_path,
}
if url_path != DEFAULT_DASHBOARD:
data[ATTR_URL_PATH] = url_path
await hass.services.async_call(
CAST_DOMAIN,
SERVICE_SHOW_VIEW,
data,
blocking=True,
)
return True
async def _get_dashboard_info(hass, url_path):
"""Load a dashboard and return info on views."""
if url_path == DEFAULT_DASHBOARD:
url_path = None
dashboard: LovelaceConfig | None = hass.data[DOMAIN]["dashboards"].get(url_path)
if dashboard is None:
raise ValueError("Invalid dashboard specified")
try:
config = await dashboard.async_load(False)
except ConfigNotFound:
config = None
if dashboard.url_path is None:
url_path = DEFAULT_DASHBOARD
title = "Default"
else:
url_path = dashboard.url_path
title = config.get("title", url_path) if config else url_path
views = []
data = {
"title": title,
"url_path": url_path,
"views": views,
}
if config is None or "views" not in config:
return data
for idx, view in enumerate(config["views"]):
path = view.get("path", f"{idx}")
views.append(
{
"title": view.get("title", path),
"path": path,
}
)
return data
@callback
def _item_from_info(info: dict) -> BrowseMedia:
"""Convert dashboard info to browse item."""
return BrowseMedia(
title=info["title"],
media_class=MediaClass.APP,
media_content_id=info["url_path"],
media_content_type=DOMAIN,
thumbnail="https://brands.home-assistant.io/_/lovelace/logo.png",
can_play=True,
can_expand=len(info["views"]) > 1,
)