Add media browser support to roon media player (#42061)
This commit is contained in:
parent
e7b6903ef4
commit
9a54c31c34
8 changed files with 185 additions and 6 deletions
|
@ -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/*
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
163
homeassistant/components/roon/media_browser.py
Normal file
163
homeassistant/components/roon/media_browser.py
Normal 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
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue