Home Assistant Unofficial Reference 2024.12.1
media_player.py
Go to the documentation of this file.
1 """Media player support for Android TV Remote."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from typing import Any
7 
8 from androidtvremote2 import AndroidTVRemote, ConnectionClosed
9 
11  BrowseMedia,
12  MediaClass,
13  MediaPlayerDeviceClass,
14  MediaPlayerEntity,
15  MediaPlayerEntityFeature,
16  MediaPlayerState,
17  MediaType,
18 )
19 from homeassistant.core import HomeAssistant, callback
20 from homeassistant.exceptions import HomeAssistantError
21 from homeassistant.helpers.entity_platform import AddEntitiesCallback
22 
23 from . import AndroidTVRemoteConfigEntry
24 from .const import CONF_APP_ICON, CONF_APP_NAME
25 from .entity import AndroidTVRemoteBaseEntity
26 
27 PARALLEL_UPDATES = 0
28 
29 
31  hass: HomeAssistant,
32  config_entry: AndroidTVRemoteConfigEntry,
33  async_add_entities: AddEntitiesCallback,
34 ) -> None:
35  """Set up the Android TV media player entity based on a config entry."""
36  api = config_entry.runtime_data
38 
39 
41  """Android TV Remote Media Player Entity."""
42 
43  _attr_assumed_state = True
44  _attr_device_class = MediaPlayerDeviceClass.TV
45  _attr_supported_features = (
46  MediaPlayerEntityFeature.PAUSE
47  | MediaPlayerEntityFeature.VOLUME_STEP
48  | MediaPlayerEntityFeature.VOLUME_MUTE
49  | MediaPlayerEntityFeature.PREVIOUS_TRACK
50  | MediaPlayerEntityFeature.NEXT_TRACK
51  | MediaPlayerEntityFeature.TURN_ON
52  | MediaPlayerEntityFeature.TURN_OFF
53  | MediaPlayerEntityFeature.PLAY
54  | MediaPlayerEntityFeature.STOP
55  | MediaPlayerEntityFeature.PLAY_MEDIA
56  | MediaPlayerEntityFeature.BROWSE_MEDIA
57  )
58 
59  def __init__(
60  self, api: AndroidTVRemote, config_entry: AndroidTVRemoteConfigEntry
61  ) -> None:
62  """Initialize the entity."""
63  super().__init__(api, config_entry)
64 
65  # This task is needed to create a job that sends a key press
66  # sequence that can be canceled if concurrency occurs
67  self._channel_set_task_channel_set_task: asyncio.Task | None = None
68 
69  def _update_current_app(self, current_app: str) -> None:
70  """Update current app info."""
71  self._attr_app_id_attr_app_id = current_app
72  self._attr_app_name_attr_app_name = (
73  self._apps[current_app].get(CONF_APP_NAME, current_app)
74  if current_app in self._apps
75  else current_app
76  )
77 
78  def _update_volume_info(self, volume_info: dict[str, str | bool]) -> None:
79  """Update volume info."""
80  if volume_info.get("max"):
81  self._attr_volume_level_attr_volume_level = int(volume_info["level"]) / int(
82  volume_info["max"]
83  )
84  self._attr_is_volume_muted_attr_is_volume_muted = bool(volume_info["muted"])
85  else:
86  self._attr_volume_level_attr_volume_level = None
87  self._attr_is_volume_muted_attr_is_volume_muted = None
88 
89  @callback
90  def _current_app_updated(self, current_app: str) -> None:
91  """Update the state when the current app changes."""
92  self._update_current_app_update_current_app(current_app)
93  self.async_write_ha_stateasync_write_ha_state()
94 
95  @callback
96  def _volume_info_updated(self, volume_info: dict[str, str | bool]) -> None:
97  """Update the state when the volume info changes."""
98  self._update_volume_info_update_volume_info(volume_info)
99  self.async_write_ha_stateasync_write_ha_state()
100 
101  async def async_added_to_hass(self) -> None:
102  """Register callbacks."""
103  await super().async_added_to_hass()
104 
105  self._update_current_app_update_current_app(self._api_api.current_app)
106  self._update_volume_info_update_volume_info(self._api_api.volume_info)
107 
108  self._api_api.add_current_app_updated_callback(self._current_app_updated_current_app_updated)
109  self._api_api.add_volume_info_updated_callback(self._volume_info_updated_volume_info_updated)
110 
111  async def async_will_remove_from_hass(self) -> None:
112  """Remove callbacks."""
113  await super().async_will_remove_from_hass()
114 
115  self._api_api.remove_current_app_updated_callback(self._current_app_updated_current_app_updated)
116  self._api_api.remove_volume_info_updated_callback(self._volume_info_updated_volume_info_updated)
117 
118  @property
119  def state(self) -> MediaPlayerState:
120  """Return the state of the device."""
121  if self._attr_is_on_attr_is_on:
122  return MediaPlayerState.ON
123  return MediaPlayerState.OFF
124 
125  async def async_turn_on(self) -> None:
126  """Turn the Android TV on."""
127  if not self._attr_is_on_attr_is_on:
128  self._send_key_command_send_key_command("POWER")
129 
130  async def async_turn_off(self) -> None:
131  """Turn the Android TV off."""
132  if self._attr_is_on_attr_is_on:
133  self._send_key_command_send_key_command("POWER")
134 
135  async def async_volume_up(self) -> None:
136  """Turn volume up for media player."""
137  self._send_key_command_send_key_command("VOLUME_UP")
138 
139  async def async_volume_down(self) -> None:
140  """Turn volume down for media player."""
141  self._send_key_command_send_key_command("VOLUME_DOWN")
142 
143  async def async_mute_volume(self, mute: bool) -> None:
144  """Mute the volume."""
145  if mute != self.is_volume_mutedis_volume_muted:
146  self._send_key_command_send_key_command("VOLUME_MUTE")
147 
148  async def async_media_play(self) -> None:
149  """Send play command."""
150  self._send_key_command_send_key_command("MEDIA_PLAY")
151 
152  async def async_media_pause(self) -> None:
153  """Send pause command."""
154  self._send_key_command_send_key_command("MEDIA_PAUSE")
155 
156  async def async_media_play_pause(self) -> None:
157  """Send play/pause command."""
158  self._send_key_command_send_key_command("MEDIA_PLAY_PAUSE")
159 
160  async def async_media_stop(self) -> None:
161  """Send stop command."""
162  self._send_key_command_send_key_command("MEDIA_STOP")
163 
164  async def async_media_previous_track(self) -> None:
165  """Send previous track command."""
166  self._send_key_command_send_key_command("MEDIA_PREVIOUS")
167 
168  async def async_media_next_track(self) -> None:
169  """Send next track command."""
170  self._send_key_command_send_key_command("MEDIA_NEXT")
171 
172  async def async_play_media(
173  self, media_type: MediaType | str, media_id: str, **kwargs: Any
174  ) -> None:
175  """Play a piece of media."""
176  if media_type == MediaType.CHANNEL:
177  if not media_id.isnumeric():
178  raise ValueError(f"Channel must be numeric: {media_id}")
179  if self._channel_set_task_channel_set_task:
180  self._channel_set_task_channel_set_task.cancel()
181  self._channel_set_task_channel_set_task = asyncio.create_task(
182  self._send_key_commands_send_key_commands(list(media_id))
183  )
184  await self._channel_set_task_channel_set_task
185  return
186 
187  if media_type in [MediaType.URL, MediaType.APP]:
188  self._send_launch_app_command_send_launch_app_command(media_id)
189  return
190 
191  raise ValueError(f"Invalid media type: {media_type}")
192 
194  self,
195  media_content_type: MediaType | str | None = None,
196  media_content_id: str | None = None,
197  ) -> BrowseMedia:
198  """Browse apps."""
199  children = [
200  BrowseMedia(
201  media_class=MediaClass.APP,
202  media_content_type=MediaType.APP,
203  media_content_id=app_id,
204  title=app.get(CONF_APP_NAME, ""),
205  thumbnail=app.get(CONF_APP_ICON, ""),
206  can_play=False,
207  can_expand=False,
208  )
209  for app_id, app in self._apps.items()
210  ]
211  return BrowseMedia(
212  title="Applications",
213  media_class=MediaClass.DIRECTORY,
214  media_content_id="apps",
215  media_content_type=MediaType.APPS,
216  children_media_class=MediaClass.APP,
217  can_play=False,
218  can_expand=True,
219  children=children,
220  )
221 
223  self, key_codes: list[str], delay_secs: float = 0.1
224  ) -> None:
225  """Send a key press sequence to Android TV.
226 
227  The delay is necessary because device may ignore
228  some commands if we send the sequence without delay.
229  """
230  try:
231  for key_code in key_codes:
232  self._api_api.send_key_command(key_code)
233  await asyncio.sleep(delay_secs)
234  except ConnectionClosed as exc:
235  raise HomeAssistantError(
236  "Connection to Android TV device is closed"
237  ) from exc
None _send_key_command(self, str key_code, str direction="SHORT")
Definition: entity.py:67
None _send_key_commands(self, list[str] key_codes, float delay_secs=0.1)
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)
None __init__(self, AndroidTVRemote api, AndroidTVRemoteConfigEntry config_entry)
Definition: media_player.py:61
None async_setup_entry(HomeAssistant hass, AndroidTVRemoteConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: media_player.py:34
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88