Home Assistant Unofficial Reference 2024.12.1
media_player.py
Go to the documentation of this file.
1 """Media player support for Bravia TV integration."""
2 
3 from __future__ import annotations
4 
5 from datetime import datetime
6 from typing import Any
7 
9  BrowseError,
10  BrowseMedia,
11  MediaClass,
12  MediaPlayerDeviceClass,
13  MediaPlayerEntity,
14  MediaPlayerEntityFeature,
15  MediaPlayerState,
16  MediaType,
17 )
18 from homeassistant.core import HomeAssistant
19 from homeassistant.helpers.entity_platform import AddEntitiesCallback
20 
21 from . import BraviaTVConfigEntry
22 from .const import SourceType
23 from .entity import BraviaTVEntity
24 
25 
27  hass: HomeAssistant,
28  config_entry: BraviaTVConfigEntry,
29  async_add_entities: AddEntitiesCallback,
30 ) -> None:
31  """Set up Bravia TV Media Player from a config_entry."""
32 
33  coordinator = config_entry.runtime_data
34  unique_id = config_entry.unique_id
35  assert unique_id is not None
36 
38  [BraviaTVMediaPlayer(coordinator, unique_id, config_entry.title)]
39  )
40 
41 
43  """Representation of a Bravia TV Media Player."""
44 
45  _attr_name = None
46  _attr_assumed_state = True
47  _attr_device_class = MediaPlayerDeviceClass.TV
48  _attr_supported_features = (
49  MediaPlayerEntityFeature.PAUSE
50  | MediaPlayerEntityFeature.VOLUME_STEP
51  | MediaPlayerEntityFeature.VOLUME_MUTE
52  | MediaPlayerEntityFeature.VOLUME_SET
53  | MediaPlayerEntityFeature.PREVIOUS_TRACK
54  | MediaPlayerEntityFeature.NEXT_TRACK
55  | MediaPlayerEntityFeature.TURN_ON
56  | MediaPlayerEntityFeature.TURN_OFF
57  | MediaPlayerEntityFeature.SELECT_SOURCE
58  | MediaPlayerEntityFeature.PLAY
59  | MediaPlayerEntityFeature.STOP
60  | MediaPlayerEntityFeature.PLAY_MEDIA
61  | MediaPlayerEntityFeature.BROWSE_MEDIA
62  )
63 
64  @property
65  def state(self) -> MediaPlayerState:
66  """Return the state of the device."""
67  if self.coordinator.is_on:
68  return MediaPlayerState.ON
69  return MediaPlayerState.OFF
70 
71  @property
72  def source(self) -> str | None:
73  """Return the current input source."""
74  return self.coordinator.source
75 
76  @property
77  def source_list(self) -> list[str]:
78  """List of available input sources."""
79  return self.coordinator.source_list
80 
81  @property
82  def volume_level(self) -> float | None:
83  """Volume level of the media player (0..1)."""
84  return self.coordinator.volume_level
85 
86  @property
87  def is_volume_muted(self) -> bool:
88  """Boolean if volume is currently muted."""
89  return self.coordinator.volume_muted
90 
91  @property
92  def media_title(self) -> str | None:
93  """Title of current playing media."""
94  return self.coordinator.media_title
95 
96  @property
97  def media_channel(self) -> str | None:
98  """Channel currently playing."""
99  return self.coordinator.media_channel
100 
101  @property
102  def media_content_id(self) -> str | None:
103  """Content ID of current playing media."""
104  return self.coordinator.media_content_id
105 
106  @property
107  def media_content_type(self) -> MediaType | None:
108  """Content type of current playing media."""
109  return self.coordinator.media_content_type
110 
111  @property
112  def media_duration(self) -> int | None:
113  """Duration of current playing media in seconds."""
114  return self.coordinator.media_duration
115 
116  @property
117  def media_position(self) -> int | None:
118  """Position of current playing media in seconds."""
119  return self.coordinator.media_position
120 
121  @property
122  def media_position_updated_at(self) -> datetime | None:
123  """When was the position of the current playing media valid."""
124  return self.coordinator.media_position_updated_at
125 
126  async def async_turn_on(self) -> None:
127  """Turn the device on."""
128  await self.coordinator.async_turn_on()
129 
130  async def async_turn_off(self) -> None:
131  """Turn the device off."""
132  await self.coordinator.async_turn_off()
133 
134  async def async_set_volume_level(self, volume: float) -> None:
135  """Set volume level, range 0..1."""
136  await self.coordinator.async_set_volume_level(volume)
137 
138  async def async_volume_up(self) -> None:
139  """Send volume up command."""
140  await self.coordinator.async_volume_up()
141 
142  async def async_volume_down(self) -> None:
143  """Send volume down command."""
144  await self.coordinator.async_volume_down()
145 
146  async def async_mute_volume(self, mute: bool) -> None:
147  """Send mute command."""
148  await self.coordinator.async_volume_mute(mute)
149 
151  self,
152  media_content_type: MediaType | str | None = None,
153  media_content_id: str | None = None,
154  ) -> BrowseMedia:
155  """Browse apps and channels."""
156  if not media_content_id:
157  await self.coordinator.async_update_sources()
158  return await self.async_browse_media_rootasync_browse_media_root()
159 
160  path = media_content_id.partition("/")
161  if path[0] == "apps":
162  return await self.async_browse_media_appsasync_browse_media_apps(True)
163  if path[0] == "channels":
164  return await self.async_browse_media_channelsasync_browse_media_channels(True)
165 
166  raise BrowseError(f"Media not found: {media_content_type} / {media_content_id}")
167 
168  async def async_browse_media_root(self) -> BrowseMedia:
169  """Return root media objects."""
170 
171  return BrowseMedia(
172  title="Sony TV",
173  media_class=MediaClass.DIRECTORY,
174  media_content_id="",
175  media_content_type="",
176  can_play=False,
177  can_expand=True,
178  children=[
179  await self.async_browse_media_appsasync_browse_media_apps(),
180  await self.async_browse_media_channelsasync_browse_media_channels(),
181  ],
182  )
183 
184  async def async_browse_media_apps(self, expanded: bool = False) -> BrowseMedia:
185  """Return apps media objects."""
186  if expanded:
187  children = [
188  BrowseMedia(
189  title=item["title"],
190  media_class=MediaClass.APP,
191  media_content_id=uri,
192  media_content_type=MediaType.APP,
193  can_play=False,
194  can_expand=False,
195  thumbnail=self.get_browse_image_urlget_browse_image_url(
196  MediaType.APP, uri, media_image_id=None
197  ),
198  )
199  for uri, item in self.coordinator.source_map.items()
200  if item["type"] == SourceType.APP
201  ]
202  else:
203  children = None
204 
205  return BrowseMedia(
206  title="Applications",
207  media_class=MediaClass.DIRECTORY,
208  media_content_id="apps",
209  media_content_type=MediaType.APPS,
210  children_media_class=MediaClass.APP,
211  can_play=False,
212  can_expand=True,
213  children=children,
214  )
215 
216  async def async_browse_media_channels(self, expanded: bool = False) -> BrowseMedia:
217  """Return channels media objects."""
218  if expanded:
219  children = [
220  BrowseMedia(
221  title=item["title"],
222  media_class=MediaClass.CHANNEL,
223  media_content_id=uri,
224  media_content_type=MediaType.CHANNEL,
225  can_play=False,
226  can_expand=False,
227  )
228  for uri, item in self.coordinator.source_map.items()
229  if item["type"] == SourceType.CHANNEL
230  ]
231  else:
232  children = None
233 
234  return BrowseMedia(
235  title="Channels",
236  media_class=MediaClass.DIRECTORY,
237  media_content_id="channels",
238  media_content_type=MediaType.CHANNELS,
239  children_media_class=MediaClass.CHANNEL,
240  can_play=False,
241  can_expand=True,
242  children=children,
243  )
244 
246  self,
247  media_content_type: MediaType | str,
248  media_content_id: str,
249  media_image_id: str | None = None,
250  ) -> tuple[bytes | None, str | None]:
251  """Serve album art. Returns (content, content_type)."""
252  if media_content_type == MediaType.APP and media_content_id:
253  if icon := self.coordinator.source_map[media_content_id].get("icon"):
254  (content, content_type) = await self._async_fetch_image_async_fetch_image(icon)
255  if content_type:
256  # Fix invalid Content-Type header returned by Bravia
257  content_type = content_type.replace("Content-Type: ", "")
258  return (content, content_type)
259  return None, None
260 
261  async def async_play_media(
262  self, media_type: MediaType | str, media_id: str, **kwargs: Any
263  ) -> None:
264  """Play a piece of media."""
265  await self.coordinator.async_play_media(media_type, media_id, **kwargs)
266 
267  async def async_select_source(self, source: str) -> None:
268  """Set the input source."""
269  await self.coordinator.async_select_source(source)
270 
271  async def async_media_play(self) -> None:
272  """Send play command."""
273  await self.coordinator.async_media_play()
274 
275  async def async_media_pause(self) -> None:
276  """Send pause command."""
277  await self.coordinator.async_media_pause()
278 
279  async def async_media_play_pause(self) -> None:
280  """Send pause command that toggle play/pause."""
281  await self.coordinator.async_media_pause()
282 
283  async def async_media_stop(self) -> None:
284  """Send media stop command to media player."""
285  await self.coordinator.async_media_stop()
286 
287  async def async_media_next_track(self) -> None:
288  """Send next track command."""
289  await self.coordinator.async_media_next_track()
290 
291  async def async_media_previous_track(self) -> None:
292  """Send previous track command."""
293  await self.coordinator.async_media_previous_track()
None async_play_media(self, MediaType|str media_type, str media_id, **Any kwargs)
BrowseMedia async_browse_media(self, MediaType|str|None media_content_type=None, str|None media_content_id=None)
BrowseMedia async_browse_media_apps(self, bool expanded=False)
tuple[bytes|None, str|None] async_get_browse_image(self, MediaType|str media_content_type, str media_content_id, str|None media_image_id=None)
BrowseMedia async_browse_media_channels(self, bool expanded=False)
str get_browse_image_url(self, str media_content_type, str media_content_id, str|None media_image_id=None)
Definition: __init__.py:1199
tuple[bytes|None, str|None] _async_fetch_image(self, str url)
Definition: __init__.py:1190
None async_setup_entry(HomeAssistant hass, BraviaTVConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: media_player.py:30
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88