Add support for app launching in Apple TV (#61732)
parent
82e280d2ac
commit
905295707d
|
@ -0,0 +1,44 @@
|
|||
"""Support for media browsing."""
|
||||
|
||||
from homeassistant.components.media_player import BrowseMedia
|
||||
from homeassistant.components.media_player.const import (
|
||||
MEDIA_CLASS_APP,
|
||||
MEDIA_CLASS_DIRECTORY,
|
||||
MEDIA_TYPE_APP,
|
||||
MEDIA_TYPE_APPS,
|
||||
)
|
||||
|
||||
|
||||
def build_app_list(app_list):
|
||||
"""Create response payload for app list."""
|
||||
app_list = [
|
||||
{"app_id": app_id, "title": app_name, "type": MEDIA_TYPE_APP}
|
||||
for app_name, app_id in app_list.items()
|
||||
]
|
||||
|
||||
return BrowseMedia(
|
||||
media_class=MEDIA_CLASS_DIRECTORY,
|
||||
media_content_id=None,
|
||||
media_content_type=MEDIA_TYPE_APPS,
|
||||
title="Apps",
|
||||
can_play=True,
|
||||
can_expand=False,
|
||||
children=[item_payload(item) for item in app_list],
|
||||
children_media_class=MEDIA_CLASS_APP,
|
||||
)
|
||||
|
||||
|
||||
def item_payload(item):
|
||||
"""
|
||||
Create response payload for a single media item.
|
||||
|
||||
Used by async_browse_media.
|
||||
"""
|
||||
return BrowseMedia(
|
||||
title=item["title"],
|
||||
media_class=MEDIA_CLASS_APP,
|
||||
media_content_type=MEDIA_TYPE_APP,
|
||||
media_content_id=item["app_id"],
|
||||
can_play=False,
|
||||
can_expand=False,
|
||||
)
|
|
@ -1,6 +1,7 @@
|
|||
"""Support for Apple TV media player."""
|
||||
import logging
|
||||
|
||||
from pyatv import exceptions
|
||||
from pyatv.const import (
|
||||
DeviceState,
|
||||
FeatureName,
|
||||
|
@ -12,14 +13,16 @@ from pyatv.const import (
|
|||
)
|
||||
from pyatv.helpers import is_streamable
|
||||
|
||||
from homeassistant.components.media_player import MediaPlayerEntity
|
||||
from homeassistant.components.media_player import BrowseMedia, MediaPlayerEntity
|
||||
from homeassistant.components.media_player.const import (
|
||||
MEDIA_TYPE_APP,
|
||||
MEDIA_TYPE_MUSIC,
|
||||
MEDIA_TYPE_TVSHOW,
|
||||
MEDIA_TYPE_VIDEO,
|
||||
REPEAT_MODE_ALL,
|
||||
REPEAT_MODE_OFF,
|
||||
REPEAT_MODE_ONE,
|
||||
SUPPORT_BROWSE_MEDIA,
|
||||
SUPPORT_NEXT_TRACK,
|
||||
SUPPORT_PAUSE,
|
||||
SUPPORT_PLAY,
|
||||
|
@ -27,6 +30,7 @@ from homeassistant.components.media_player.const import (
|
|||
SUPPORT_PREVIOUS_TRACK,
|
||||
SUPPORT_REPEAT_SET,
|
||||
SUPPORT_SEEK,
|
||||
SUPPORT_SELECT_SOURCE,
|
||||
SUPPORT_SHUFFLE_SET,
|
||||
SUPPORT_STOP,
|
||||
SUPPORT_TURN_OFF,
|
||||
|
@ -46,6 +50,7 @@ from homeassistant.core import callback
|
|||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from . import AppleTVEntity
|
||||
from .browse_media import build_app_list
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -60,6 +65,7 @@ SUPPORT_BASE = SUPPORT_TURN_ON | SUPPORT_TURN_OFF
|
|||
# of these).
|
||||
SUPPORT_APPLE_TV = (
|
||||
SUPPORT_BASE
|
||||
| SUPPORT_BROWSE_MEDIA
|
||||
| SUPPORT_PLAY_MEDIA
|
||||
| SUPPORT_PAUSE
|
||||
| SUPPORT_PLAY
|
||||
|
@ -89,6 +95,8 @@ SUPPORT_FEATURE_MAPPING = {
|
|||
FeatureName.SetRepeat: SUPPORT_REPEAT_SET,
|
||||
FeatureName.SetShuffle: SUPPORT_SHUFFLE_SET,
|
||||
FeatureName.SetVolume: SUPPORT_VOLUME_SET,
|
||||
FeatureName.AppList: SUPPORT_BROWSE_MEDIA | SUPPORT_SELECT_SOURCE,
|
||||
FeatureName.LaunchApp: SUPPORT_BROWSE_MEDIA | SUPPORT_SELECT_SOURCE,
|
||||
}
|
||||
|
||||
|
||||
|
@ -108,6 +116,7 @@ class AppleTvMediaPlayer(AppleTVEntity, MediaPlayerEntity):
|
|||
"""Initialize the Apple TV media player."""
|
||||
super().__init__(name, identifier, manager, **kwargs)
|
||||
self._playing = None
|
||||
self._app_list = {}
|
||||
|
||||
@callback
|
||||
def async_device_connected(self, atv):
|
||||
|
@ -135,6 +144,21 @@ class AppleTvMediaPlayer(AppleTVEntity, MediaPlayerEntity):
|
|||
# Listen to power updates
|
||||
self.atv.power.listener = self
|
||||
|
||||
if self.atv.features.in_state(FeatureState.Available, FeatureName.AppList):
|
||||
self.hass.create_task(self._update_app_list())
|
||||
|
||||
async def _update_app_list(self):
|
||||
_LOGGER.debug("Updating app list")
|
||||
try:
|
||||
apps = await self.atv.apps.app_list()
|
||||
except exceptions.NotSupportedError:
|
||||
_LOGGER.error("Listing apps is not supported")
|
||||
except exceptions.ProtocolError:
|
||||
_LOGGER.exception("Failed to update app list")
|
||||
else:
|
||||
self._app_list = {app.name: app.identifier for app in apps}
|
||||
self.async_write_ha_state()
|
||||
|
||||
@callback
|
||||
def async_device_disconnected(self):
|
||||
"""Handle when connection was lost to device."""
|
||||
|
@ -198,6 +222,11 @@ class AppleTvMediaPlayer(AppleTVEntity, MediaPlayerEntity):
|
|||
return self.atv.metadata.app.name
|
||||
return None
|
||||
|
||||
@property
|
||||
def source_list(self):
|
||||
"""List of available input sources."""
|
||||
return list(self._app_list.keys())
|
||||
|
||||
@property
|
||||
def media_content_type(self):
|
||||
"""Content type of current playing media."""
|
||||
|
@ -248,7 +277,9 @@ class AppleTvMediaPlayer(AppleTVEntity, MediaPlayerEntity):
|
|||
"""Send the play_media command to the media player."""
|
||||
# If input (file) has a file format supported by pyatv, then stream it with
|
||||
# RAOP. Otherwise try to play it with regular AirPlay.
|
||||
if self._is_feature_available(FeatureName.StreamFile) and (
|
||||
if media_type == MEDIA_TYPE_APP:
|
||||
await self.atv.apps.launch_app(media_id)
|
||||
elif self._is_feature_available(FeatureName.StreamFile) and (
|
||||
await is_streamable(media_id) or media_type == MEDIA_TYPE_MUSIC
|
||||
):
|
||||
_LOGGER.debug("Streaming %s via RAOP", media_id)
|
||||
|
@ -346,6 +377,14 @@ class AppleTvMediaPlayer(AppleTVEntity, MediaPlayerEntity):
|
|||
return self.atv.features.in_state(FeatureState.Available, feature)
|
||||
return False
|
||||
|
||||
async def async_browse_media(
|
||||
self,
|
||||
media_content_type=None,
|
||||
media_content_id=None,
|
||||
) -> BrowseMedia:
|
||||
"""Implement the websocket media browsing helper."""
|
||||
return build_app_list(self._app_list)
|
||||
|
||||
async def async_turn_on(self):
|
||||
"""Turn the media player on."""
|
||||
if self._is_feature_available(FeatureName.TurnOn):
|
||||
|
@ -425,3 +464,8 @@ class AppleTvMediaPlayer(AppleTVEntity, MediaPlayerEntity):
|
|||
await self.atv.remote_control.set_shuffle(
|
||||
ShuffleState.Songs if shuffle else ShuffleState.Off
|
||||
)
|
||||
|
||||
async def async_select_source(self, source: str) -> None:
|
||||
"""Select input source."""
|
||||
if app_id := self._app_list.get(source):
|
||||
await self.atv.apps.launch_app(app_id)
|
||||
|
|
Loading…
Reference in New Issue