1 """Media Player component to integrate TVs exposing the Joint Space API."""
3 from __future__
import annotations
7 from haphilipsjs
import ConnectionFailure
13 MediaPlayerDeviceClass,
15 MediaPlayerEntityFeature,
24 from .
import LOGGER
as _LOGGER, PhilipsTVConfigEntry
25 from .coordinator
import PhilipsTVDataUpdateCoordinator
26 from .entity
import PhilipsJsEntity
27 from .helpers
import async_get_turn_on_trigger
29 SUPPORT_PHILIPS_JS = (
30 MediaPlayerEntityFeature.TURN_OFF
31 | MediaPlayerEntityFeature.VOLUME_STEP
32 | MediaPlayerEntityFeature.VOLUME_SET
33 | MediaPlayerEntityFeature.VOLUME_MUTE
34 | MediaPlayerEntityFeature.SELECT_SOURCE
35 | MediaPlayerEntityFeature.NEXT_TRACK
36 | MediaPlayerEntityFeature.PREVIOUS_TRACK
37 | MediaPlayerEntityFeature.PLAY_MEDIA
38 | MediaPlayerEntityFeature.BROWSE_MEDIA
39 | MediaPlayerEntityFeature.PLAY
40 | MediaPlayerEntityFeature.PAUSE
41 | MediaPlayerEntityFeature.STOP
46 return {v: k
for k, v
in data.items()}
51 config_entry: PhilipsTVConfigEntry,
52 async_add_entities: AddEntitiesCallback,
54 """Set up the configuration entry."""
55 coordinator = config_entry.runtime_data
66 """Representation of a Philips TV exposing the JointSpace API."""
68 _attr_device_class = MediaPlayerDeviceClass.TV
73 coordinator: PhilipsTVDataUpdateCoordinator,
75 """Initialize the Philips TV."""
76 self.
_tv_tv = coordinator.api
77 self.
_sources_sources: dict[str, str] = {}
86 """Handle being added to hass."""
89 if (entry := self.
registry_entryregistry_entry)
and entry.device_id:
97 """Reschedule update task."""
103 """Flag media player features that are supported."""
104 supports = SUPPORT_PHILIPS_JS
105 if self.
_turn_on_turn_on
or (self.
_tv_tv.on
and self.
_tv_tv.powerstate
is not None):
106 supports |= MediaPlayerEntityFeature.TURN_ON
110 """Set the input source."""
112 await self.
_tv_tv.setSource(source_id)
116 """Turn on the device."""
117 if self.
_tv_tv.on
and self.
_tv_tv.powerstate:
118 await self.
_tv_tv.setPowerState(
"On")
125 """Turn off the device."""
126 if self.
_attr_state_attr_state == MediaPlayerState.ON:
127 await self.
_tv_tv.sendKey(
"Standby")
131 _LOGGER.debug(
"Ignoring turn off when already in expected state")
134 """Send volume up command."""
135 await self.
_tv_tv.sendKey(
"VolumeUp")
139 """Send volume down command."""
140 await self.
_tv_tv.sendKey(
"VolumeDown")
144 """Send mute command."""
145 if self.
_tv_tv.muted != mute:
146 await self.
_tv_tv.sendKey(
"Mute")
149 _LOGGER.debug(
"Ignoring request when already in expected state")
152 """Set volume level, range 0..1."""
153 await self.
_tv_tv.setVolume(volume, self.
_tv_tv.muted)
157 """Send rewind command."""
158 if self.
_tv_tv.channel_active:
159 await self.
_tv_tv.sendKey(
"ChannelStepDown")
161 await self.
_tv_tv.sendKey(
"Previous")
165 """Send fast forward command."""
166 if self.
_tv_tv.channel_active:
167 await self.
_tv_tv.sendKey(
"ChannelStepUp")
169 await self.
_tv_tv.sendKey(
"Next")
173 """Send pause command to media player."""
174 if self.
_tv_tv.quirk_playpause_spacebar:
175 await self.
_tv_tv.sendKey(
"Confirm")
177 await self.
_tv_tv.sendKey(
"PlayPause")
181 """Send pause command to media player."""
182 await self.
_tv_tv.sendKey(
"Play")
186 """Send play command to media player."""
187 await self.
_tv_tv.sendKey(
"Pause")
191 """Send play command to media player."""
192 await self.
_tv_tv.sendKey(
"Stop")
197 """Image url of current playing media."""
210 """Play a channel."""
211 list_id, _, channel_id = media_id.partition(
"/")
213 await self.
_tv_tv.setChannel(channel_id, list_id)
217 for channel
in self.
_tv_tv.channels_current:
218 if channel.get(
"preset") == media_id:
219 await self.
_tv_tv.setChannel(channel[
"ccid"], self.
_tv_tv.channel_list_id)
226 self, media_type: MediaType | str, media_id: str, **kwargs: Any
228 """Play a piece of media."""
229 _LOGGER.debug(
"Call play media type <%s>, Id <%s>", media_type, media_id)
231 if media_type == MediaType.CHANNEL:
233 elif media_type == MediaType.APP:
234 if app := self.
_tv_tv.applications.get(media_id):
235 await self.
_tv_tv.setApplication(app[
"intent"])
243 """Return channel media objects."""
247 title=channel.get(
"name", f
"Channel: {channel['ccid']}"),
248 media_class=MediaClass.CHANNEL,
249 media_content_id=f
"{self._tv.channel_list_id}/{channel['ccid']}",
250 media_content_type=MediaType.CHANNEL,
254 for channel
in self.
_tv_tv.channels_current
261 media_class=MediaClass.DIRECTORY,
262 media_content_id=
"channels",
263 media_content_type=MediaType.CHANNELS,
264 children_media_class=MediaClass.CHANNEL,
271 self, list_id: str, expanded: bool
273 """Return channel media objects."""
275 favorites = self.
_tv_tv.favorite_lists.get(list_id)
279 channel_data = self.
_tv_tv.channels.get(
str(channel[
"ccid"]))
281 return channel_data[
"name"]
282 return f
"Channel: {channel['ccid']}"
287 media_class=MediaClass.CHANNEL,
288 media_content_id=f
"{list_id}/{channel['ccid']}",
289 media_content_type=MediaType.CHANNEL,
293 for channel
in favorites.get(
"channels", [])
300 favorite = self.
_tv_tv.favorite_lists[list_id]
302 title=favorite.get(
"name", f
"Favorites {list_id}"),
303 media_class=MediaClass.DIRECTORY,
304 media_content_id=f
"favorites/{list_id}",
305 media_content_type=MediaType.CHANNELS,
306 children_media_class=MediaClass.CHANNEL,
313 """Return application media objects."""
317 title=application[
"label"],
318 media_class=MediaClass.APP,
319 media_content_id=application_id,
320 media_content_type=MediaType.APP,
324 MediaType.APP, application_id, media_image_id=
None
327 for application_id, application
in self.
_tv_tv.applications.items()
333 title=
"Applications",
334 media_class=MediaClass.DIRECTORY,
335 media_content_id=
"applications",
336 media_content_type=MediaType.APPS,
337 children_media_class=MediaClass.APP,
344 """Return favorite media objects."""
345 if self.
_tv_tv.favorite_lists
and expanded:
348 for list_id
in self.
_tv_tv.favorite_lists
355 media_class=MediaClass.DIRECTORY,
356 media_content_id=
"favorite_lists",
357 media_content_type=MediaType.CHANNELS,
358 children_media_class=MediaClass.CHANNEL,
365 """Return root media objects."""
369 media_class=MediaClass.DIRECTORY,
371 media_content_type=
"",
383 media_content_type: MediaType | str |
None =
None,
384 media_content_id: str |
None =
None,
386 """Implement the websocket media browsing helper."""
387 if not self.
_tv_tv.on:
388 raise BrowseError(
"Can't browse when tv is turned off")
390 if media_content_id
is None or media_content_id ==
"":
392 path = media_content_id.partition(
"/")
393 if path[0] ==
"channels":
395 if path[0] ==
"applications":
397 if path[0] ==
"favorite_lists":
399 if path[0] ==
"favorites":
402 raise BrowseError(f
"Media not found: {media_content_type} / {media_content_id}")
406 media_content_type: MediaType | str,
407 media_content_id: str,
408 media_image_id: str |
None =
None,
409 ) -> tuple[bytes |
None, str |
None]:
410 """Serve album art. Returns (content, content_type)."""
412 if media_content_type == MediaType.APP
and media_content_id:
413 return await self.
_tv_tv.getApplicationIcon(media_content_id)
414 if media_content_type == MediaType.CHANNEL
and media_content_id:
415 return await self.
_tv_tv.getChannelLogo(media_content_id)
416 except ConnectionFailure:
417 _LOGGER.warning(
"Failed to fetch image")
421 """Serve album art. Returns (content, content_type)."""
431 if self.
_tv_tv.powerstate
in (
"Standby",
"StandbyKeep"):
439 srcid: source.get(
"name")
or f
"Source {srcid}"
440 for srcid, source
in (self.
_tv_tv.sources
or {}).items()
447 if app := self.
_tv_tv.applications.get(self.
_tv_tv.application_id):
455 if self.
_tv_tv.channel_active:
462 elif self.
_tv_tv.application_id:
466 self.
_tv_tv.application_id, {}
479 """Handle updated data from the coordinator."""
None async_write_ha_state(self)
None async_on_remove(self, CALLBACK_TYPE func)
None async_request_refresh(self)
None async_register(HomeAssistant hass, system_health.SystemHealthRegistration register)
str get_name(AirthingsDevice device)
web.Response get(self, web.Request request, str config_key)
dict[str, str] async_get_turn_on_trigger(str device_id)
def async_run(config_dir)