Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """The media_source integration."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from typing import Any, Protocol
7 
8 import voluptuous as vol
9 
10 from homeassistant.components import frontend, websocket_api
12  ATTR_MEDIA_CONTENT_ID,
13  CONTENT_AUTH_EXPIRY_TIME,
14  BrowseError,
15  BrowseMedia,
16  async_process_play_media_url,
17 )
18 from homeassistant.components.websocket_api import ActiveConnection
19 from homeassistant.core import HomeAssistant, callback
20 from homeassistant.helpers import config_validation as cv
21 from homeassistant.helpers.frame import report_usage
23  async_process_integration_platforms,
24 )
25 from homeassistant.helpers.typing import UNDEFINED, ConfigType, UndefinedType
26 from homeassistant.loader import bind_hass
27 
28 from . import local_source
29 from .const import (
30  DOMAIN,
31  MEDIA_CLASS_MAP,
32  MEDIA_MIME_TYPES,
33  URI_SCHEME,
34  URI_SCHEME_REGEX,
35 )
36 from .error import MediaSourceError, Unresolvable
37 from .models import BrowseMediaSource, MediaSource, MediaSourceItem, PlayMedia
38 
39 __all__ = [
40  "DOMAIN",
41  "is_media_source_id",
42  "generate_media_source_id",
43  "async_browse_media",
44  "async_resolve_media",
45  "BrowseMediaSource",
46  "PlayMedia",
47  "MediaSourceItem",
48  "Unresolvable",
49  "MediaSource",
50  "MediaSourceError",
51  "MEDIA_CLASS_MAP",
52  "MEDIA_MIME_TYPES",
53 ]
54 
55 
56 CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
57 
58 
59 class MediaSourceProtocol(Protocol):
60  """Define the format of media_source platforms."""
61 
62  async def async_get_media_source(self, hass: HomeAssistant) -> MediaSource:
63  """Set up media source."""
64 
65 
66 def is_media_source_id(media_content_id: str) -> bool:
67  """Test if identifier is a media source."""
68  return URI_SCHEME_REGEX.match(media_content_id) is not None
69 
70 
71 def generate_media_source_id(domain: str, identifier: str) -> str:
72  """Generate a media source ID."""
73  uri = f"{URI_SCHEME}{domain or ''}"
74  if identifier:
75  uri += f"/{identifier}"
76  return uri
77 
78 
79 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
80  """Set up the media_source component."""
81  hass.data[DOMAIN] = {}
82  websocket_api.async_register_command(hass, websocket_browse_media)
83  websocket_api.async_register_command(hass, websocket_resolve_media)
84  frontend.async_register_built_in_panel(
85  hass, "media-browser", "media_browser", "hass:play-box-multiple"
86  )
87  local_source.async_setup(hass)
89  hass, DOMAIN, _process_media_source_platform
90  )
91  return True
92 
93 
95  hass: HomeAssistant,
96  domain: str,
97  platform: MediaSourceProtocol,
98 ) -> None:
99  """Process a media source platform."""
100  hass.data[DOMAIN][domain] = await platform.async_get_media_source(hass)
101 
102 
103 @callback
105  hass: HomeAssistant, media_content_id: str | None, target_media_player: str | None
106 ) -> MediaSourceItem:
107  """Return media item."""
108  if media_content_id:
109  item = MediaSourceItem.from_uri(hass, media_content_id, target_media_player)
110  else:
111  # We default to our own domain if its only one registered
112  domain = None if len(hass.data[DOMAIN]) > 1 else DOMAIN
113  return MediaSourceItem(hass, domain, "", target_media_player)
114 
115  if item.domain is not None and item.domain not in hass.data[DOMAIN]:
116  raise ValueError("Unknown media source")
117 
118  return item
119 
120 
121 @bind_hass
123  hass: HomeAssistant,
124  media_content_id: str | None,
125  *,
126  content_filter: Callable[[BrowseMedia], bool] | None = None,
127 ) -> BrowseMediaSource:
128  """Return media player browse media results."""
129  if DOMAIN not in hass.data:
130  raise BrowseError("Media Source not loaded")
131 
132  try:
133  item = await _get_media_item(hass, media_content_id, None).async_browse()
134  except ValueError as err:
135  raise BrowseError(str(err)) from err
136 
137  if content_filter is None or item.children is None:
138  return item
139 
140  old_count = len(item.children)
141  item.children = [
142  child for child in item.children if child.can_expand or content_filter(child)
143  ]
144  item.not_shown += old_count - len(item.children)
145  return item
146 
147 
148 @bind_hass
150  hass: HomeAssistant,
151  media_content_id: str,
152  target_media_player: str | None | UndefinedType = UNDEFINED,
153 ) -> PlayMedia:
154  """Get info to play media."""
155  if DOMAIN not in hass.data:
156  raise Unresolvable("Media Source not loaded")
157 
158  if target_media_player is UNDEFINED:
159  report_usage(
160  "calls media_source.async_resolve_media without passing an entity_id",
161  exclude_integrations={DOMAIN},
162  )
163  target_media_player = None
164 
165  try:
166  item = _get_media_item(hass, media_content_id, target_media_player)
167  except ValueError as err:
168  raise Unresolvable(str(err)) from err
169 
170  return await item.async_resolve()
171 
172 
173 @websocket_api.websocket_command( { vol.Required("type"): "media_source/browse_media",
174  vol.Optional(ATTR_MEDIA_CONTENT_ID, default=""): str,
175  }
176 )
177 @websocket_api.async_response
178 async def websocket_browse_media(
179  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
180 ) -> None:
181  """Browse available media."""
182  try:
183  media = await async_browse_media(hass, msg.get("media_content_id", ""))
184  connection.send_result(
185  msg["id"],
186  media.as_dict(),
187  )
188  except BrowseError as err:
189  connection.send_error(msg["id"], "browse_media_failed", str(err))
190 
191 
192 @websocket_api.websocket_command( { vol.Required("type"): "media_source/resolve_media",
193  vol.Required(ATTR_MEDIA_CONTENT_ID): str,
194  vol.Optional("expires", default=CONTENT_AUTH_EXPIRY_TIME): int,
195  }
196 )
197 @websocket_api.async_response
198 async def websocket_resolve_media(
199  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
200 ) -> None:
201  """Resolve media."""
202  try:
203  media = await async_resolve_media(hass, msg["media_content_id"], None)
204  except Unresolvable as err:
205  connection.send_error(msg["id"], "resolve_media_failed", str(err))
206  return
207 
208  connection.send_result(
209  msg["id"],
210  {
212  hass, media.url, allow_relative_url=True
213  ),
214  "mime_type": media.mime_type,
215  },
216  )
217 
MediaSource async_get_media_source(self, HomeAssistant hass)
Definition: __init__.py:62
str async_process_play_media_url(HomeAssistant hass, str media_content_id, *bool allow_relative_url=False, bool for_supervisor_network=False)
Definition: browse_media.py:36
str generate_media_source_id(str domain, str identifier)
Definition: __init__.py:71
None _process_media_source_platform(HomeAssistant hass, str domain, MediaSourceProtocol platform)
Definition: __init__.py:98
MediaSourceItem _get_media_item(HomeAssistant hass, str|None media_content_id, str|None target_media_player)
Definition: __init__.py:106
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:79
None websocket_browse_media(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: __init__.py:182
None websocket_resolve_media(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: __init__.py:204
BrowseMediaSource async_browse_media(HomeAssistant hass, str|None media_content_id, *Callable[[BrowseMedia], bool]|None content_filter=None)
Definition: __init__.py:127
PlayMedia async_resolve_media(HomeAssistant hass, str media_content_id, str|None|UndefinedType target_media_player=UNDEFINED)
Definition: __init__.py:153
bool is_media_source_id(str media_content_id)
Definition: __init__.py:66
None report_usage(str what, *str|None breaks_in_ha_version=None, ReportBehavior core_behavior=ReportBehavior.ERROR, ReportBehavior core_integration_behavior=ReportBehavior.LOG, ReportBehavior custom_integration_behavior=ReportBehavior.LOG, set[str]|None exclude_integrations=None, str|None integration_domain=None, int level=logging.WARNING)
Definition: frame.py:195
None async_process_integration_platforms(HomeAssistant hass, str platform_name, Callable[[HomeAssistant, str, Any], Awaitable[None]|None] process_platform, bool wait_for_platforms=False)