diff --git a/homeassistant/components/roku/browse_media.py b/homeassistant/components/roku/browse_media.py new file mode 100644 index 00000000000..809c6ac3578 --- /dev/null +++ b/homeassistant/components/roku/browse_media.py @@ -0,0 +1,142 @@ +"""Support for media browsing.""" + +from homeassistant.components.media_player import BrowseMedia +from homeassistant.components.media_player.const import ( + MEDIA_CLASS_APP, + MEDIA_CLASS_CHANNEL, + MEDIA_CLASS_DIRECTORY, + MEDIA_TYPE_APP, + MEDIA_TYPE_APPS, + MEDIA_TYPE_CHANNEL, + MEDIA_TYPE_CHANNELS, +) + +CONTENT_TYPE_MEDIA_CLASS = { + MEDIA_TYPE_APP: MEDIA_CLASS_APP, + MEDIA_TYPE_APPS: MEDIA_CLASS_APP, + MEDIA_TYPE_CHANNEL: MEDIA_CLASS_CHANNEL, + MEDIA_TYPE_CHANNELS: MEDIA_CLASS_CHANNEL, +} + +PLAYABLE_MEDIA_TYPES = [ + MEDIA_TYPE_APP, + MEDIA_TYPE_CHANNEL, +] + +EXPANDABLE_MEDIA_TYPES = [ + MEDIA_TYPE_APPS, + MEDIA_TYPE_CHANNELS, +] + + +def build_item_response(coordinator, payload): + """Create response payload for the provided media query.""" + search_id = payload["search_id"] + search_type = payload["search_type"] + + thumbnail = None + title = None + media = None + + if search_type == MEDIA_TYPE_APPS: + title = "Apps" + media = [ + {"app_id": item.app_id, "title": item.name, "type": MEDIA_TYPE_APP} + for item in coordinator.data.apps + ] + elif search_type == MEDIA_TYPE_CHANNELS: + title = "Channels" + media = [ + { + "channel_number": item.number, + "title": item.name, + "type": MEDIA_TYPE_CHANNEL, + } + for item in coordinator.data.channels + ] + + if media is None: + return None + + return BrowseMedia( + media_class=CONTENT_TYPE_MEDIA_CLASS[search_type], + media_content_id=search_id, + media_content_type=search_type, + title=title, + can_play=search_type in PLAYABLE_MEDIA_TYPES and search_id, + can_expand=True, + children=[item_payload(item, coordinator) for item in media], + thumbnail=thumbnail, + ) + + +def item_payload(item, coordinator): + """ + Create response payload for a single media item. + + Used by async_browse_media. + """ + thumbnail = None + + if "app_id" in item: + media_content_type = MEDIA_TYPE_APP + media_content_id = item["app_id"] + thumbnail = coordinator.roku.app_icon_url(item["app_id"]) + elif "channel_number" in item: + media_content_type = MEDIA_TYPE_CHANNEL + media_content_id = item["channel_number"] + else: + media_content_type = item["type"] + media_content_id = "" + + title = item["title"] + can_play = media_content_type in PLAYABLE_MEDIA_TYPES and media_content_id + can_expand = media_content_type in EXPANDABLE_MEDIA_TYPES + + return BrowseMedia( + title=title, + media_class=CONTENT_TYPE_MEDIA_CLASS[media_content_type], + media_content_type=media_content_type, + media_content_id=media_content_id, + can_play=can_play, + can_expand=can_expand, + thumbnail=thumbnail, + ) + + +def library_payload(coordinator): + """ + Create response payload to describe contents of a specific library. + + Used by async_browse_media. + """ + library_info = BrowseMedia( + media_class=MEDIA_CLASS_DIRECTORY, + media_content_id="library", + media_content_type="library", + title="Media Library", + can_play=False, + can_expand=True, + children=[], + ) + + library = { + MEDIA_TYPE_APPS: "Apps", + MEDIA_TYPE_CHANNELS: "Channels", + } + + for item in [{"title": name, "type": type_} for type_, name in library.items()]: + if ( + item["type"] == MEDIA_TYPE_CHANNELS + and coordinator.data.info.device_type != "tv" + ): + continue + + library_info.children.append( + item_payload( + {"title": item["title"], "type": item["type"]}, + coordinator, + ) + ) + + return library_info diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py index 3438b8c7add..0e035106824 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -7,17 +7,11 @@ import voluptuous as vol from homeassistant.components.media_player import ( DEVICE_CLASS_RECEIVER, DEVICE_CLASS_TV, - BrowseMedia, MediaPlayerEntity, ) from homeassistant.components.media_player.const import ( - MEDIA_CLASS_APP, - MEDIA_CLASS_CHANNEL, - MEDIA_CLASS_DIRECTORY, MEDIA_TYPE_APP, - MEDIA_TYPE_APPS, MEDIA_TYPE_CHANNEL, - MEDIA_TYPE_CHANNELS, SUPPORT_BROWSE_MEDIA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, @@ -42,6 +36,7 @@ from homeassistant.const import ( from homeassistant.helpers import entity_platform from . import RokuDataUpdateCoordinator, RokuEntity, roku_exception_handler +from .browse_media import build_item_response, library_payload from .const import ATTR_KEYWORD, DOMAIN, SERVICE_SEARCH _LOGGER = logging.getLogger(__name__) @@ -78,44 +73,6 @@ async def async_setup_entry(hass, entry, async_add_entities): ) -def browse_media_library(channels: bool = False) -> BrowseMedia: - """Create response payload to describe contents of a specific library.""" - library_info = BrowseMedia( - title="Media Library", - media_class=MEDIA_CLASS_DIRECTORY, - media_content_id="library", - media_content_type="library", - can_play=False, - can_expand=True, - children=[], - ) - - library_info.children.append( - BrowseMedia( - title="Apps", - media_class=MEDIA_CLASS_APP, - media_content_id="apps", - media_content_type=MEDIA_TYPE_APPS, - can_expand=True, - can_play=False, - ) - ) - - if channels: - library_info.children.append( - BrowseMedia( - title="Channels", - media_class=MEDIA_CLASS_CHANNEL, - media_content_id="channels", - media_content_type=MEDIA_TYPE_CHANNELS, - can_expand=True, - can_play=False, - ) - ) - - return library_info - - class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): """Representation of a Roku media player on the network.""" @@ -284,53 +241,13 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): async def async_browse_media(self, media_content_type=None, media_content_id=None): """Implement the websocket media browsing helper.""" if media_content_type in [None, "library"]: - is_tv = self.coordinator.data.info.device_type == "tv" - return browse_media_library(channels=is_tv) + return library_payload(self.coordinator) - response = None - - if media_content_type == MEDIA_TYPE_APPS: - response = BrowseMedia( - title="Apps", - media_class=MEDIA_CLASS_APP, - media_content_id="apps", - media_content_type=MEDIA_TYPE_APPS, - can_expand=True, - can_play=False, - children=[ - BrowseMedia( - title=app.name, - thumbnail=self.coordinator.roku.app_icon_url(app.app_id), - media_class=MEDIA_CLASS_APP, - media_content_id=app.app_id, - media_content_type=MEDIA_TYPE_APP, - can_play=True, - can_expand=False, - ) - for app in self.coordinator.data.apps - ], - ) - - if media_content_type == MEDIA_TYPE_CHANNELS: - response = BrowseMedia( - title="Channels", - media_class=MEDIA_CLASS_CHANNEL, - media_content_id="channels", - media_content_type=MEDIA_TYPE_CHANNELS, - can_expand=True, - can_play=False, - children=[ - BrowseMedia( - title=channel.name, - media_class=MEDIA_CLASS_CHANNEL, - media_content_id=channel.number, - media_content_type=MEDIA_TYPE_CHANNEL, - can_play=True, - can_expand=False, - ) - for channel in self.coordinator.data.channels - ], - ) + payload = { + "search_type": media_content_type, + "search_id": media_content_id, + } + response = build_item_response(self.coordinator, payload) if response is None: raise BrowseError(