Skip to content

Commit

Permalink
Add support for launching apps
Browse files Browse the repository at this point in the history
  • Loading branch information
postlund committed Dec 6, 2021
1 parent 50d2c3d commit 084e113
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 2 deletions.
50 changes: 50 additions & 0 deletions custom_components/apple_tv/browse_media.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""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."""
title = None
media = None
children_media_class = None

title = "Apps"
media = [
{"app_id": app_id, "title": app_name, "type": MEDIA_TYPE_APP}
for app_name, app_id in app_list.items()
]
children_media_class = MEDIA_CLASS_APP

return BrowseMedia(
media_class=MEDIA_CLASS_DIRECTORY,
media_content_id=None,
media_content_type=MEDIA_TYPE_APPS,
title=title,
can_play=True,
can_expand=False,
children=[item_payload(item) for item in media],
children_media_class=children_media_class,
)


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,
)
44 changes: 42 additions & 2 deletions custom_components/apple_tv/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,24 @@
)
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,
SUPPORT_PLAY_MEDIA,
SUPPORT_PREVIOUS_TRACK,
SUPPORT_REPEAT_SET,
SUPPORT_SEEK,
SUPPORT_SELECT_SOURCE,
SUPPORT_SHUFFLE_SET,
SUPPORT_STOP,
SUPPORT_TURN_OFF,
Expand All @@ -46,6 +49,7 @@
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__)
Expand All @@ -60,6 +64,7 @@
# of these).
SUPPORT_APPLE_TV = (
SUPPORT_BASE
| SUPPORT_BROWSE_MEDIA
| SUPPORT_PLAY_MEDIA
| SUPPORT_PAUSE
| SUPPORT_PLAY
Expand Down Expand Up @@ -89,6 +94,8 @@
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,
}


Expand All @@ -108,6 +115,7 @@ def __init__(self, name, identifier, manager, **kwargs):
"""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):
Expand Down Expand Up @@ -135,6 +143,18 @@ def async_device_connected(self, atv):
# 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()
self._app_list = {app.name: app.identifier for app in apps}
self.async_write_ha_state()
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Failed to update app list")

@callback
def async_device_disconnected(self):
"""Handle when connection was lost to device."""
Expand Down Expand Up @@ -198,6 +218,11 @@ def app_name(self):
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."""
Expand Down Expand Up @@ -248,7 +273,9 @@ async def async_play_media(self, media_type, media_id, **kwargs):
"""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)
Expand Down Expand Up @@ -346,6 +373,14 @@ def _is_feature_available(self, feature):
return self.atv.features.in_state(FeatureState.Available, feature)
return False

async def async_browse_media(
self,
media_content_type=None, # : str | None = None,
media_content_id=None # : str | None = 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):
Expand Down Expand Up @@ -425,3 +460,8 @@ async def async_set_shuffle(self, shuffle):
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)

0 comments on commit 084e113

Please sign in to comment.