mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Add media browser support to roon media player (#42061)
This commit is contained in:
parent
e7b6903ef4
commit
9a54c31c34
@ -730,6 +730,7 @@ omit =
|
|||||||
homeassistant/components/roomba/vacuum.py
|
homeassistant/components/roomba/vacuum.py
|
||||||
homeassistant/components/roon/__init__.py
|
homeassistant/components/roon/__init__.py
|
||||||
homeassistant/components/roon/const.py
|
homeassistant/components/roon/const.py
|
||||||
|
homeassistant/components/roon/media_browser.py
|
||||||
homeassistant/components/roon/media_player.py
|
homeassistant/components/roon/media_player.py
|
||||||
homeassistant/components/roon/server.py
|
homeassistant/components/roon/server.py
|
||||||
homeassistant/components/route53/*
|
homeassistant/components/route53/*
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from roon import RoonApi
|
from roonapi import RoonApi
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries, core, exceptions
|
from homeassistant import config_entries, core, exceptions
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/roon",
|
"documentation": "https://www.home-assistant.io/integrations/roon",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"roonapi==0.0.21"
|
"roonapi==0.0.23"
|
||||||
],
|
],
|
||||||
"codeowners": [
|
"codeowners": [
|
||||||
"@pavoni"
|
"@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 import MediaPlayerEntity
|
||||||
from homeassistant.components.media_player.const import (
|
from homeassistant.components.media_player.const import (
|
||||||
|
SUPPORT_BROWSE_MEDIA,
|
||||||
SUPPORT_NEXT_TRACK,
|
SUPPORT_NEXT_TRACK,
|
||||||
SUPPORT_PAUSE,
|
SUPPORT_PAUSE,
|
||||||
SUPPORT_PLAY,
|
SUPPORT_PLAY,
|
||||||
@ -33,9 +34,11 @@ from homeassistant.util import convert
|
|||||||
from homeassistant.util.dt import utcnow
|
from homeassistant.util.dt import utcnow
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
from .media_browser import browse_media
|
||||||
|
|
||||||
SUPPORT_ROON = (
|
SUPPORT_ROON = (
|
||||||
SUPPORT_PAUSE
|
SUPPORT_BROWSE_MEDIA
|
||||||
|
| SUPPORT_PAUSE
|
||||||
| SUPPORT_VOLUME_SET
|
| SUPPORT_VOLUME_SET
|
||||||
| SUPPORT_STOP
|
| SUPPORT_STOP
|
||||||
| SUPPORT_PREVIOUS_TRACK
|
| SUPPORT_PREVIOUS_TRACK
|
||||||
@ -466,9 +469,21 @@ class RoonDevice(MediaPlayerEntity):
|
|||||||
self._server.roonapi.queue_playlist(self.zone_id, media_id)
|
self._server.roonapi.queue_playlist(self.zone_id, media_id)
|
||||||
elif media_type == "genre":
|
elif media_type == "genre":
|
||||||
self._server.roonapi.play_genre(self.zone_id, media_id)
|
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:
|
else:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Playback requested of unsupported type: %s --> %s",
|
"Playback requested of unsupported type: %s --> %s",
|
||||||
media_type,
|
media_type,
|
||||||
media_id,
|
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 asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from roon import RoonApi
|
from roonapi import RoonApi
|
||||||
|
|
||||||
from homeassistant.const import CONF_API_KEY, CONF_HOST
|
from homeassistant.const import CONF_API_KEY, CONF_HOST
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
|
@ -1948,7 +1948,7 @@ rokuecp==0.6.0
|
|||||||
roombapy==1.6.1
|
roombapy==1.6.1
|
||||||
|
|
||||||
# homeassistant.components.roon
|
# homeassistant.components.roon
|
||||||
roonapi==0.0.21
|
roonapi==0.0.23
|
||||||
|
|
||||||
# homeassistant.components.rova
|
# homeassistant.components.rova
|
||||||
rova==0.1.0
|
rova==0.1.0
|
||||||
|
@ -932,7 +932,7 @@ rokuecp==0.6.0
|
|||||||
roombapy==1.6.1
|
roombapy==1.6.1
|
||||||
|
|
||||||
# homeassistant.components.roon
|
# homeassistant.components.roon
|
||||||
roonapi==0.0.21
|
roonapi==0.0.23
|
||||||
|
|
||||||
# homeassistant.components.rpi_power
|
# homeassistant.components.rpi_power
|
||||||
rpi-bad-power==0.0.3
|
rpi-bad-power==0.0.3
|
||||||
|
Loading…
x
Reference in New Issue
Block a user