mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Add children media class (#39902)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
e208aac834
commit
e96fed20c8
@ -29,8 +29,15 @@ PLAYABLE_MEDIA_TYPES = [
|
|||||||
MEDIA_TYPE_TRACK,
|
MEDIA_TYPE_TRACK,
|
||||||
]
|
]
|
||||||
|
|
||||||
CONTENT_TYPE_MEDIA_CLASS = {
|
CONTAINER_TYPES_SPECIFIC_MEDIA_CLASS = {
|
||||||
"library_music": MEDIA_CLASS_MUSIC,
|
MEDIA_TYPE_ALBUM: MEDIA_CLASS_ALBUM,
|
||||||
|
MEDIA_TYPE_ARTIST: MEDIA_CLASS_ARTIST,
|
||||||
|
MEDIA_TYPE_PLAYLIST: MEDIA_CLASS_PLAYLIST,
|
||||||
|
MEDIA_TYPE_SEASON: MEDIA_CLASS_SEASON,
|
||||||
|
MEDIA_TYPE_TVSHOW: MEDIA_CLASS_TV_SHOW,
|
||||||
|
}
|
||||||
|
|
||||||
|
CHILD_TYPE_MEDIA_CLASS = {
|
||||||
MEDIA_TYPE_SEASON: MEDIA_CLASS_SEASON,
|
MEDIA_TYPE_SEASON: MEDIA_CLASS_SEASON,
|
||||||
MEDIA_TYPE_ALBUM: MEDIA_CLASS_ALBUM,
|
MEDIA_TYPE_ALBUM: MEDIA_CLASS_ALBUM,
|
||||||
MEDIA_TYPE_ARTIST: MEDIA_CLASS_ARTIST,
|
MEDIA_TYPE_ARTIST: MEDIA_CLASS_ARTIST,
|
||||||
@ -151,8 +158,10 @@ async def build_item_response(media_library, payload):
|
|||||||
except UnknownMediaType:
|
except UnknownMediaType:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return BrowseMedia(
|
response = BrowseMedia(
|
||||||
media_class=CONTENT_TYPE_MEDIA_CLASS[search_type],
|
media_class=CONTAINER_TYPES_SPECIFIC_MEDIA_CLASS.get(
|
||||||
|
search_type, MEDIA_CLASS_DIRECTORY
|
||||||
|
),
|
||||||
media_content_id=search_id,
|
media_content_id=search_id,
|
||||||
media_content_type=search_type,
|
media_content_type=search_type,
|
||||||
title=title,
|
title=title,
|
||||||
@ -162,6 +171,13 @@ async def build_item_response(media_library, payload):
|
|||||||
thumbnail=thumbnail,
|
thumbnail=thumbnail,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if search_type == "library_music":
|
||||||
|
response.children_media_class = MEDIA_CLASS_MUSIC
|
||||||
|
else:
|
||||||
|
response.calculate_children_class()
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
def item_payload(item, media_library):
|
def item_payload(item, media_library):
|
||||||
"""
|
"""
|
||||||
@ -170,11 +186,12 @@ def item_payload(item, media_library):
|
|||||||
Used by async_browse_media.
|
Used by async_browse_media.
|
||||||
"""
|
"""
|
||||||
title = item["label"]
|
title = item["label"]
|
||||||
|
|
||||||
thumbnail = item.get("thumbnail")
|
thumbnail = item.get("thumbnail")
|
||||||
if thumbnail:
|
if thumbnail:
|
||||||
thumbnail = media_library.thumbnail_url(thumbnail)
|
thumbnail = media_library.thumbnail_url(thumbnail)
|
||||||
|
|
||||||
|
media_class = None
|
||||||
|
|
||||||
if "songid" in item:
|
if "songid" in item:
|
||||||
media_content_type = MEDIA_TYPE_TRACK
|
media_content_type = MEDIA_TYPE_TRACK
|
||||||
media_content_id = f"{item['songid']}"
|
media_content_id = f"{item['songid']}"
|
||||||
@ -213,16 +230,18 @@ def item_payload(item, media_library):
|
|||||||
else:
|
else:
|
||||||
# this case is for the top folder of each type
|
# this case is for the top folder of each type
|
||||||
# possible content types: album, artist, movie, library_music, tvshow
|
# possible content types: album, artist, movie, library_music, tvshow
|
||||||
|
media_class = MEDIA_CLASS_DIRECTORY
|
||||||
media_content_type = item["type"]
|
media_content_type = item["type"]
|
||||||
media_content_id = ""
|
media_content_id = ""
|
||||||
can_play = False
|
can_play = False
|
||||||
can_expand = True
|
can_expand = True
|
||||||
|
|
||||||
try:
|
if media_class is None:
|
||||||
media_class = CONTENT_TYPE_MEDIA_CLASS[media_content_type]
|
try:
|
||||||
except KeyError as err:
|
media_class = CHILD_TYPE_MEDIA_CLASS[media_content_type]
|
||||||
_LOGGER.debug("Unknown media type received: %s", media_content_type)
|
except KeyError as err:
|
||||||
raise UnknownMediaType from err
|
_LOGGER.debug("Unknown media type received: %s", media_content_type)
|
||||||
|
raise UnknownMediaType from err
|
||||||
|
|
||||||
return BrowseMedia(
|
return BrowseMedia(
|
||||||
title=title,
|
title=title,
|
||||||
|
@ -85,6 +85,7 @@ from .const import (
|
|||||||
ATTR_SOUND_MODE,
|
ATTR_SOUND_MODE,
|
||||||
ATTR_SOUND_MODE_LIST,
|
ATTR_SOUND_MODE_LIST,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
MEDIA_CLASS_DIRECTORY,
|
||||||
SERVICE_CLEAR_PLAYLIST,
|
SERVICE_CLEAR_PLAYLIST,
|
||||||
SERVICE_PLAY_MEDIA,
|
SERVICE_PLAY_MEDIA,
|
||||||
SERVICE_SELECT_SOUND_MODE,
|
SERVICE_SELECT_SOUND_MODE,
|
||||||
@ -816,24 +817,10 @@ class MediaPlayerEntity(Entity):
|
|||||||
media_content_type: Optional[str] = None,
|
media_content_type: Optional[str] = None,
|
||||||
media_content_id: Optional[str] = None,
|
media_content_id: Optional[str] = None,
|
||||||
) -> "BrowseMedia":
|
) -> "BrowseMedia":
|
||||||
"""
|
"""Return a BrowseMedia instance.
|
||||||
Return a payload for the "media_player/browse_media" websocket command.
|
|
||||||
|
|
||||||
Payload should follow this format:
|
The BrowseMedia instance will be used by the
|
||||||
{
|
"media_player/browse_media" websocket command.
|
||||||
"title": str - Title of the item
|
|
||||||
"media_class": str - Media class
|
|
||||||
"media_content_type": str - see below
|
|
||||||
"media_content_id": str - see below
|
|
||||||
- Can be passed back in to browse further
|
|
||||||
- Can be used as-is with media_player.play_media service
|
|
||||||
"can_play": bool - If item is playable
|
|
||||||
"can_expand": bool - If item contains other media
|
|
||||||
"thumbnail": str (Optional) - URL to image thumbnail for item
|
|
||||||
"children": list (Optional) - [{<item_with_keys_above>}, ...]
|
|
||||||
}
|
|
||||||
|
|
||||||
Note: Children should omit the children key.
|
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@ -1054,6 +1041,7 @@ class BrowseMedia:
|
|||||||
can_play: bool,
|
can_play: bool,
|
||||||
can_expand: bool,
|
can_expand: bool,
|
||||||
children: Optional[List["BrowseMedia"]] = None,
|
children: Optional[List["BrowseMedia"]] = None,
|
||||||
|
children_media_class: Optional[str] = None,
|
||||||
thumbnail: Optional[str] = None,
|
thumbnail: Optional[str] = None,
|
||||||
):
|
):
|
||||||
"""Initialize browse media item."""
|
"""Initialize browse media item."""
|
||||||
@ -1064,10 +1052,14 @@ class BrowseMedia:
|
|||||||
self.can_play = can_play
|
self.can_play = can_play
|
||||||
self.can_expand = can_expand
|
self.can_expand = can_expand
|
||||||
self.children = children
|
self.children = children
|
||||||
|
self.children_media_class = children_media_class
|
||||||
self.thumbnail = thumbnail
|
self.thumbnail = thumbnail
|
||||||
|
|
||||||
def as_dict(self, *, parent: bool = True) -> dict:
|
def as_dict(self, *, parent: bool = True) -> dict:
|
||||||
"""Convert Media class to browse media dictionary."""
|
"""Convert Media class to browse media dictionary."""
|
||||||
|
if self.children_media_class is None:
|
||||||
|
self.calculate_children_class()
|
||||||
|
|
||||||
response = {
|
response = {
|
||||||
"title": self.title,
|
"title": self.title,
|
||||||
"media_class": self.media_class,
|
"media_class": self.media_class,
|
||||||
@ -1075,6 +1067,7 @@ class BrowseMedia:
|
|||||||
"media_content_id": self.media_content_id,
|
"media_content_id": self.media_content_id,
|
||||||
"can_play": self.can_play,
|
"can_play": self.can_play,
|
||||||
"can_expand": self.can_expand,
|
"can_expand": self.can_expand,
|
||||||
|
"children_media_class": self.children_media_class,
|
||||||
"thumbnail": self.thumbnail,
|
"thumbnail": self.thumbnail,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1089,3 +1082,14 @@ class BrowseMedia:
|
|||||||
response["children"] = []
|
response["children"] = []
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
def calculate_children_class(self) -> None:
|
||||||
|
"""Count the children media classes and calculate the correct class."""
|
||||||
|
if self.children is None or len(self.children) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.children_media_class = MEDIA_CLASS_DIRECTORY
|
||||||
|
|
||||||
|
proposed_class = self.children[0].media_class
|
||||||
|
if all(child.media_class == proposed_class for child in self.children):
|
||||||
|
self.children_media_class = proposed_class
|
||||||
|
@ -6,6 +6,7 @@ from typing import List, Optional, Tuple
|
|||||||
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_CHANNEL,
|
MEDIA_CLASS_CHANNEL,
|
||||||
|
MEDIA_CLASS_DIRECTORY,
|
||||||
MEDIA_TYPE_CHANNEL,
|
MEDIA_TYPE_CHANNEL,
|
||||||
MEDIA_TYPE_CHANNELS,
|
MEDIA_TYPE_CHANNELS,
|
||||||
)
|
)
|
||||||
@ -53,11 +54,12 @@ class MediaSourceItem:
|
|||||||
base = BrowseMediaSource(
|
base = BrowseMediaSource(
|
||||||
domain=None,
|
domain=None,
|
||||||
identifier=None,
|
identifier=None,
|
||||||
media_class=MEDIA_CLASS_CHANNEL,
|
media_class=MEDIA_CLASS_DIRECTORY,
|
||||||
media_content_type=MEDIA_TYPE_CHANNELS,
|
media_content_type=MEDIA_TYPE_CHANNELS,
|
||||||
title="Media Sources",
|
title="Media Sources",
|
||||||
can_play=False,
|
can_play=False,
|
||||||
can_expand=True,
|
can_expand=True,
|
||||||
|
children_media_class=MEDIA_CLASS_CHANNEL,
|
||||||
)
|
)
|
||||||
base.children = [
|
base.children = [
|
||||||
BrowseMediaSource(
|
BrowseMediaSource(
|
||||||
|
@ -5,6 +5,7 @@ import re
|
|||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
from homeassistant.components.media_player.const import (
|
from homeassistant.components.media_player.const import (
|
||||||
|
MEDIA_CLASS_DIRECTORY,
|
||||||
MEDIA_CLASS_VIDEO,
|
MEDIA_CLASS_VIDEO,
|
||||||
MEDIA_TYPE_VIDEO,
|
MEDIA_TYPE_VIDEO,
|
||||||
)
|
)
|
||||||
@ -91,10 +92,12 @@ class NetatmoSource(MediaSource):
|
|||||||
else:
|
else:
|
||||||
path = f"{source}/{camera_id}"
|
path = f"{source}/{camera_id}"
|
||||||
|
|
||||||
|
media_class = MEDIA_CLASS_DIRECTORY if event_id is None else MEDIA_CLASS_VIDEO
|
||||||
|
|
||||||
media = BrowseMediaSource(
|
media = BrowseMediaSource(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
identifier=path,
|
identifier=path,
|
||||||
media_class=MEDIA_CLASS_VIDEO,
|
media_class=media_class,
|
||||||
media_content_type=MEDIA_TYPE_VIDEO,
|
media_content_type=MEDIA_TYPE_VIDEO,
|
||||||
title=title,
|
title=title,
|
||||||
can_play=bool(
|
can_play=bool(
|
||||||
|
@ -12,6 +12,7 @@ from homeassistant.components.media_player import (
|
|||||||
)
|
)
|
||||||
from homeassistant.components.media_player.const import (
|
from homeassistant.components.media_player.const import (
|
||||||
MEDIA_CLASS_CHANNEL,
|
MEDIA_CLASS_CHANNEL,
|
||||||
|
MEDIA_CLASS_DIRECTORY,
|
||||||
MEDIA_TYPE_CHANNEL,
|
MEDIA_TYPE_CHANNEL,
|
||||||
MEDIA_TYPE_CHANNELS,
|
MEDIA_TYPE_CHANNELS,
|
||||||
SUPPORT_BROWSE_MEDIA,
|
SUPPORT_BROWSE_MEDIA,
|
||||||
@ -289,7 +290,7 @@ class PhilipsTVMediaPlayer(MediaPlayerEntity):
|
|||||||
|
|
||||||
return BrowseMedia(
|
return BrowseMedia(
|
||||||
title="Channels",
|
title="Channels",
|
||||||
media_class=MEDIA_CLASS_CHANNEL,
|
media_class=MEDIA_CLASS_DIRECTORY,
|
||||||
media_content_id="",
|
media_content_id="",
|
||||||
media_content_type=MEDIA_TYPE_CHANNELS,
|
media_content_type=MEDIA_TYPE_CHANNELS,
|
||||||
can_play=False,
|
can_play=False,
|
||||||
|
@ -26,7 +26,7 @@ class UnknownMediaType(BrowseError):
|
|||||||
EXPANDABLES = ["album", "artist", "playlist", "season", "show"]
|
EXPANDABLES = ["album", "artist", "playlist", "season", "show"]
|
||||||
PLAYLISTS_BROWSE_PAYLOAD = {
|
PLAYLISTS_BROWSE_PAYLOAD = {
|
||||||
"title": "Playlists",
|
"title": "Playlists",
|
||||||
"media_class": MEDIA_CLASS_PLAYLIST,
|
"media_class": MEDIA_CLASS_DIRECTORY,
|
||||||
"media_content_id": "all",
|
"media_content_id": "all",
|
||||||
"media_content_type": "playlists",
|
"media_content_type": "playlists",
|
||||||
"can_play": False,
|
"can_play": False,
|
||||||
@ -94,10 +94,21 @@ def browse_media(
|
|||||||
if special_folder:
|
if special_folder:
|
||||||
if media_content_type == "server":
|
if media_content_type == "server":
|
||||||
library_or_section = plex_server.library
|
library_or_section = plex_server.library
|
||||||
|
children_media_class = MEDIA_CLASS_DIRECTORY
|
||||||
title = plex_server.friendly_name
|
title = plex_server.friendly_name
|
||||||
elif media_content_type == "library":
|
elif media_content_type == "library":
|
||||||
library_or_section = plex_server.library.sectionByID(media_content_id)
|
library_or_section = plex_server.library.sectionByID(media_content_id)
|
||||||
title = library_or_section.title
|
title = library_or_section.title
|
||||||
|
try:
|
||||||
|
children_media_class = ITEM_TYPE_MEDIA_CLASS[library_or_section.TYPE]
|
||||||
|
except KeyError as err:
|
||||||
|
raise BrowseError(
|
||||||
|
f"Media not found: {media_content_type} / {media_content_id}"
|
||||||
|
) from err
|
||||||
|
else:
|
||||||
|
raise BrowseError(
|
||||||
|
f"Media not found: {media_content_type} / {media_content_id}"
|
||||||
|
)
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"title": title,
|
"title": title,
|
||||||
@ -107,6 +118,7 @@ def browse_media(
|
|||||||
"can_play": False,
|
"can_play": False,
|
||||||
"can_expand": True,
|
"can_expand": True,
|
||||||
"children": [],
|
"children": [],
|
||||||
|
"children_media_class": children_media_class,
|
||||||
}
|
}
|
||||||
|
|
||||||
method = SPECIAL_METHODS[special_folder]
|
method = SPECIAL_METHODS[special_folder]
|
||||||
@ -116,13 +128,20 @@ def browse_media(
|
|||||||
payload["children"].append(item_payload(item))
|
payload["children"].append(item_payload(item))
|
||||||
except UnknownMediaType:
|
except UnknownMediaType:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
return BrowseMedia(**payload)
|
return BrowseMedia(**payload)
|
||||||
|
|
||||||
if media_content_type in ["server", None]:
|
try:
|
||||||
return server_payload(plex_server)
|
if media_content_type in ["server", None]:
|
||||||
|
return server_payload(plex_server)
|
||||||
|
|
||||||
if media_content_type == "library":
|
if media_content_type == "library":
|
||||||
return library_payload(plex_server, media_content_id)
|
return library_payload(plex_server, media_content_id)
|
||||||
|
|
||||||
|
except UnknownMediaType as err:
|
||||||
|
raise BrowseError(
|
||||||
|
f"Media not found: {media_content_type} / {media_content_id}"
|
||||||
|
) from err
|
||||||
|
|
||||||
if media_content_type == "playlists":
|
if media_content_type == "playlists":
|
||||||
return playlists_payload(plex_server)
|
return playlists_payload(plex_server)
|
||||||
@ -160,6 +179,11 @@ def item_payload(item):
|
|||||||
|
|
||||||
def library_section_payload(section):
|
def library_section_payload(section):
|
||||||
"""Create response payload for a single library section."""
|
"""Create response payload for a single library section."""
|
||||||
|
try:
|
||||||
|
children_media_class = ITEM_TYPE_MEDIA_CLASS[section.TYPE]
|
||||||
|
except KeyError as err:
|
||||||
|
_LOGGER.debug("Unknown type received: %s", section.TYPE)
|
||||||
|
raise UnknownMediaType from err
|
||||||
return BrowseMedia(
|
return BrowseMedia(
|
||||||
title=section.title,
|
title=section.title,
|
||||||
media_class=MEDIA_CLASS_DIRECTORY,
|
media_class=MEDIA_CLASS_DIRECTORY,
|
||||||
@ -167,6 +191,7 @@ def library_section_payload(section):
|
|||||||
media_content_type="library",
|
media_content_type="library",
|
||||||
can_play=False,
|
can_play=False,
|
||||||
can_expand=True,
|
can_expand=True,
|
||||||
|
children_media_class=children_media_class,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -194,6 +219,7 @@ def server_payload(plex_server):
|
|||||||
can_expand=True,
|
can_expand=True,
|
||||||
)
|
)
|
||||||
server_info.children = []
|
server_info.children = []
|
||||||
|
server_info.children_media_class = MEDIA_CLASS_DIRECTORY
|
||||||
server_info.children.append(special_library_payload(server_info, "On Deck"))
|
server_info.children.append(special_library_payload(server_info, "On Deck"))
|
||||||
server_info.children.append(special_library_payload(server_info, "Recently Added"))
|
server_info.children.append(special_library_payload(server_info, "Recently Added"))
|
||||||
for library in plex_server.library.sections():
|
for library in plex_server.library.sections():
|
||||||
@ -229,4 +255,6 @@ def playlists_payload(plex_server):
|
|||||||
playlists_info["children"].append(item_payload(playlist))
|
playlists_info["children"].append(item_payload(playlist))
|
||||||
except UnknownMediaType:
|
except UnknownMediaType:
|
||||||
continue
|
continue
|
||||||
return BrowseMedia(**playlists_info)
|
response = BrowseMedia(**playlists_info)
|
||||||
|
response.children_media_class = MEDIA_CLASS_PLAYLIST
|
||||||
|
return response
|
||||||
|
@ -13,9 +13,9 @@ from homeassistant.components.media_player.const import (
|
|||||||
|
|
||||||
CONTENT_TYPE_MEDIA_CLASS = {
|
CONTENT_TYPE_MEDIA_CLASS = {
|
||||||
MEDIA_TYPE_APP: MEDIA_CLASS_APP,
|
MEDIA_TYPE_APP: MEDIA_CLASS_APP,
|
||||||
MEDIA_TYPE_APPS: MEDIA_CLASS_APP,
|
MEDIA_TYPE_APPS: MEDIA_CLASS_DIRECTORY,
|
||||||
MEDIA_TYPE_CHANNEL: MEDIA_CLASS_CHANNEL,
|
MEDIA_TYPE_CHANNEL: MEDIA_CLASS_CHANNEL,
|
||||||
MEDIA_TYPE_CHANNELS: MEDIA_CLASS_CHANNEL,
|
MEDIA_TYPE_CHANNELS: MEDIA_CLASS_DIRECTORY,
|
||||||
}
|
}
|
||||||
|
|
||||||
PLAYABLE_MEDIA_TYPES = [
|
PLAYABLE_MEDIA_TYPES = [
|
||||||
@ -59,7 +59,7 @@ def build_item_response(coordinator, payload):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
return BrowseMedia(
|
return BrowseMedia(
|
||||||
media_class=CONTENT_TYPE_MEDIA_CLASS[search_type],
|
media_class=MEDIA_CLASS_DIRECTORY,
|
||||||
media_content_id=search_id,
|
media_content_id=search_id,
|
||||||
media_content_type=search_type,
|
media_content_type=search_type,
|
||||||
title=title,
|
title=title,
|
||||||
@ -139,4 +139,16 @@ def library_payload(coordinator):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if all(
|
||||||
|
child.media_content_type == MEDIA_TYPE_APPS for child in library_info.children
|
||||||
|
):
|
||||||
|
library_info.children_media_class = MEDIA_CLASS_APP
|
||||||
|
elif all(
|
||||||
|
child.media_content_type == MEDIA_TYPE_CHANNELS
|
||||||
|
for child in library_info.children
|
||||||
|
):
|
||||||
|
library_info.children_media_class = MEDIA_CLASS_CHANNEL
|
||||||
|
else:
|
||||||
|
library_info.children_media_class = MEDIA_CLASS_DIRECTORY
|
||||||
|
|
||||||
return library_info
|
return library_info
|
||||||
|
@ -222,6 +222,10 @@ ATTR_STATUS_LIGHT = "status_light"
|
|||||||
UNAVAILABLE_VALUES = {"", "NOT_IMPLEMENTED", None}
|
UNAVAILABLE_VALUES = {"", "NOT_IMPLEMENTED", None}
|
||||||
|
|
||||||
|
|
||||||
|
class UnknownMediaType(BrowseError):
|
||||||
|
"""Unknown media type."""
|
||||||
|
|
||||||
|
|
||||||
class SonosData:
|
class SonosData:
|
||||||
"""Storage class for platform global data."""
|
"""Storage class for platform global data."""
|
||||||
|
|
||||||
@ -1487,7 +1491,20 @@ def build_item_response(media_library, payload):
|
|||||||
except IndexError:
|
except IndexError:
|
||||||
title = LIBRARY_TITLES_MAPPING[payload["idstring"]]
|
title = LIBRARY_TITLES_MAPPING[payload["idstring"]]
|
||||||
|
|
||||||
media_class = SONOS_TO_MEDIA_CLASSES[MEDIA_TYPES_TO_SONOS[payload["search_type"]]]
|
try:
|
||||||
|
media_class = SONOS_TO_MEDIA_CLASSES[
|
||||||
|
MEDIA_TYPES_TO_SONOS[payload["search_type"]]
|
||||||
|
]
|
||||||
|
except KeyError:
|
||||||
|
_LOGGER.debug("Unknown media type received %s", payload["search_type"])
|
||||||
|
return None
|
||||||
|
|
||||||
|
children = []
|
||||||
|
for item in media:
|
||||||
|
try:
|
||||||
|
children.append(item_payload(item))
|
||||||
|
except UnknownMediaType:
|
||||||
|
pass
|
||||||
|
|
||||||
return BrowseMedia(
|
return BrowseMedia(
|
||||||
title=title,
|
title=title,
|
||||||
@ -1495,7 +1512,7 @@ def build_item_response(media_library, payload):
|
|||||||
media_class=media_class,
|
media_class=media_class,
|
||||||
media_content_id=payload["idstring"],
|
media_content_id=payload["idstring"],
|
||||||
media_content_type=payload["search_type"],
|
media_content_type=payload["search_type"],
|
||||||
children=[item_payload(item) for item in media],
|
children=children,
|
||||||
can_play=can_play(payload["search_type"]),
|
can_play=can_play(payload["search_type"]),
|
||||||
can_expand=can_expand(payload["search_type"]),
|
can_expand=can_expand(payload["search_type"]),
|
||||||
)
|
)
|
||||||
@ -1507,12 +1524,18 @@ def item_payload(item):
|
|||||||
|
|
||||||
Used by async_browse_media.
|
Used by async_browse_media.
|
||||||
"""
|
"""
|
||||||
|
media_type = get_media_type(item)
|
||||||
|
try:
|
||||||
|
media_class = SONOS_TO_MEDIA_CLASSES[media_type]
|
||||||
|
except KeyError as err:
|
||||||
|
_LOGGER.debug("Unknown media type received %s", media_type)
|
||||||
|
raise UnknownMediaType from err
|
||||||
return BrowseMedia(
|
return BrowseMedia(
|
||||||
title=item.title,
|
title=item.title,
|
||||||
thumbnail=getattr(item, "album_art_uri", None),
|
thumbnail=getattr(item, "album_art_uri", None),
|
||||||
media_class=SONOS_TO_MEDIA_CLASSES[get_media_type(item)],
|
media_class=media_class,
|
||||||
media_content_id=get_content_id(item),
|
media_content_id=get_content_id(item),
|
||||||
media_content_type=SONOS_TO_MEDIA_TYPES[get_media_type(item)],
|
media_content_type=SONOS_TO_MEDIA_TYPES[media_type],
|
||||||
can_play=can_play(item.item_class),
|
can_play=can_play(item.item_class),
|
||||||
can_expand=can_expand(item),
|
can_expand=can_expand(item),
|
||||||
)
|
)
|
||||||
@ -1524,6 +1547,13 @@ def library_payload(media_library):
|
|||||||
|
|
||||||
Used by async_browse_media.
|
Used by async_browse_media.
|
||||||
"""
|
"""
|
||||||
|
children = []
|
||||||
|
for item in media_library.browse():
|
||||||
|
try:
|
||||||
|
children.append(item_payload(item))
|
||||||
|
except UnknownMediaType:
|
||||||
|
pass
|
||||||
|
|
||||||
return BrowseMedia(
|
return BrowseMedia(
|
||||||
title="Music Library",
|
title="Music Library",
|
||||||
media_class=MEDIA_CLASS_DIRECTORY,
|
media_class=MEDIA_CLASS_DIRECTORY,
|
||||||
@ -1531,7 +1561,7 @@ def library_payload(media_library):
|
|||||||
media_content_type="library",
|
media_content_type="library",
|
||||||
can_play=False,
|
can_play=False,
|
||||||
can_expand=True,
|
can_expand=True,
|
||||||
children=[item_payload(item) for item in media_library.browse()],
|
children=children,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,18 +105,18 @@ LIBRARY_MAP = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CONTENT_TYPE_MEDIA_CLASS = {
|
CONTENT_TYPE_MEDIA_CLASS = {
|
||||||
"current_user_playlists": MEDIA_CLASS_PLAYLIST,
|
"current_user_playlists": MEDIA_CLASS_DIRECTORY,
|
||||||
"current_user_followed_artists": MEDIA_CLASS_ARTIST,
|
"current_user_followed_artists": MEDIA_CLASS_DIRECTORY,
|
||||||
"current_user_saved_albums": MEDIA_CLASS_ALBUM,
|
"current_user_saved_albums": MEDIA_CLASS_DIRECTORY,
|
||||||
"current_user_saved_tracks": MEDIA_CLASS_TRACK,
|
"current_user_saved_tracks": MEDIA_CLASS_DIRECTORY,
|
||||||
"current_user_saved_shows": MEDIA_CLASS_PODCAST,
|
"current_user_saved_shows": MEDIA_CLASS_DIRECTORY,
|
||||||
"current_user_recently_played": MEDIA_CLASS_TRACK,
|
"current_user_recently_played": MEDIA_CLASS_DIRECTORY,
|
||||||
"current_user_top_artists": MEDIA_CLASS_ARTIST,
|
"current_user_top_artists": MEDIA_CLASS_DIRECTORY,
|
||||||
"current_user_top_tracks": MEDIA_CLASS_TRACK,
|
"current_user_top_tracks": MEDIA_CLASS_DIRECTORY,
|
||||||
"featured_playlists": MEDIA_CLASS_PLAYLIST,
|
"featured_playlists": MEDIA_CLASS_DIRECTORY,
|
||||||
"categories": MEDIA_CLASS_GENRE,
|
"categories": MEDIA_CLASS_DIRECTORY,
|
||||||
"category_playlists": MEDIA_CLASS_PLAYLIST,
|
"category_playlists": MEDIA_CLASS_DIRECTORY,
|
||||||
"new_releases": MEDIA_CLASS_ALBUM,
|
"new_releases": MEDIA_CLASS_DIRECTORY,
|
||||||
MEDIA_TYPE_PLAYLIST: MEDIA_CLASS_PLAYLIST,
|
MEDIA_TYPE_PLAYLIST: MEDIA_CLASS_PLAYLIST,
|
||||||
MEDIA_TYPE_ALBUM: MEDIA_CLASS_ALBUM,
|
MEDIA_TYPE_ALBUM: MEDIA_CLASS_ALBUM,
|
||||||
MEDIA_TYPE_ARTIST: MEDIA_CLASS_ARTIST,
|
MEDIA_TYPE_ARTIST: MEDIA_CLASS_ARTIST,
|
||||||
@ -567,6 +567,7 @@ def build_item_response(spotify, user, payload):
|
|||||||
can_expand=True,
|
can_expand=True,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
media_item.children_media_class = MEDIA_CLASS_GENRE
|
||||||
return media_item
|
return media_item
|
||||||
|
|
||||||
if title is None:
|
if title is None:
|
||||||
@ -575,7 +576,7 @@ def build_item_response(spotify, user, payload):
|
|||||||
else:
|
else:
|
||||||
title = LIBRARY_MAP.get(payload["media_content_id"])
|
title = LIBRARY_MAP.get(payload["media_content_id"])
|
||||||
|
|
||||||
response = {
|
params = {
|
||||||
"title": title,
|
"title": title,
|
||||||
"media_class": media_class,
|
"media_class": media_class,
|
||||||
"media_content_id": media_content_id,
|
"media_content_id": media_content_id,
|
||||||
@ -586,16 +587,16 @@ def build_item_response(spotify, user, payload):
|
|||||||
}
|
}
|
||||||
for item in items:
|
for item in items:
|
||||||
try:
|
try:
|
||||||
response["children"].append(item_payload(item))
|
params["children"].append(item_payload(item))
|
||||||
except (MissingMediaInformation, UnknownMediaType):
|
except (MissingMediaInformation, UnknownMediaType):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if "images" in media:
|
if "images" in media:
|
||||||
response["thumbnail"] = fetch_image_url(media)
|
params["thumbnail"] = fetch_image_url(media)
|
||||||
elif image:
|
elif image:
|
||||||
response["thumbnail"] = image
|
params["thumbnail"] = image
|
||||||
|
|
||||||
return BrowseMedia(**response)
|
return BrowseMedia(**params)
|
||||||
|
|
||||||
|
|
||||||
def item_payload(item):
|
def item_payload(item):
|
||||||
@ -624,17 +625,13 @@ def item_payload(item):
|
|||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"title": item.get("name"),
|
"title": item.get("name"),
|
||||||
|
"media_class": media_class,
|
||||||
"media_content_id": media_id,
|
"media_content_id": media_id,
|
||||||
"media_content_type": media_type,
|
"media_content_type": media_type,
|
||||||
"can_play": media_type in PLAYABLE_MEDIA_TYPES,
|
"can_play": media_type in PLAYABLE_MEDIA_TYPES,
|
||||||
"can_expand": can_expand,
|
"can_expand": can_expand,
|
||||||
}
|
}
|
||||||
|
|
||||||
payload = {
|
|
||||||
**payload,
|
|
||||||
"media_class": media_class,
|
|
||||||
}
|
|
||||||
|
|
||||||
if "images" in item:
|
if "images" in item:
|
||||||
payload["thumbnail"] = fetch_image_url(item)
|
payload["thumbnail"] = fetch_image_url(item)
|
||||||
elif MEDIA_TYPE_ALBUM in item:
|
elif MEDIA_TYPE_ALBUM in item:
|
||||||
@ -665,7 +662,9 @@ def library_payload():
|
|||||||
{"name": item["name"], "type": item["type"], "uri": item["type"]}
|
{"name": item["name"], "type": item["type"], "uri": item["type"]}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return BrowseMedia(**library_info)
|
response = BrowseMedia(**library_info)
|
||||||
|
response.children_media_class = MEDIA_CLASS_DIRECTORY
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
def fetch_image_url(item, key="images"):
|
def fetch_image_url(item, key="images"):
|
||||||
|
@ -17,6 +17,7 @@ async def test_browse_media_as_dict():
|
|||||||
title="media/",
|
title="media/",
|
||||||
can_play=False,
|
can_play=False,
|
||||||
can_expand=True,
|
can_expand=True,
|
||||||
|
children_media_class=MEDIA_CLASS_MUSIC,
|
||||||
)
|
)
|
||||||
base.children = [
|
base.children = [
|
||||||
models.BrowseMediaSource(
|
models.BrowseMediaSource(
|
||||||
@ -37,6 +38,7 @@ async def test_browse_media_as_dict():
|
|||||||
assert item["media_content_id"] == f"{const.URI_SCHEME}{const.DOMAIN}/media"
|
assert item["media_content_id"] == f"{const.URI_SCHEME}{const.DOMAIN}/media"
|
||||||
assert not item["can_play"]
|
assert not item["can_play"]
|
||||||
assert item["can_expand"]
|
assert item["can_expand"]
|
||||||
|
assert item["children_media_class"] == MEDIA_CLASS_MUSIC
|
||||||
assert len(item["children"]) == 1
|
assert len(item["children"]) == 1
|
||||||
assert item["children"][0]["title"] == "test.mp3"
|
assert item["children"][0]["title"] == "test.mp3"
|
||||||
assert item["children"][0]["media_class"] == MEDIA_CLASS_MUSIC
|
assert item["children"][0]["media_class"] == MEDIA_CLASS_MUSIC
|
||||||
@ -62,6 +64,7 @@ async def test_browse_media_parent_no_children():
|
|||||||
assert not item["can_play"]
|
assert not item["can_play"]
|
||||||
assert item["can_expand"]
|
assert item["can_expand"]
|
||||||
assert len(item["children"]) == 0
|
assert len(item["children"]) == 0
|
||||||
|
assert item["children_media_class"] is None
|
||||||
|
|
||||||
|
|
||||||
async def test_media_source_default_name():
|
async def test_media_source_default_name():
|
||||||
|
@ -409,6 +409,11 @@ class MockPlexLibrarySection:
|
|||||||
if self.title == "Photos":
|
if self.title == "Photos":
|
||||||
return "photo"
|
return "photo"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def TYPE(self):
|
||||||
|
"""Return the library type."""
|
||||||
|
return self.type
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def key(self):
|
def key(self):
|
||||||
"""Mock the key identifier property."""
|
"""Mock the key identifier property."""
|
||||||
|
@ -16,6 +16,9 @@ from homeassistant.components.media_player.const import (
|
|||||||
ATTR_MEDIA_TITLE,
|
ATTR_MEDIA_TITLE,
|
||||||
ATTR_MEDIA_VOLUME_MUTED,
|
ATTR_MEDIA_VOLUME_MUTED,
|
||||||
DOMAIN as MP_DOMAIN,
|
DOMAIN as MP_DOMAIN,
|
||||||
|
MEDIA_CLASS_APP,
|
||||||
|
MEDIA_CLASS_CHANNEL,
|
||||||
|
MEDIA_CLASS_DIRECTORY,
|
||||||
MEDIA_TYPE_APP,
|
MEDIA_TYPE_APP,
|
||||||
MEDIA_TYPE_APPS,
|
MEDIA_TYPE_APPS,
|
||||||
MEDIA_TYPE_CHANNEL,
|
MEDIA_TYPE_CHANNEL,
|
||||||
@ -499,6 +502,7 @@ async def test_media_browse(hass, aioclient_mock, hass_ws_client):
|
|||||||
|
|
||||||
assert msg["result"]
|
assert msg["result"]
|
||||||
assert msg["result"]["title"] == "Media Library"
|
assert msg["result"]["title"] == "Media Library"
|
||||||
|
assert msg["result"]["media_class"] == MEDIA_CLASS_DIRECTORY
|
||||||
assert msg["result"]["media_content_type"] == "library"
|
assert msg["result"]["media_content_type"] == "library"
|
||||||
assert msg["result"]["can_expand"]
|
assert msg["result"]["can_expand"]
|
||||||
assert not msg["result"]["can_play"]
|
assert not msg["result"]["can_play"]
|
||||||
@ -523,10 +527,12 @@ async def test_media_browse(hass, aioclient_mock, hass_ws_client):
|
|||||||
|
|
||||||
assert msg["result"]
|
assert msg["result"]
|
||||||
assert msg["result"]["title"] == "Apps"
|
assert msg["result"]["title"] == "Apps"
|
||||||
|
assert msg["result"]["media_class"] == MEDIA_CLASS_DIRECTORY
|
||||||
assert msg["result"]["media_content_type"] == MEDIA_TYPE_APPS
|
assert msg["result"]["media_content_type"] == MEDIA_TYPE_APPS
|
||||||
assert msg["result"]["can_expand"]
|
assert msg["result"]["can_expand"]
|
||||||
assert not msg["result"]["can_play"]
|
assert not msg["result"]["can_play"]
|
||||||
assert len(msg["result"]["children"]) == 11
|
assert len(msg["result"]["children"]) == 11
|
||||||
|
assert msg["result"]["children_media_class"] == MEDIA_CLASS_APP
|
||||||
|
|
||||||
assert msg["result"]["children"][0]["title"] == "Satellite TV"
|
assert msg["result"]["children"][0]["title"] == "Satellite TV"
|
||||||
assert msg["result"]["children"][0]["media_content_type"] == MEDIA_TYPE_APP
|
assert msg["result"]["children"][0]["media_content_type"] == MEDIA_TYPE_APP
|
||||||
@ -565,10 +571,12 @@ async def test_media_browse(hass, aioclient_mock, hass_ws_client):
|
|||||||
|
|
||||||
assert msg["result"]
|
assert msg["result"]
|
||||||
assert msg["result"]["title"] == "Channels"
|
assert msg["result"]["title"] == "Channels"
|
||||||
|
assert msg["result"]["media_class"] == MEDIA_CLASS_DIRECTORY
|
||||||
assert msg["result"]["media_content_type"] == MEDIA_TYPE_CHANNELS
|
assert msg["result"]["media_content_type"] == MEDIA_TYPE_CHANNELS
|
||||||
assert msg["result"]["can_expand"]
|
assert msg["result"]["can_expand"]
|
||||||
assert not msg["result"]["can_play"]
|
assert not msg["result"]["can_play"]
|
||||||
assert len(msg["result"]["children"]) == 2
|
assert len(msg["result"]["children"]) == 2
|
||||||
|
assert msg["result"]["children_media_class"] == MEDIA_CLASS_CHANNEL
|
||||||
|
|
||||||
assert msg["result"]["children"][0]["title"] == "WhatsOn"
|
assert msg["result"]["children"][0]["title"] == "WhatsOn"
|
||||||
assert msg["result"]["children"][0]["media_content_type"] == MEDIA_TYPE_CHANNEL
|
assert msg["result"]["children"][0]["media_content_type"] == MEDIA_TYPE_CHANNEL
|
||||||
|
Loading…
x
Reference in New Issue
Block a user