Home Assistant Unofficial Reference 2024.12.1
cast.py
Go to the documentation of this file.
1 """Home Assistant Cast platform."""
2 
3 from __future__ import annotations
4 
5 from pychromecast import Chromecast
6 from pychromecast.const import CAST_TYPE_CHROMECAST
7 
8 from homeassistant.components.cast import DOMAIN as CAST_DOMAIN
10  ATTR_URL_PATH,
11  ATTR_VIEW_PATH,
12  NO_URL_AVAILABLE_ERROR,
13  SERVICE_SHOW_VIEW,
14 )
16  BrowseError,
17  BrowseMedia,
18  MediaClass,
19  MediaType,
20 )
21 from homeassistant.const import ATTR_ENTITY_ID
22 from homeassistant.core import HomeAssistant, callback
23 from homeassistant.exceptions import HomeAssistantError
24 from homeassistant.helpers.network import NoURLAvailableError, get_url
25 
26 from .const import DOMAIN, ConfigNotFound
27 from .dashboard import LovelaceConfig
28 
29 DEFAULT_DASHBOARD = "_default_"
30 
31 
33  hass: HomeAssistant, cast_type: str
34 ) -> list[BrowseMedia]:
35  """Create a root object for media browsing."""
36  if cast_type != CAST_TYPE_CHROMECAST:
37  return []
38  return [
39  BrowseMedia(
40  title="Dashboards",
41  media_class=MediaClass.APP,
42  media_content_id="",
43  media_content_type=DOMAIN,
44  thumbnail="https://brands.home-assistant.io/_/lovelace/logo.png",
45  can_play=False,
46  can_expand=True,
47  )
48  ]
49 
50 
52  hass: HomeAssistant,
53  media_content_type: MediaType | str,
54  media_content_id: str,
55  cast_type: str,
56 ) -> BrowseMedia | None:
57  """Browse media."""
58  if media_content_type != DOMAIN:
59  return None
60 
61  try:
62  get_url(hass, require_ssl=True, prefer_external=True)
63  except NoURLAvailableError as err:
64  raise BrowseError(NO_URL_AVAILABLE_ERROR) from err
65 
66  # List dashboards.
67  if not media_content_id:
68  children = [
69  BrowseMedia(
70  title="Default",
71  media_class=MediaClass.APP,
72  media_content_id=DEFAULT_DASHBOARD,
73  media_content_type=DOMAIN,
74  thumbnail="https://brands.home-assistant.io/_/lovelace/logo.png",
75  can_play=True,
76  can_expand=False,
77  )
78  ]
79  for url_path in hass.data[DOMAIN]["dashboards"]:
80  if url_path is None:
81  continue
82 
83  info = await _get_dashboard_info(hass, url_path)
84  children.append(_item_from_info(info))
85 
86  root = (await async_get_media_browser_root_object(hass, CAST_TYPE_CHROMECAST))[
87  0
88  ]
89  root.children = children
90  return root
91 
92  try:
93  info = await _get_dashboard_info(hass, media_content_id)
94  except ValueError as err:
95  raise BrowseError(f"Dashboard {media_content_id} not found") from err
96 
97  children = []
98 
99  for view in info["views"]:
100  children.append(
101  BrowseMedia(
102  title=view["title"],
103  media_class=MediaClass.APP,
104  media_content_id=f'{info["url_path"]}/{view["path"]}',
105  media_content_type=DOMAIN,
106  thumbnail="https://brands.home-assistant.io/_/lovelace/logo.png",
107  can_play=True,
108  can_expand=False,
109  )
110  )
111 
112  root = _item_from_info(info)
113  root.children = children
114  return root
115 
116 
118  hass: HomeAssistant,
119  cast_entity_id: str,
120  chromecast: Chromecast,
121  media_type: MediaType | str,
122  media_id: str,
123 ) -> bool:
124  """Play media."""
125  if media_type != DOMAIN:
126  return False
127 
128  if "/" in media_id:
129  url_path, view_path = media_id.split("/", 1)
130  else:
131  url_path = media_id
132  try:
133  info = await _get_dashboard_info(hass, media_id)
134  except ValueError as err:
135  raise HomeAssistantError(f"Invalid dashboard {media_id} specified") from err
136  view_path = info["views"][0]["path"] if info["views"] else "0"
137 
138  data = {
139  ATTR_ENTITY_ID: cast_entity_id,
140  ATTR_VIEW_PATH: view_path,
141  }
142  if url_path != DEFAULT_DASHBOARD:
143  data[ATTR_URL_PATH] = url_path
144 
145  await hass.services.async_call(
146  CAST_DOMAIN,
147  SERVICE_SHOW_VIEW,
148  data,
149  blocking=True,
150  )
151  return True
152 
153 
154 async def _get_dashboard_info(hass, url_path):
155  """Load a dashboard and return info on views."""
156  if url_path == DEFAULT_DASHBOARD:
157  url_path = None
158  dashboard: LovelaceConfig | None = hass.data[DOMAIN]["dashboards"].get(url_path)
159 
160  if dashboard is None:
161  raise ValueError("Invalid dashboard specified")
162 
163  try:
164  config = await dashboard.async_load(False)
165  except ConfigNotFound:
166  config = None
167 
168  if dashboard.url_path is None:
169  url_path = DEFAULT_DASHBOARD
170  title = "Default"
171  else:
172  url_path = dashboard.url_path
173  title = config.get("title", url_path) if config else url_path
174 
175  views = []
176  data = {
177  "title": title,
178  "url_path": url_path,
179  "views": views,
180  }
181 
182  if config is None or "views" not in config:
183  return data
184 
185  for idx, view in enumerate(config["views"]):
186  path = view.get("path", f"{idx}")
187  views.append(
188  {
189  "title": view.get("title", path),
190  "path": path,
191  }
192  )
193 
194  return data
195 
196 
197 @callback
198 def _item_from_info(info: dict) -> BrowseMedia:
199  """Convert dashboard info to browse item."""
200  return BrowseMedia(
201  title=info["title"],
202  media_class=MediaClass.APP,
203  media_content_id=info["url_path"],
204  media_content_type=DOMAIN,
205  thumbnail="https://brands.home-assistant.io/_/lovelace/logo.png",
206  can_play=True,
207  can_expand=len(info["views"]) > 1,
208  )
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
list[BrowseMedia] async_get_media_browser_root_object(HomeAssistant hass, str cast_type)
Definition: cast.py:34
def _get_dashboard_info(hass, url_path)
Definition: cast.py:154
BrowseMedia _item_from_info(dict info)
Definition: cast.py:198
BrowseMedia|None async_browse_media(HomeAssistant hass, MediaType|str media_content_type, str media_content_id, str cast_type)
Definition: cast.py:56
bool async_play_media(HomeAssistant hass, str cast_entity_id, Chromecast chromecast, MediaType|str media_type, str media_id)
Definition: cast.py:123
str get_url(HomeAssistant hass, *bool require_current_request=False, bool require_ssl=False, bool require_standard_port=False, bool require_cloud=False, bool allow_internal=True, bool allow_external=True, bool allow_cloud=True, bool|None allow_ip=None, bool|None prefer_external=None, bool prefer_cloud=False)
Definition: network.py:131