Add media browser support to roon media player (#42061)

This commit is contained in:
Greg Dowling 2020-10-22 13:54:55 +01:00 committed by GitHub
parent e7b6903ef4
commit 9a54c31c34
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 185 additions and 6 deletions

View file

@ -730,6 +730,7 @@ omit =
homeassistant/components/roomba/vacuum.py
homeassistant/components/roon/__init__.py
homeassistant/components/roon/const.py
homeassistant/components/roon/media_browser.py
homeassistant/components/roon/media_player.py
homeassistant/components/roon/server.py
homeassistant/components/route53/*

View file

@ -2,7 +2,7 @@
import asyncio
import logging
from roon import RoonApi
from roonapi import RoonApi
import voluptuous as vol
from homeassistant import config_entries, core, exceptions

View file

@ -4,7 +4,7 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/roon",
"requirements": [
"roonapi==0.0.21"
"roonapi==0.0.23"
],
"codeowners": [
"@pavoni"

View file

@ -0,0 +1,163 @@
"""Support to interface with the Roon API."""
import logging
from homeassistant.components.media_player import BrowseMedia
from homeassistant.components.media_player.const import (
MEDIA_CLASS_DIRECTORY,
MEDIA_CLASS_PLAYLIST,
MEDIA_CLASS_TRACK,
)
from homeassistant.components.media_player.errors import BrowseError
class UnknownMediaType(BrowseError):
"""Unknown media type."""
EXCLUDE_ITEMS = {
"Play Album",
"Play Artist",
"Play Playlist",
"Play Composer",
"Play Now",
"Play From Here",
"Queue",
"Start Radio",
"Add Next",
"Play Radio",
"Play Work",
"Settings",
"Search",
"Search Tidal",
"Search Qobuz",
}
# Maximum number of items to pull back from the API
ITEM_LIMIT = 3000
_LOGGER = logging.getLogger(__name__)
def browse_media(zone_id, roon_server, media_content_type=None, media_content_id=None):
"""Implement the websocket media browsing helper."""
try:
_LOGGER.debug("browse_media: %s: %s", media_content_type, media_content_id)
if media_content_type in [None, "library"]:
return library_payload(roon_server, zone_id, media_content_id)
except UnknownMediaType as err:
raise BrowseError(
f"Media not found: {media_content_type} / {media_content_id}"
) from err
def item_payload(roon_server, item, list_image_id):
"""Create response payload for a single media item."""
title = item.get("title")
subtitle = item.get("subtitle")
if subtitle is None:
display_title = title
else:
display_title = f"{title} ({subtitle})"
image_id = item.get("image_key") or list_image_id
image = None
if image_id:
image = roon_server.roonapi.get_image(image_id)
media_content_id = item.get("item_key")
media_content_type = "library"
hint = item.get("hint")
if hint == "list":
media_class = MEDIA_CLASS_DIRECTORY
can_expand = True
elif hint == "action_list":
media_class = MEDIA_CLASS_PLAYLIST
can_expand = False
elif hint == "action":
media_content_type = "track"
media_class = MEDIA_CLASS_TRACK
can_expand = False
else:
# Roon API says to treat unknown as a list
media_class = MEDIA_CLASS_DIRECTORY
can_expand = True
_LOGGER.warning("Unknown hint %s - %s", title, hint)
payload = {
"title": display_title,
"media_class": media_class,
"media_content_id": media_content_id,
"media_content_type": media_content_type,
"can_play": True,
"can_expand": can_expand,
"thumbnail": image,
}
return BrowseMedia(**payload)
def library_payload(roon_server, zone_id, media_content_id):
"""Create response payload for the library."""
opts = {
"hierarchy": "browse",
"zone_or_output_id": zone_id,
"count": ITEM_LIMIT,
}
# Roon starts browsing for a zone where it left off - so start from the top unless otherwise specified
if media_content_id is None or media_content_id == "Explore":
opts["pop_all"] = True
content_id = "Explore"
else:
opts["item_key"] = media_content_id
content_id = media_content_id
result_header = roon_server.roonapi.browse_browse(opts)
_LOGGER.debug("result_header %s", result_header)
header = result_header["list"]
title = header.get("title")
subtitle = header.get("subtitle")
if subtitle is None:
list_title = title
else:
list_title = f"{title} ({subtitle})"
total_count = header["count"]
library_image_id = header.get("image_key")
library_info = BrowseMedia(
title=list_title,
media_content_id=content_id,
media_content_type="library",
media_class=MEDIA_CLASS_DIRECTORY,
can_play=False,
can_expand=True,
children=[],
)
result_detail = roon_server.roonapi.browse_load(opts)
_LOGGER.debug("result_detail %s", result_detail)
items = result_detail.get("items")
count = len(items)
if count < total_count:
_LOGGER.debug(
"Exceeded limit of %d, loaded %d/%d", ITEM_LIMIT, count, total_count
)
for item in items:
if item.get("title") in EXCLUDE_ITEMS:
continue
entry = item_payload(roon_server, item, library_image_id)
library_info.children.append(entry)
return library_info

View file

@ -3,6 +3,7 @@ import logging
from homeassistant.components.media_player import MediaPlayerEntity
from homeassistant.components.media_player.const import (
SUPPORT_BROWSE_MEDIA,
SUPPORT_NEXT_TRACK,
SUPPORT_PAUSE,
SUPPORT_PLAY,
@ -33,9 +34,11 @@ from homeassistant.util import convert
from homeassistant.util.dt import utcnow
from .const import DOMAIN
from .media_browser import browse_media
SUPPORT_ROON = (
SUPPORT_PAUSE
SUPPORT_BROWSE_MEDIA
| SUPPORT_PAUSE
| SUPPORT_VOLUME_SET
| SUPPORT_STOP
| SUPPORT_PREVIOUS_TRACK
@ -466,9 +469,21 @@ class RoonDevice(MediaPlayerEntity):
self._server.roonapi.queue_playlist(self.zone_id, media_id)
elif media_type == "genre":
self._server.roonapi.play_genre(self.zone_id, media_id)
elif media_type in ("library", "track"):
self._server.roonapi.play_id(self.zone_id, media_id)
else:
_LOGGER.error(
"Playback requested of unsupported type: %s --> %s",
media_type,
media_id,
)
async def async_browse_media(self, media_content_type=None, media_content_id=None):
"""Implement the websocket media browsing helper."""
return await self.hass.async_add_executor_job(
browse_media,
self.zone_id,
self._server,
media_content_type,
media_content_id,
)

View file

@ -2,7 +2,7 @@
import asyncio
import logging
from roon import RoonApi
from roonapi import RoonApi
from homeassistant.const import CONF_API_KEY, CONF_HOST
from homeassistant.helpers.dispatcher import async_dispatcher_send

View file

@ -1948,7 +1948,7 @@ rokuecp==0.6.0
roombapy==1.6.1
# homeassistant.components.roon
roonapi==0.0.21
roonapi==0.0.23
# homeassistant.components.rova
rova==0.1.0

View file

@ -932,7 +932,7 @@ rokuecp==0.6.0
roombapy==1.6.1
# homeassistant.components.roon
roonapi==0.0.21
roonapi==0.0.23
# homeassistant.components.rpi_power
rpi-bad-power==0.0.3