Home Assistant Unofficial Reference 2024.12.1
media_player.py
Go to the documentation of this file.
1 """Support for Ubiquiti's UniFi Protect NVR."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from typing import Any
7 
8 from uiprotect.data import Camera, ProtectAdoptableDeviceModel, StateType
9 from uiprotect.exceptions import StreamError
10 
11 from homeassistant.components import media_source
13  BrowseMedia,
14  MediaPlayerDeviceClass,
15  MediaPlayerEntity,
16  MediaPlayerEntityDescription,
17  MediaPlayerEntityFeature,
18  MediaPlayerState,
19  MediaType,
20  async_process_play_media_url,
21 )
22 from homeassistant.core import HomeAssistant, callback
23 from homeassistant.exceptions import HomeAssistantError
24 from homeassistant.helpers.entity_platform import AddEntitiesCallback
25 
26 from .data import ProtectDeviceType, UFPConfigEntry
27 from .entity import ProtectDeviceEntity
28 
29 _LOGGER = logging.getLogger(__name__)
30 
31 _SPEAKER_DESCRIPTION = MediaPlayerEntityDescription(
32  key="speaker", name="Speaker", device_class=MediaPlayerDeviceClass.SPEAKER
33 )
34 
35 
37  hass: HomeAssistant,
38  entry: UFPConfigEntry,
39  async_add_entities: AddEntitiesCallback,
40 ) -> None:
41  """Discover cameras with speakers on a UniFi Protect NVR."""
42  data = entry.runtime_data
43 
44  @callback
45  def _add_new_device(device: ProtectAdoptableDeviceModel) -> None:
46  if isinstance(device, Camera) and (
47  device.has_speaker or device.has_removable_speaker
48  ):
49  async_add_entities([ProtectMediaPlayer(data, device)])
50 
51  data.async_subscribe_adopt(_add_new_device)
53  ProtectMediaPlayer(data, device, _SPEAKER_DESCRIPTION)
54  for device in data.get_cameras()
55  if device.has_speaker or device.has_removable_speaker
56  )
57 
58 
60  """A Ubiquiti UniFi Protect Speaker."""
61 
62  device: Camera
63  entity_description: MediaPlayerEntityDescription
64  _attr_supported_features = (
65  MediaPlayerEntityFeature.PLAY_MEDIA
66  | MediaPlayerEntityFeature.VOLUME_SET
67  | MediaPlayerEntityFeature.VOLUME_STEP
68  | MediaPlayerEntityFeature.STOP
69  | MediaPlayerEntityFeature.BROWSE_MEDIA
70  )
71  _attr_media_content_type = MediaType.MUSIC
72  _state_attrs = ("_attr_available", "_attr_state", "_attr_volume_level")
73 
74  @callback
75  def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:
76  super()._async_update_device_from_protect(device)
77  updated_device = self.devicedevice
78  self._attr_volume_level_attr_volume_level = float(updated_device.speaker_settings.volume / 100)
79 
80  if (
81  updated_device.talkback_stream is not None
82  and updated_device.talkback_stream.is_running
83  ):
84  self._attr_state_attr_state = MediaPlayerState.PLAYING
85  else:
86  self._attr_state_attr_state = MediaPlayerState.IDLE
87 
88  is_connected = self.datadata.last_update_success and (
89  updated_device.state is StateType.CONNECTED
90  or (not updated_device.is_adopted_by_us and updated_device.can_adopt)
91  )
92  self._attr_available_attr_available_attr_available = is_connected and updated_device.feature_flags.has_speaker
93 
94  async def async_set_volume_level(self, volume: float) -> None:
95  """Set volume level, range 0..1."""
96 
97  volume_int = int(volume * 100)
98  await self.devicedevice.set_speaker_volume(volume_int)
99 
100  async def async_media_stop(self) -> None:
101  """Send stop command."""
102 
103  if (
104  self.devicedevice.talkback_stream is not None
105  and self.devicedevice.talkback_stream.is_running
106  ):
107  _LOGGER.debug("Stopping playback for %s Speaker", self.devicedevice.display_name)
108  await self.devicedevice.stop_audio()
109  self._async_updated_event_async_updated_event(self.devicedevice)
110 
111  async def async_play_media(
112  self, media_type: MediaType | str, media_id: str, **kwargs: Any
113  ) -> None:
114  """Play a piece of media."""
115  if media_source.is_media_source_id(media_id):
116  media_type = MediaType.MUSIC
117  play_item = await media_source.async_resolve_media(
118  self.hasshass, media_id, self.entity_identity_id
119  )
120  media_id = async_process_play_media_url(self.hasshass, play_item.url)
121 
122  if media_type != MediaType.MUSIC:
123  raise HomeAssistantError("Only music media type is supported")
124 
125  _LOGGER.debug(
126  "Playing Media %s for %s Speaker", media_id, self.devicedevice.display_name
127  )
128  await self.async_media_stopasync_media_stopasync_media_stop()
129  try:
130  await self.devicedevice.play_audio(media_id, blocking=False)
131  except StreamError as err:
132  raise HomeAssistantError(err) from err
133 
134  # update state after starting player
135  self._async_updated_event_async_updated_event(self.devicedevice)
136  # wait until player finishes to update state again
137  await self.devicedevice.wait_until_audio_completes()
138 
139  self._async_updated_event_async_updated_event(self.devicedevice)
140 
142  self,
143  media_content_type: MediaType | str | None = None,
144  media_content_id: str | None = None,
145  ) -> BrowseMedia:
146  """Implement the websocket media browsing helper."""
147  return await media_source.async_browse_media(
148  self.hasshass,
149  media_content_id,
150  content_filter=lambda item: item.media_content_type.startswith("audio/"),
151  )
None _async_updated_event(self, ProtectDeviceType device)
Definition: entity.py:242
BrowseMedia async_browse_media(self, MediaType|str|None media_content_type=None, str|None media_content_id=None)
None _async_update_device_from_protect(self, ProtectDeviceType device)
Definition: media_player.py:75
None async_play_media(self, MediaType|str media_type, str media_id, **Any kwargs)
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
None async_setup_entry(HomeAssistant hass, UFPConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: media_player.py:40