mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Support Plex in Sonos media browser (#64951)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
e5b0031103
commit
e17f1ea577
@ -13,7 +13,7 @@ from plexwebsocket import (
|
|||||||
)
|
)
|
||||||
import requests.exceptions
|
import requests.exceptions
|
||||||
|
|
||||||
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
|
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN, BrowseError
|
||||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||||
from homeassistant.const import CONF_URL, CONF_VERIFY_SSL, EVENT_HOMEASSISTANT_STOP
|
from homeassistant.const import CONF_URL, CONF_VERIFY_SSL, EVENT_HOMEASSISTANT_STOP
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
@ -25,6 +25,7 @@ from homeassistant.helpers.dispatcher import (
|
|||||||
async_dispatcher_connect,
|
async_dispatcher_connect,
|
||||||
async_dispatcher_send,
|
async_dispatcher_send,
|
||||||
)
|
)
|
||||||
|
from homeassistant.helpers.network import is_internal_request
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
@ -39,16 +40,41 @@ from .const import (
|
|||||||
PLEX_SERVER_CONFIG,
|
PLEX_SERVER_CONFIG,
|
||||||
PLEX_UPDATE_LIBRARY_SIGNAL,
|
PLEX_UPDATE_LIBRARY_SIGNAL,
|
||||||
PLEX_UPDATE_PLATFORMS_SIGNAL,
|
PLEX_UPDATE_PLATFORMS_SIGNAL,
|
||||||
|
PLEX_URI_SCHEME,
|
||||||
SERVERS,
|
SERVERS,
|
||||||
WEBSOCKETS,
|
WEBSOCKETS,
|
||||||
)
|
)
|
||||||
from .errors import ShouldUpdateConfigEntry
|
from .errors import ShouldUpdateConfigEntry
|
||||||
|
from .media_browser import browse_media
|
||||||
from .server import PlexServer
|
from .server import PlexServer
|
||||||
from .services import async_setup_services
|
from .services import async_setup_services
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__package__)
|
_LOGGER = logging.getLogger(__package__)
|
||||||
|
|
||||||
|
|
||||||
|
def is_plex_media_id(media_content_id):
|
||||||
|
"""Return whether the media_content_id is a valid Plex media_id."""
|
||||||
|
return media_content_id and media_content_id.startswith(PLEX_URI_SCHEME)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_browse_media(hass, media_content_type, media_content_id, platform=None):
|
||||||
|
"""Browse Plex media."""
|
||||||
|
plex_server = next(iter(hass.data[PLEX_DOMAIN][SERVERS].values()), None)
|
||||||
|
if not plex_server:
|
||||||
|
raise BrowseError("No Plex servers available")
|
||||||
|
is_internal = is_internal_request(hass)
|
||||||
|
return await hass.async_add_executor_job(
|
||||||
|
partial(
|
||||||
|
browse_media,
|
||||||
|
plex_server,
|
||||||
|
is_internal,
|
||||||
|
media_content_type,
|
||||||
|
media_content_id,
|
||||||
|
platform=platform,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
"""Set up the Plex component."""
|
"""Set up the Plex component."""
|
||||||
hass.data.setdefault(
|
hass.data.setdefault(
|
||||||
|
@ -16,7 +16,7 @@ from homeassistant.components.media_player.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.components.media_player.errors import BrowseError
|
from homeassistant.components.media_player.errors import BrowseError
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN, PLEX_URI_SCHEME
|
||||||
from .helpers import pretty_title
|
from .helpers import pretty_title
|
||||||
|
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ EXPANDABLES = ["album", "artist", "playlist", "season", "show"]
|
|||||||
PLAYLISTS_BROWSE_PAYLOAD = {
|
PLAYLISTS_BROWSE_PAYLOAD = {
|
||||||
"title": "Playlists",
|
"title": "Playlists",
|
||||||
"media_class": MEDIA_CLASS_DIRECTORY,
|
"media_class": MEDIA_CLASS_DIRECTORY,
|
||||||
"media_content_id": "all",
|
"media_content_id": PLEX_URI_SCHEME + "all",
|
||||||
"media_content_type": "playlists",
|
"media_content_type": "playlists",
|
||||||
"can_play": False,
|
"can_play": False,
|
||||||
"can_expand": True,
|
"can_expand": True,
|
||||||
@ -53,7 +53,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def browse_media( # noqa: C901
|
def browse_media( # noqa: C901
|
||||||
entity, is_internal, media_content_type=None, media_content_id=None
|
plex_server, is_internal, media_content_type, media_content_id, *, platform=None
|
||||||
):
|
):
|
||||||
"""Implement the websocket media browsing helper."""
|
"""Implement the websocket media browsing helper."""
|
||||||
|
|
||||||
@ -67,28 +67,19 @@ def browse_media( # noqa: C901
|
|||||||
payload = {
|
payload = {
|
||||||
"title": pretty_title(item, short_name),
|
"title": pretty_title(item, short_name),
|
||||||
"media_class": media_class,
|
"media_class": media_class,
|
||||||
"media_content_id": str(item.ratingKey),
|
"media_content_id": PLEX_URI_SCHEME + str(item.ratingKey),
|
||||||
"media_content_type": item.type,
|
"media_content_type": item.type,
|
||||||
"can_play": True,
|
"can_play": True,
|
||||||
"can_expand": item.type in EXPANDABLES,
|
"can_expand": item.type in EXPANDABLES,
|
||||||
}
|
}
|
||||||
if hasattr(item, "thumbUrl"):
|
if hasattr(item, "thumbUrl"):
|
||||||
entity.plex_server.thumbnail_cache.setdefault(
|
payload["thumbnail"] = item.thumbUrl
|
||||||
str(item.ratingKey), item.thumbUrl
|
|
||||||
)
|
|
||||||
|
|
||||||
if is_internal:
|
|
||||||
thumbnail = item.thumbUrl
|
|
||||||
else:
|
|
||||||
thumbnail = entity.get_browse_image_url(item.type, item.ratingKey)
|
|
||||||
|
|
||||||
payload["thumbnail"] = thumbnail
|
|
||||||
|
|
||||||
return BrowseMedia(**payload)
|
return BrowseMedia(**payload)
|
||||||
|
|
||||||
def library_payload(library_id):
|
def library_payload(library_id):
|
||||||
"""Create response payload to describe contents of a specific library."""
|
"""Create response payload to describe contents of a specific library."""
|
||||||
library = entity.plex_server.library.sectionByID(library_id)
|
library = plex_server.library.sectionByID(library_id)
|
||||||
library_info = library_section_payload(library)
|
library_info = library_section_payload(library)
|
||||||
library_info.children = [special_library_payload(library_info, "Recommended")]
|
library_info.children = [special_library_payload(library_info, "Recommended")]
|
||||||
for item in library.all():
|
for item in library.all():
|
||||||
@ -98,10 +89,12 @@ def browse_media( # noqa: C901
|
|||||||
continue
|
continue
|
||||||
return library_info
|
return library_info
|
||||||
|
|
||||||
def playlists_payload():
|
def playlists_payload(platform):
|
||||||
"""Create response payload for all available playlists."""
|
"""Create response payload for all available playlists."""
|
||||||
playlists_info = {**PLAYLISTS_BROWSE_PAYLOAD, "children": []}
|
playlists_info = {**PLAYLISTS_BROWSE_PAYLOAD, "children": []}
|
||||||
for playlist in entity.plex_server.playlists():
|
for playlist in plex_server.playlists():
|
||||||
|
if playlist.playlistType != "audio" and platform == "sonos":
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
playlists_info["children"].append(item_payload(playlist))
|
playlists_info["children"].append(item_payload(playlist))
|
||||||
except UnknownMediaType:
|
except UnknownMediaType:
|
||||||
@ -112,7 +105,7 @@ def browse_media( # noqa: C901
|
|||||||
|
|
||||||
def build_item_response(payload):
|
def build_item_response(payload):
|
||||||
"""Create response payload for the provided media query."""
|
"""Create response payload for the provided media query."""
|
||||||
media = entity.plex_server.lookup_media(**payload)
|
media = plex_server.lookup_media(**payload)
|
||||||
|
|
||||||
if media is None:
|
if media is None:
|
||||||
return None
|
return None
|
||||||
@ -123,7 +116,7 @@ def browse_media( # noqa: C901
|
|||||||
return None
|
return None
|
||||||
if media_info.can_expand:
|
if media_info.can_expand:
|
||||||
media_info.children = []
|
media_info.children = []
|
||||||
if media.TYPE == "artist":
|
if media.TYPE == "artist" and platform != "sonos":
|
||||||
if (station := media.station()) is not None:
|
if (station := media.station()) is not None:
|
||||||
media_info.children.append(station_payload(station))
|
media_info.children.append(station_payload(station))
|
||||||
for item in media:
|
for item in media:
|
||||||
@ -133,18 +126,22 @@ def browse_media( # noqa: C901
|
|||||||
continue
|
continue
|
||||||
return media_info
|
return media_info
|
||||||
|
|
||||||
|
if media_content_id:
|
||||||
|
assert media_content_id.startswith(PLEX_URI_SCHEME)
|
||||||
|
media_content_id = media_content_id[len(PLEX_URI_SCHEME) :]
|
||||||
|
|
||||||
if media_content_id and media_content_id.startswith(HUB_PREFIX):
|
if media_content_id and media_content_id.startswith(HUB_PREFIX):
|
||||||
media_content_id = media_content_id[len(HUB_PREFIX) :]
|
media_content_id = media_content_id[len(HUB_PREFIX) :]
|
||||||
location, hub_identifier = media_content_id.split(":")
|
location, hub_identifier = media_content_id.split(":")
|
||||||
if location == "server":
|
if location == "server":
|
||||||
hub = next(
|
hub = next(
|
||||||
x
|
x
|
||||||
for x in entity.plex_server.library.hubs()
|
for x in plex_server.library.hubs()
|
||||||
if x.hubIdentifier == hub_identifier
|
if x.hubIdentifier == hub_identifier
|
||||||
)
|
)
|
||||||
media_content_id = f"{HUB_PREFIX}server:{hub.hubIdentifier}"
|
media_content_id = f"{HUB_PREFIX}server:{hub.hubIdentifier}"
|
||||||
else:
|
else:
|
||||||
library_section = entity.plex_server.library.sectionByID(int(location))
|
library_section = plex_server.library.sectionByID(int(location))
|
||||||
hub = next(
|
hub = next(
|
||||||
x for x in library_section.hubs() if x.hubIdentifier == hub_identifier
|
x for x in library_section.hubs() if x.hubIdentifier == hub_identifier
|
||||||
)
|
)
|
||||||
@ -156,7 +153,7 @@ def browse_media( # noqa: C901
|
|||||||
payload = {
|
payload = {
|
||||||
"title": hub.title,
|
"title": hub.title,
|
||||||
"media_class": MEDIA_CLASS_DIRECTORY,
|
"media_class": MEDIA_CLASS_DIRECTORY,
|
||||||
"media_content_id": media_content_id,
|
"media_content_id": PLEX_URI_SCHEME + media_content_id,
|
||||||
"media_content_type": hub.type,
|
"media_content_type": hub.type,
|
||||||
"can_play": False,
|
"can_play": False,
|
||||||
"can_expand": True,
|
"can_expand": True,
|
||||||
@ -165,6 +162,8 @@ def browse_media( # noqa: C901
|
|||||||
}
|
}
|
||||||
for item in hub.items:
|
for item in hub.items:
|
||||||
if hub.type == "station":
|
if hub.type == "station":
|
||||||
|
if platform == "sonos":
|
||||||
|
continue
|
||||||
payload["children"].append(station_payload(item))
|
payload["children"].append(station_payload(item))
|
||||||
else:
|
else:
|
||||||
payload["children"].append(item_payload(item))
|
payload["children"].append(item_payload(item))
|
||||||
@ -175,24 +174,13 @@ def browse_media( # noqa: C901
|
|||||||
else:
|
else:
|
||||||
special_folder = None
|
special_folder = None
|
||||||
|
|
||||||
if (
|
|
||||||
media_content_type
|
|
||||||
and media_content_type == "server"
|
|
||||||
and media_content_id != entity.plex_server.machine_identifier
|
|
||||||
):
|
|
||||||
raise BrowseError(
|
|
||||||
f"Plex server with ID '{media_content_id}' is not associated with {entity.entity_id}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if special_folder:
|
if special_folder:
|
||||||
if media_content_type == "server":
|
if media_content_type == "server":
|
||||||
library_or_section = entity.plex_server.library
|
library_or_section = plex_server.library
|
||||||
children_media_class = MEDIA_CLASS_DIRECTORY
|
children_media_class = MEDIA_CLASS_DIRECTORY
|
||||||
title = entity.plex_server.friendly_name
|
title = plex_server.friendly_name
|
||||||
elif media_content_type == "library":
|
elif media_content_type == "library":
|
||||||
library_or_section = entity.plex_server.library.sectionByID(
|
library_or_section = plex_server.library.sectionByID(int(media_content_id))
|
||||||
int(media_content_id)
|
|
||||||
)
|
|
||||||
title = library_or_section.title
|
title = library_or_section.title
|
||||||
try:
|
try:
|
||||||
children_media_class = ITEM_TYPE_MEDIA_CLASS[library_or_section.TYPE]
|
children_media_class = ITEM_TYPE_MEDIA_CLASS[library_or_section.TYPE]
|
||||||
@ -208,7 +196,8 @@ def browse_media( # noqa: C901
|
|||||||
payload = {
|
payload = {
|
||||||
"title": title,
|
"title": title,
|
||||||
"media_class": MEDIA_CLASS_DIRECTORY,
|
"media_class": MEDIA_CLASS_DIRECTORY,
|
||||||
"media_content_id": f"{media_content_id}:{special_folder}",
|
"media_content_id": PLEX_URI_SCHEME
|
||||||
|
+ f"{media_content_id}:{special_folder}",
|
||||||
"media_content_type": media_content_type,
|
"media_content_type": media_content_type,
|
||||||
"can_play": False,
|
"can_play": False,
|
||||||
"can_expand": True,
|
"can_expand": True,
|
||||||
@ -226,7 +215,7 @@ def browse_media( # noqa: C901
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if media_content_type in ("server", None):
|
if media_content_type in ("server", None):
|
||||||
return server_payload(entity.plex_server)
|
return server_payload(plex_server, platform)
|
||||||
|
|
||||||
if media_content_type == "library":
|
if media_content_type == "library":
|
||||||
return library_payload(int(media_content_id))
|
return library_payload(int(media_content_id))
|
||||||
@ -237,7 +226,7 @@ def browse_media( # noqa: C901
|
|||||||
) from err
|
) from err
|
||||||
|
|
||||||
if media_content_type == "playlists":
|
if media_content_type == "playlists":
|
||||||
return playlists_payload()
|
return playlists_payload(platform)
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"media_type": DOMAIN,
|
"media_type": DOMAIN,
|
||||||
@ -259,7 +248,7 @@ def library_section_payload(section):
|
|||||||
return BrowseMedia(
|
return BrowseMedia(
|
||||||
title=section.title,
|
title=section.title,
|
||||||
media_class=MEDIA_CLASS_DIRECTORY,
|
media_class=MEDIA_CLASS_DIRECTORY,
|
||||||
media_content_id=str(section.key),
|
media_content_id=PLEX_URI_SCHEME + str(section.key),
|
||||||
media_content_type="library",
|
media_content_type="library",
|
||||||
can_play=False,
|
can_play=False,
|
||||||
can_expand=True,
|
can_expand=True,
|
||||||
@ -281,21 +270,25 @@ def special_library_payload(parent_payload, special_type):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def server_payload(plex_server):
|
def server_payload(plex_server, platform):
|
||||||
"""Create response payload to describe libraries of the Plex server."""
|
"""Create response payload to describe libraries of the Plex server."""
|
||||||
server_info = BrowseMedia(
|
server_info = BrowseMedia(
|
||||||
title=plex_server.friendly_name,
|
title=plex_server.friendly_name,
|
||||||
media_class=MEDIA_CLASS_DIRECTORY,
|
media_class=MEDIA_CLASS_DIRECTORY,
|
||||||
media_content_id=plex_server.machine_identifier,
|
media_content_id=PLEX_URI_SCHEME + plex_server.machine_identifier,
|
||||||
media_content_type="server",
|
media_content_type="server",
|
||||||
can_play=False,
|
can_play=False,
|
||||||
can_expand=True,
|
can_expand=True,
|
||||||
|
children=[],
|
||||||
children_media_class=MEDIA_CLASS_DIRECTORY,
|
children_media_class=MEDIA_CLASS_DIRECTORY,
|
||||||
)
|
)
|
||||||
server_info.children = [special_library_payload(server_info, "Recommended")]
|
if platform != "sonos":
|
||||||
|
server_info.children.append(special_library_payload(server_info, "Recommended"))
|
||||||
for library in plex_server.library.sections():
|
for library in plex_server.library.sections():
|
||||||
if library.type == "photo":
|
if library.type == "photo":
|
||||||
continue
|
continue
|
||||||
|
if library.type != "artist" and platform == "sonos":
|
||||||
|
continue
|
||||||
server_info.children.append(library_section_payload(library))
|
server_info.children.append(library_section_payload(library))
|
||||||
server_info.children.append(BrowseMedia(**PLAYLISTS_BROWSE_PAYLOAD))
|
server_info.children.append(BrowseMedia(**PLAYLISTS_BROWSE_PAYLOAD))
|
||||||
return server_info
|
return server_info
|
||||||
@ -310,7 +303,7 @@ def hub_payload(hub):
|
|||||||
payload = {
|
payload = {
|
||||||
"title": hub.title,
|
"title": hub.title,
|
||||||
"media_class": MEDIA_CLASS_DIRECTORY,
|
"media_class": MEDIA_CLASS_DIRECTORY,
|
||||||
"media_content_id": media_content_id,
|
"media_content_id": PLEX_URI_SCHEME + media_content_id,
|
||||||
"media_content_type": hub.type,
|
"media_content_type": hub.type,
|
||||||
"can_play": False,
|
"can_play": False,
|
||||||
"can_expand": True,
|
"can_expand": True,
|
||||||
@ -323,7 +316,7 @@ def station_payload(station):
|
|||||||
return BrowseMedia(
|
return BrowseMedia(
|
||||||
title=station.title,
|
title=station.title,
|
||||||
media_class=ITEM_TYPE_MEDIA_CLASS[station.type],
|
media_class=ITEM_TYPE_MEDIA_CLASS[station.type],
|
||||||
media_content_id=station.key,
|
media_content_id=PLEX_URI_SCHEME + station.key,
|
||||||
media_content_type="station",
|
media_content_type="station",
|
||||||
can_play=True,
|
can_play=True,
|
||||||
can_expand=False,
|
can_expand=False,
|
||||||
|
@ -46,6 +46,7 @@ from .const import (
|
|||||||
PLEX_UPDATE_MEDIA_PLAYER_SESSION_SIGNAL,
|
PLEX_UPDATE_MEDIA_PLAYER_SESSION_SIGNAL,
|
||||||
PLEX_UPDATE_MEDIA_PLAYER_SIGNAL,
|
PLEX_UPDATE_MEDIA_PLAYER_SIGNAL,
|
||||||
PLEX_UPDATE_SENSOR_SIGNAL,
|
PLEX_UPDATE_SENSOR_SIGNAL,
|
||||||
|
PLEX_URI_SCHEME,
|
||||||
SERVERS,
|
SERVERS,
|
||||||
TRANSIENT_DEVICE_MODELS,
|
TRANSIENT_DEVICE_MODELS,
|
||||||
)
|
)
|
||||||
@ -486,6 +487,9 @@ class PlexMediaPlayer(MediaPlayerEntity):
|
|||||||
"Plex integration configured without a token, playback may fail"
|
"Plex integration configured without a token, playback may fail"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if media_id.startswith(PLEX_URI_SCHEME):
|
||||||
|
media_id = media_id[len(PLEX_URI_SCHEME) :]
|
||||||
|
|
||||||
if media_type == "station":
|
if media_type == "station":
|
||||||
playqueue = self.plex_server.create_station_playqueue(media_id)
|
playqueue = self.plex_server.create_station_playqueue(media_id)
|
||||||
try:
|
try:
|
||||||
@ -576,7 +580,7 @@ class PlexMediaPlayer(MediaPlayerEntity):
|
|||||||
is_internal = is_internal_request(self.hass)
|
is_internal = is_internal_request(self.hass)
|
||||||
return await self.hass.async_add_executor_job(
|
return await self.hass.async_add_executor_job(
|
||||||
browse_media,
|
browse_media,
|
||||||
self,
|
self.plex_server,
|
||||||
is_internal,
|
is_internal,
|
||||||
media_content_type,
|
media_content_type,
|
||||||
media_content_id,
|
media_content_id,
|
||||||
|
@ -7,7 +7,7 @@ from functools import partial
|
|||||||
import logging
|
import logging
|
||||||
from urllib.parse import quote_plus, unquote
|
from urllib.parse import quote_plus, unquote
|
||||||
|
|
||||||
from homeassistant.components import media_source, spotify
|
from homeassistant.components import media_source, plex, spotify
|
||||||
from homeassistant.components.media_player import BrowseMedia
|
from homeassistant.components.media_player import BrowseMedia
|
||||||
from homeassistant.components.media_player.const import (
|
from homeassistant.components.media_player.const import (
|
||||||
MEDIA_CLASS_DIRECTORY,
|
MEDIA_CLASS_DIRECTORY,
|
||||||
@ -18,6 +18,7 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.helpers.network import is_internal_request
|
from homeassistant.helpers.network import is_internal_request
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
|
DOMAIN,
|
||||||
EXPANDABLE_MEDIA_TYPES,
|
EXPANDABLE_MEDIA_TYPES,
|
||||||
LIBRARY_TITLES_MAPPING,
|
LIBRARY_TITLES_MAPPING,
|
||||||
MEDIA_TYPES_TO_SONOS,
|
MEDIA_TYPES_TO_SONOS,
|
||||||
@ -90,6 +91,14 @@ async def async_browse_media(
|
|||||||
hass, media_content_id, content_filter=media_source_filter
|
hass, media_content_id, content_filter=media_source_filter
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if plex.is_plex_media_id(media_content_id):
|
||||||
|
return await plex.async_browse_media(
|
||||||
|
hass, media_content_type, media_content_id, platform=DOMAIN
|
||||||
|
)
|
||||||
|
|
||||||
|
if media_content_type == "plex":
|
||||||
|
return await plex.async_browse_media(hass, None, None, platform=DOMAIN)
|
||||||
|
|
||||||
if spotify.is_spotify_media_type(media_content_type):
|
if spotify.is_spotify_media_type(media_content_type):
|
||||||
return await spotify.async_browse_media(
|
return await spotify.async_browse_media(
|
||||||
hass, media_content_type, media_content_id, can_play_artist=False
|
hass, media_content_type, media_content_id, can_play_artist=False
|
||||||
@ -256,6 +265,19 @@ async def root_payload(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if "plex" in hass.config.components:
|
||||||
|
children.append(
|
||||||
|
BrowseMedia(
|
||||||
|
title="Plex",
|
||||||
|
media_class=MEDIA_CLASS_DIRECTORY,
|
||||||
|
media_content_id="",
|
||||||
|
media_content_type="plex",
|
||||||
|
thumbnail="https://brands.home-assistant.io/_/plex/logo.png",
|
||||||
|
can_play=False,
|
||||||
|
can_expand=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if "spotify" in hass.config.components:
|
if "spotify" in hass.config.components:
|
||||||
children.append(
|
children.append(
|
||||||
BrowseMedia(
|
BrowseMedia(
|
||||||
@ -263,7 +285,7 @@ async def root_payload(
|
|||||||
media_class=MEDIA_CLASS_DIRECTORY,
|
media_class=MEDIA_CLASS_DIRECTORY,
|
||||||
media_content_id="",
|
media_content_id="",
|
||||||
media_content_type="spotify",
|
media_content_type="spotify",
|
||||||
thumbnail="https://brands.home-assistant.io/spotify/logo.png",
|
thumbnail="https://brands.home-assistant.io/_/spotify/logo.png",
|
||||||
can_play=False,
|
can_play=False,
|
||||||
can_expand=True,
|
can_expand=True,
|
||||||
)
|
)
|
||||||
|
@ -546,7 +546,10 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
|
|||||||
plex_plugin = self.speaker.plex_plugin
|
plex_plugin = self.speaker.plex_plugin
|
||||||
media_id = media_id[len(PLEX_URI_SCHEME) :]
|
media_id = media_id[len(PLEX_URI_SCHEME) :]
|
||||||
payload = json.loads(media_id)
|
payload = json.loads(media_id)
|
||||||
shuffle = payload.pop("shuffle", None)
|
if isinstance(payload, dict):
|
||||||
|
shuffle = payload.pop("shuffle", False)
|
||||||
|
else:
|
||||||
|
shuffle = False
|
||||||
media = lookup_plex_media(self.hass, media_type, json.dumps(payload))
|
media = lookup_plex_media(self.hass, media_type, json.dumps(payload))
|
||||||
if not kwargs.get(ATTR_MEDIA_ENQUEUE):
|
if not kwargs.get(ATTR_MEDIA_ENQUEUE):
|
||||||
soco.clear_queue()
|
soco.clear_queue()
|
||||||
|
@ -6,7 +6,7 @@ from homeassistant.components.media_player.const import (
|
|||||||
ATTR_MEDIA_CONTENT_ID,
|
ATTR_MEDIA_CONTENT_ID,
|
||||||
ATTR_MEDIA_CONTENT_TYPE,
|
ATTR_MEDIA_CONTENT_TYPE,
|
||||||
)
|
)
|
||||||
from homeassistant.components.plex.const import CONF_SERVER_IDENTIFIER
|
from homeassistant.components.plex.const import CONF_SERVER_IDENTIFIER, PLEX_URI_SCHEME
|
||||||
from homeassistant.components.websocket_api.const import ERR_UNKNOWN_ERROR, TYPE_RESULT
|
from homeassistant.components.websocket_api.const import ERR_UNKNOWN_ERROR, TYPE_RESULT
|
||||||
|
|
||||||
from .const import DEFAULT_DATA
|
from .const import DEFAULT_DATA
|
||||||
@ -120,23 +120,6 @@ async def test_browse_media(
|
|||||||
media_players = hass.states.async_entity_ids("media_player")
|
media_players = hass.states.async_entity_ids("media_player")
|
||||||
msg_id = 1
|
msg_id = 1
|
||||||
|
|
||||||
# Browse base of non-existent Plex server
|
|
||||||
await websocket_client.send_json(
|
|
||||||
{
|
|
||||||
"id": msg_id,
|
|
||||||
"type": "media_player/browse_media",
|
|
||||||
"entity_id": media_players[0],
|
|
||||||
ATTR_MEDIA_CONTENT_TYPE: "server",
|
|
||||||
ATTR_MEDIA_CONTENT_ID: "this server does not exist",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
msg = await websocket_client.receive_json()
|
|
||||||
assert msg["id"] == msg_id
|
|
||||||
assert msg["type"] == TYPE_RESULT
|
|
||||||
assert not msg["success"]
|
|
||||||
assert msg["error"]["code"] == ERR_UNKNOWN_ERROR
|
|
||||||
|
|
||||||
# Browse base of Plex server
|
# Browse base of Plex server
|
||||||
msg_id += 1
|
msg_id += 1
|
||||||
await websocket_client.send_json(
|
await websocket_client.send_json(
|
||||||
@ -153,7 +136,10 @@ async def test_browse_media(
|
|||||||
assert msg["success"]
|
assert msg["success"]
|
||||||
result = msg["result"]
|
result = msg["result"]
|
||||||
assert result[ATTR_MEDIA_CONTENT_TYPE] == "server"
|
assert result[ATTR_MEDIA_CONTENT_TYPE] == "server"
|
||||||
assert result[ATTR_MEDIA_CONTENT_ID] == DEFAULT_DATA[CONF_SERVER_IDENTIFIER]
|
assert (
|
||||||
|
result[ATTR_MEDIA_CONTENT_ID]
|
||||||
|
== PLEX_URI_SCHEME + DEFAULT_DATA[CONF_SERVER_IDENTIFIER]
|
||||||
|
)
|
||||||
# Library Sections + Recommended + Playlists
|
# Library Sections + Recommended + Playlists
|
||||||
assert len(result["children"]) == len(mock_plex_server.library.sections()) + 2
|
assert len(result["children"]) == len(mock_plex_server.library.sections()) + 2
|
||||||
|
|
||||||
@ -175,7 +161,8 @@ async def test_browse_media(
|
|||||||
"type": "media_player/browse_media",
|
"type": "media_player/browse_media",
|
||||||
"entity_id": media_players[0],
|
"entity_id": media_players[0],
|
||||||
ATTR_MEDIA_CONTENT_TYPE: "server",
|
ATTR_MEDIA_CONTENT_TYPE: "server",
|
||||||
ATTR_MEDIA_CONTENT_ID: f"{DEFAULT_DATA[CONF_SERVER_IDENTIFIER]}:{special_keys[0]}",
|
ATTR_MEDIA_CONTENT_ID: PLEX_URI_SCHEME
|
||||||
|
+ f"{DEFAULT_DATA[CONF_SERVER_IDENTIFIER]}:{special_keys[0]}",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -187,7 +174,7 @@ async def test_browse_media(
|
|||||||
assert result[ATTR_MEDIA_CONTENT_TYPE] == "server"
|
assert result[ATTR_MEDIA_CONTENT_TYPE] == "server"
|
||||||
assert (
|
assert (
|
||||||
result[ATTR_MEDIA_CONTENT_ID]
|
result[ATTR_MEDIA_CONTENT_ID]
|
||||||
== f"{DEFAULT_DATA[CONF_SERVER_IDENTIFIER]}:{special_keys[0]}"
|
== PLEX_URI_SCHEME + f"{DEFAULT_DATA[CONF_SERVER_IDENTIFIER]}:{special_keys[0]}"
|
||||||
)
|
)
|
||||||
assert len(result["children"]) == 4 # Hardcoded in fixture
|
assert len(result["children"]) == 4 # Hardcoded in fixture
|
||||||
assert result["children"][0]["media_content_type"] == "mixed"
|
assert result["children"][0]["media_content_type"] == "mixed"
|
||||||
@ -228,7 +215,8 @@ async def test_browse_media(
|
|||||||
"type": "media_player/browse_media",
|
"type": "media_player/browse_media",
|
||||||
"entity_id": media_players[0],
|
"entity_id": media_players[0],
|
||||||
ATTR_MEDIA_CONTENT_TYPE: "library",
|
ATTR_MEDIA_CONTENT_TYPE: "library",
|
||||||
ATTR_MEDIA_CONTENT_ID: f"{library_section_id}:{special_keys[0]}",
|
ATTR_MEDIA_CONTENT_ID: PLEX_URI_SCHEME
|
||||||
|
+ f"{library_section_id}:{special_keys[0]}",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -238,7 +226,10 @@ async def test_browse_media(
|
|||||||
assert msg["success"]
|
assert msg["success"]
|
||||||
result = msg["result"]
|
result = msg["result"]
|
||||||
assert result[ATTR_MEDIA_CONTENT_TYPE] == "library"
|
assert result[ATTR_MEDIA_CONTENT_TYPE] == "library"
|
||||||
assert result[ATTR_MEDIA_CONTENT_ID] == f"{library_section_id}:{special_keys[0]}"
|
assert (
|
||||||
|
result[ATTR_MEDIA_CONTENT_ID]
|
||||||
|
== PLEX_URI_SCHEME + f"{library_section_id}:{special_keys[0]}"
|
||||||
|
)
|
||||||
assert len(result["children"]) == 1
|
assert len(result["children"]) == 1
|
||||||
|
|
||||||
# Browse into a library radio station hub
|
# Browse into a library radio station hub
|
||||||
@ -280,7 +271,7 @@ async def test_browse_media(
|
|||||||
assert msg["success"]
|
assert msg["success"]
|
||||||
result = msg["result"]
|
result = msg["result"]
|
||||||
assert result[ATTR_MEDIA_CONTENT_TYPE] == "library"
|
assert result[ATTR_MEDIA_CONTENT_TYPE] == "library"
|
||||||
result_id = int(result[ATTR_MEDIA_CONTENT_ID])
|
result_id = int(result[ATTR_MEDIA_CONTENT_ID][len(PLEX_URI_SCHEME) :])
|
||||||
# All items in section + Hubs
|
# All items in section + Hubs
|
||||||
assert (
|
assert (
|
||||||
len(result["children"])
|
len(result["children"])
|
||||||
@ -314,7 +305,7 @@ async def test_browse_media(
|
|||||||
assert msg["success"]
|
assert msg["success"]
|
||||||
result = msg["result"]
|
result = msg["result"]
|
||||||
assert result[ATTR_MEDIA_CONTENT_TYPE] == "show"
|
assert result[ATTR_MEDIA_CONTENT_TYPE] == "show"
|
||||||
result_id = int(result[ATTR_MEDIA_CONTENT_ID])
|
result_id = int(result[ATTR_MEDIA_CONTENT_ID][len(PLEX_URI_SCHEME) :])
|
||||||
assert result["title"] == mock_plex_server.fetch_item(result_id).title
|
assert result["title"] == mock_plex_server.fetch_item(result_id).title
|
||||||
assert result["children"][0]["title"] == f"{mock_season.title} ({mock_season.year})"
|
assert result["children"][0]["title"] == f"{mock_season.title} ({mock_season.year})"
|
||||||
|
|
||||||
@ -344,7 +335,7 @@ async def test_browse_media(
|
|||||||
assert msg["success"]
|
assert msg["success"]
|
||||||
result = msg["result"]
|
result = msg["result"]
|
||||||
assert result[ATTR_MEDIA_CONTENT_TYPE] == "season"
|
assert result[ATTR_MEDIA_CONTENT_TYPE] == "season"
|
||||||
result_id = int(result[ATTR_MEDIA_CONTENT_ID])
|
result_id = int(result[ATTR_MEDIA_CONTENT_ID][len(PLEX_URI_SCHEME) :])
|
||||||
assert (
|
assert (
|
||||||
result["title"]
|
result["title"]
|
||||||
== f"{mock_season.parentTitle} - {mock_season.title} ({mock_season.year})"
|
== f"{mock_season.parentTitle} - {mock_season.title} ({mock_season.year})"
|
||||||
@ -369,7 +360,7 @@ async def test_browse_media(
|
|||||||
|
|
||||||
assert msg["success"]
|
assert msg["success"]
|
||||||
result = msg["result"]
|
result = msg["result"]
|
||||||
result_id = int(result[ATTR_MEDIA_CONTENT_ID])
|
result_id = int(result[ATTR_MEDIA_CONTENT_ID][len(PLEX_URI_SCHEME) :])
|
||||||
assert result[ATTR_MEDIA_CONTENT_TYPE] == "library"
|
assert result[ATTR_MEDIA_CONTENT_TYPE] == "library"
|
||||||
assert result["title"] == "Music"
|
assert result["title"] == "Music"
|
||||||
|
|
||||||
@ -399,7 +390,7 @@ async def test_browse_media(
|
|||||||
assert mock_fetch.called
|
assert mock_fetch.called
|
||||||
assert msg["success"]
|
assert msg["success"]
|
||||||
result = msg["result"]
|
result = msg["result"]
|
||||||
result_id = int(result[ATTR_MEDIA_CONTENT_ID])
|
result_id = int(result[ATTR_MEDIA_CONTENT_ID][len(PLEX_URI_SCHEME) :])
|
||||||
assert result[ATTR_MEDIA_CONTENT_TYPE] == "artist"
|
assert result[ATTR_MEDIA_CONTENT_TYPE] == "artist"
|
||||||
assert result["title"] == mock_artist.title
|
assert result["title"] == mock_artist.title
|
||||||
assert result["children"][0]["title"] == "Radio Station"
|
assert result["children"][0]["title"] == "Radio Station"
|
||||||
@ -420,7 +411,7 @@ async def test_browse_media(
|
|||||||
|
|
||||||
assert msg["success"]
|
assert msg["success"]
|
||||||
result = msg["result"]
|
result = msg["result"]
|
||||||
result_id = int(result[ATTR_MEDIA_CONTENT_ID])
|
result_id = int(result[ATTR_MEDIA_CONTENT_ID][len(PLEX_URI_SCHEME) :])
|
||||||
assert result[ATTR_MEDIA_CONTENT_TYPE] == "album"
|
assert result[ATTR_MEDIA_CONTENT_TYPE] == "album"
|
||||||
assert (
|
assert (
|
||||||
result["title"]
|
result["title"]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user