Home Assistant Unofficial Reference 2024.12.1
media_source.py
Go to the documentation of this file.
1 """Expose cameras as media sources."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 
7 from homeassistant.components.media_player import BrowseError, MediaClass
9  BrowseMediaSource,
10  MediaSource,
11  MediaSourceItem,
12  PlayMedia,
13  Unresolvable,
14 )
15 from homeassistant.components.stream import FORMAT_CONTENT_TYPE, HLS_PROVIDER
16 from homeassistant.const import ATTR_FRIENDLY_NAME
17 from homeassistant.core import HomeAssistant
18 from homeassistant.exceptions import HomeAssistantError
19 
20 from . import Camera, _async_stream_endpoint_url
21 from .const import DATA_COMPONENT, DOMAIN, StreamType
22 
23 
24 async def async_get_media_source(hass: HomeAssistant) -> CameraMediaSource:
25  """Set up camera media source."""
26  return CameraMediaSource(hass)
27 
28 
30  hass: HomeAssistant, camera: Camera, content_type: str
31 ) -> BrowseMediaSource:
32  camera_state = hass.states.get(camera.entity_id)
33  title = camera.name
34  if camera_state:
35  title = camera_state.attributes.get(ATTR_FRIENDLY_NAME, camera.name)
36 
37  return BrowseMediaSource(
38  domain=DOMAIN,
39  identifier=camera.entity_id,
40  media_class=MediaClass.VIDEO,
41  media_content_type=content_type,
42  title=title,
43  thumbnail=f"/api/camera_proxy/{camera.entity_id}",
44  can_play=True,
45  can_expand=False,
46  )
47 
48 
49 class CameraMediaSource(MediaSource):
50  """Provide camera feeds as media sources."""
51 
52  name: str = "Camera"
53 
54  def __init__(self, hass: HomeAssistant) -> None:
55  """Initialize CameraMediaSource."""
56  super().__init__(DOMAIN)
57  self.hasshass = hass
58 
59  async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia:
60  """Resolve media to a url."""
61  component = self.hasshass.data[DATA_COMPONENT]
62  camera = component.get_entity(item.identifier)
63 
64  if not camera:
65  raise Unresolvable(f"Could not resolve media item: {item.identifier}")
66 
67  if not (stream_types := camera.camera_capabilities.frontend_stream_types):
68  return PlayMedia(
69  f"/api/camera_proxy_stream/{camera.entity_id}", camera.content_type
70  )
71 
72  if "stream" not in self.hasshass.config.components:
73  raise Unresolvable("Stream integration not loaded")
74 
75  try:
76  url = await _async_stream_endpoint_url(self.hasshass, camera, HLS_PROVIDER)
77  except HomeAssistantError as err:
78  # Handle known error
79  if StreamType.HLS not in stream_types:
80  raise Unresolvable(
81  "Camera does not support MJPEG or HLS streaming."
82  ) from err
83  raise Unresolvable(str(err)) from err
84 
85  return PlayMedia(url, FORMAT_CONTENT_TYPE[HLS_PROVIDER])
86 
87  async def async_browse_media(
88  self,
89  item: MediaSourceItem,
90  ) -> BrowseMediaSource:
91  """Return media."""
92  if item.identifier:
93  raise BrowseError("Unknown item")
94 
95  can_stream_hls = "stream" in self.hasshass.config.components
96 
97  async def _filter_browsable_camera(camera: Camera) -> BrowseMediaSource | None:
98  stream_types = camera.camera_capabilities.frontend_stream_types
99  if not stream_types:
100  return _media_source_for_camera(self.hasshass, camera, camera.content_type)
101  if not can_stream_hls:
102  return None
103 
104  content_type = FORMAT_CONTENT_TYPE[HLS_PROVIDER]
105  if StreamType.HLS not in stream_types and not (
106  await camera.stream_source()
107  ):
108  return None
109 
110  return _media_source_for_camera(self.hasshass, camera, content_type)
111 
112  component = self.hasshass.data[DATA_COMPONENT]
113  results = await asyncio.gather(
114  *(_filter_browsable_camera(camera) for camera in component.entities),
115  return_exceptions=True,
116  )
117  children = [
118  result for result in results if isinstance(result, BrowseMediaSource)
119  ]
120  not_shown = len(results) - len(children)
121  return BrowseMediaSource(
122  domain=DOMAIN,
123  identifier=None,
124  media_class=MediaClass.APP,
125  media_content_type="",
126  title="Camera",
127  can_play=False,
128  can_expand=True,
129  children_media_class=MediaClass.VIDEO,
130  children=children,
131  not_shown=not_shown,
132  )
BrowseMediaSource async_browse_media(self, MediaSourceItem item)
Definition: media_source.py:90
PlayMedia async_resolve_media(self, MediaSourceItem item)
Definition: media_source.py:59
CameraMediaSource async_get_media_source(HomeAssistant hass)
Definition: media_source.py:24
BrowseMediaSource _media_source_for_camera(HomeAssistant hass, Camera camera, str content_type)
Definition: media_source.py:31
str _async_stream_endpoint_url(HomeAssistant hass, Camera camera, str fmt)
Definition: __init__.py:1236