Add media class browse media attribute (#39770)
This commit is contained in:
parent
f075823529
commit
586d7eaba6
14 changed files with 248 additions and 18 deletions
|
@ -7,6 +7,8 @@ from arcam.fmj.state import State
|
|||
from homeassistant import config_entries
|
||||
from homeassistant.components.media_player import BrowseMedia, MediaPlayerEntity
|
||||
from homeassistant.components.media_player.const import (
|
||||
MEDIA_CLASS_DIRECTORY,
|
||||
MEDIA_CLASS_MUSIC,
|
||||
MEDIA_TYPE_MUSIC,
|
||||
SUPPORT_BROWSE_MEDIA,
|
||||
SUPPORT_PLAY_MEDIA,
|
||||
|
@ -255,6 +257,7 @@ class ArcamFmj(MediaPlayerEntity):
|
|||
radio = [
|
||||
BrowseMedia(
|
||||
title=preset.name,
|
||||
media_class=MEDIA_CLASS_MUSIC,
|
||||
media_content_id=f"preset:{preset.index}",
|
||||
media_content_type=MEDIA_TYPE_MUSIC,
|
||||
can_play=True,
|
||||
|
@ -265,6 +268,7 @@ class ArcamFmj(MediaPlayerEntity):
|
|||
|
||||
root = BrowseMedia(
|
||||
title="Root",
|
||||
media_class=MEDIA_CLASS_DIRECTORY,
|
||||
media_content_id="root",
|
||||
media_content_type="library",
|
||||
can_play=False,
|
||||
|
|
|
@ -2,6 +2,14 @@
|
|||
|
||||
from homeassistant.components.media_player import BrowseMedia
|
||||
from homeassistant.components.media_player.const import (
|
||||
MEDIA_CLASS_ALBUM,
|
||||
MEDIA_CLASS_ARTIST,
|
||||
MEDIA_CLASS_DIRECTORY,
|
||||
MEDIA_CLASS_MOVIE,
|
||||
MEDIA_CLASS_MUSIC,
|
||||
MEDIA_CLASS_PLAYLIST,
|
||||
MEDIA_CLASS_SEASON,
|
||||
MEDIA_CLASS_TV_SHOW,
|
||||
MEDIA_TYPE_ALBUM,
|
||||
MEDIA_TYPE_ARTIST,
|
||||
MEDIA_TYPE_EPISODE,
|
||||
|
@ -26,6 +34,16 @@ EXPANDABLE_MEDIA_TYPES = [
|
|||
MEDIA_TYPE_SEASON,
|
||||
]
|
||||
|
||||
CONTENT_TYPE_MEDIA_CLASS = {
|
||||
"library_music": MEDIA_CLASS_MUSIC,
|
||||
MEDIA_TYPE_SEASON: MEDIA_CLASS_SEASON,
|
||||
MEDIA_TYPE_ALBUM: MEDIA_CLASS_ALBUM,
|
||||
MEDIA_TYPE_ARTIST: MEDIA_CLASS_ARTIST,
|
||||
MEDIA_TYPE_MOVIE: MEDIA_CLASS_MOVIE,
|
||||
MEDIA_TYPE_PLAYLIST: MEDIA_CLASS_PLAYLIST,
|
||||
MEDIA_TYPE_TVSHOW: MEDIA_CLASS_TV_SHOW,
|
||||
}
|
||||
|
||||
|
||||
async def build_item_response(media_library, payload):
|
||||
"""Create response payload for the provided media query."""
|
||||
|
@ -124,6 +142,7 @@ async def build_item_response(media_library, payload):
|
|||
return
|
||||
|
||||
return BrowseMedia(
|
||||
media_class=CONTENT_TYPE_MEDIA_CLASS[search_type],
|
||||
media_content_id=payload["search_id"],
|
||||
media_content_type=search_type,
|
||||
title=title,
|
||||
|
@ -177,6 +196,7 @@ def item_payload(item, media_library):
|
|||
|
||||
return BrowseMedia(
|
||||
title=title,
|
||||
media_class=CONTENT_TYPE_MEDIA_CLASS[item["type"]],
|
||||
media_content_type=media_content_type,
|
||||
media_content_id=media_content_id,
|
||||
can_play=can_play,
|
||||
|
@ -192,6 +212,7 @@ def library_payload(media_library):
|
|||
Used by async_browse_media.
|
||||
"""
|
||||
library_info = BrowseMedia(
|
||||
media_class=MEDIA_CLASS_DIRECTORY,
|
||||
media_content_id="library",
|
||||
media_content_type="library",
|
||||
title="Media Library",
|
||||
|
|
|
@ -822,6 +822,7 @@ class MediaPlayerEntity(Entity):
|
|||
Payload should follow this format:
|
||||
{
|
||||
"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
|
||||
|
@ -1046,6 +1047,7 @@ class BrowseMedia:
|
|||
def __init__(
|
||||
self,
|
||||
*,
|
||||
media_class: str,
|
||||
media_content_id: str,
|
||||
media_content_type: str,
|
||||
title: str,
|
||||
|
@ -1055,6 +1057,7 @@ class BrowseMedia:
|
|||
thumbnail: Optional[str] = None,
|
||||
):
|
||||
"""Initialize browse media item."""
|
||||
self.media_class = media_class
|
||||
self.media_content_id = media_content_id
|
||||
self.media_content_type = media_content_type
|
||||
self.title = title
|
||||
|
@ -1067,6 +1070,7 @@ class BrowseMedia:
|
|||
"""Convert Media class to browse media dictionary."""
|
||||
response = {
|
||||
"title": self.title,
|
||||
"media_class": self.media_class,
|
||||
"media_content_type": self.media_content_type,
|
||||
"media_content_id": self.media_content_id,
|
||||
"can_play": self.can_play,
|
||||
|
|
|
@ -29,6 +29,29 @@ ATTR_SOUND_MODE_LIST = "sound_mode_list"
|
|||
|
||||
DOMAIN = "media_player"
|
||||
|
||||
MEDIA_CLASS_ALBUM = "album"
|
||||
MEDIA_CLASS_APP = "app"
|
||||
MEDIA_CLASS_APPS = "apps"
|
||||
MEDIA_CLASS_ARTIST = "artist"
|
||||
MEDIA_CLASS_CHANNEL = "channel"
|
||||
MEDIA_CLASS_CHANNELS = "channels"
|
||||
MEDIA_CLASS_COMPOSER = "composer"
|
||||
MEDIA_CLASS_CONTRIBUTING_ARTIST = "contributing_artist"
|
||||
MEDIA_CLASS_DIRECTORY = "directory"
|
||||
MEDIA_CLASS_EPISODE = "episode"
|
||||
MEDIA_CLASS_GAME = "game"
|
||||
MEDIA_CLASS_GENRE = "genre"
|
||||
MEDIA_CLASS_IMAGE = "image"
|
||||
MEDIA_CLASS_MOVIE = "movie"
|
||||
MEDIA_CLASS_MUSIC = "music"
|
||||
MEDIA_CLASS_PLAYLIST = "playlist"
|
||||
MEDIA_CLASS_PODCAST = "podcast"
|
||||
MEDIA_CLASS_SEASON = "season"
|
||||
MEDIA_CLASS_TRACK = "track"
|
||||
MEDIA_CLASS_TV_SHOW = "tv_show"
|
||||
MEDIA_CLASS_URL = "url"
|
||||
MEDIA_CLASS_VIDEO = "video"
|
||||
|
||||
MEDIA_TYPE_ALBUM = "album"
|
||||
MEDIA_TYPE_APP = "app"
|
||||
MEDIA_TYPE_APPS = "apps"
|
||||
|
|
|
@ -6,6 +6,7 @@ from typing import Tuple
|
|||
from aiohttp import web
|
||||
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
from homeassistant.components.media_player.const import MEDIA_CLASS_DIRECTORY
|
||||
from homeassistant.components.media_player.errors import BrowseError
|
||||
from homeassistant.components.media_source.error import Unresolvable
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
|
@ -114,6 +115,7 @@ class LocalSource(MediaSource):
|
|||
media = BrowseMediaSource(
|
||||
domain=DOMAIN,
|
||||
identifier=f"{source_dir_id}/{path.relative_to(self.hass.config.path('media'))}",
|
||||
media_class=MEDIA_CLASS_DIRECTORY,
|
||||
media_content_type="directory",
|
||||
title=title,
|
||||
can_play=is_file,
|
||||
|
|
|
@ -5,6 +5,8 @@ from typing import List, Optional, Tuple
|
|||
|
||||
from homeassistant.components.media_player import BrowseMedia
|
||||
from homeassistant.components.media_player.const import (
|
||||
MEDIA_CLASS_CHANNEL,
|
||||
MEDIA_CLASS_CHANNELS,
|
||||
MEDIA_TYPE_CHANNEL,
|
||||
MEDIA_TYPE_CHANNELS,
|
||||
)
|
||||
|
@ -52,6 +54,7 @@ class MediaSourceItem:
|
|||
base = BrowseMediaSource(
|
||||
domain=None,
|
||||
identifier=None,
|
||||
media_class=MEDIA_CLASS_CHANNELS,
|
||||
media_content_type=MEDIA_TYPE_CHANNELS,
|
||||
title="Media Sources",
|
||||
can_play=False,
|
||||
|
@ -61,6 +64,7 @@ class MediaSourceItem:
|
|||
BrowseMediaSource(
|
||||
domain=source.domain,
|
||||
identifier=None,
|
||||
media_class=MEDIA_CLASS_CHANNEL,
|
||||
media_content_type=MEDIA_TYPE_CHANNEL,
|
||||
title=source.name,
|
||||
can_play=False,
|
||||
|
|
|
@ -4,7 +4,10 @@ import logging
|
|||
import re
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from homeassistant.components.media_player.const import MEDIA_TYPE_VIDEO
|
||||
from homeassistant.components.media_player.const import (
|
||||
MEDIA_CLASS_VIDEO,
|
||||
MEDIA_TYPE_VIDEO,
|
||||
)
|
||||
from homeassistant.components.media_player.errors import BrowseError
|
||||
from homeassistant.components.media_source.const import MEDIA_MIME_TYPES
|
||||
from homeassistant.components.media_source.error import MediaSourceError, Unresolvable
|
||||
|
@ -91,6 +94,7 @@ class NetatmoSource(MediaSource):
|
|||
media = BrowseMediaSource(
|
||||
domain=DOMAIN,
|
||||
identifier=path,
|
||||
media_class=MEDIA_CLASS_VIDEO,
|
||||
media_content_type=MEDIA_TYPE_VIDEO,
|
||||
title=title,
|
||||
can_play=bool(
|
||||
|
|
|
@ -11,6 +11,8 @@ from homeassistant.components.media_player import (
|
|||
MediaPlayerEntity,
|
||||
)
|
||||
from homeassistant.components.media_player.const import (
|
||||
MEDIA_CLASS_CHANNEL,
|
||||
MEDIA_CLASS_CHANNELS,
|
||||
MEDIA_TYPE_CHANNEL,
|
||||
MEDIA_TYPE_CHANNELS,
|
||||
SUPPORT_BROWSE_MEDIA,
|
||||
|
@ -288,6 +290,7 @@ class PhilipsTVMediaPlayer(MediaPlayerEntity):
|
|||
|
||||
return BrowseMedia(
|
||||
title="Channels",
|
||||
media_class=MEDIA_CLASS_CHANNELS,
|
||||
media_content_id="",
|
||||
media_content_type=MEDIA_TYPE_CHANNELS,
|
||||
can_play=False,
|
||||
|
@ -295,6 +298,7 @@ class PhilipsTVMediaPlayer(MediaPlayerEntity):
|
|||
children=[
|
||||
BrowseMedia(
|
||||
title=channel,
|
||||
media_class=MEDIA_CLASS_CHANNEL,
|
||||
media_content_id=channel,
|
||||
media_content_type=MEDIA_TYPE_CHANNEL,
|
||||
can_play=True,
|
||||
|
|
|
@ -2,13 +2,31 @@
|
|||
import logging
|
||||
|
||||
from homeassistant.components.media_player import BrowseMedia
|
||||
from homeassistant.components.media_player.const import (
|
||||
MEDIA_CLASS_ALBUM,
|
||||
MEDIA_CLASS_ARTIST,
|
||||
MEDIA_CLASS_DIRECTORY,
|
||||
MEDIA_CLASS_EPISODE,
|
||||
MEDIA_CLASS_MOVIE,
|
||||
MEDIA_CLASS_PLAYLIST,
|
||||
MEDIA_CLASS_SEASON,
|
||||
MEDIA_CLASS_TRACK,
|
||||
MEDIA_CLASS_TV_SHOW,
|
||||
MEDIA_CLASS_VIDEO,
|
||||
)
|
||||
from homeassistant.components.media_player.errors import BrowseError
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
class UnknownMediaType(BrowseError):
|
||||
"""Unknown media type."""
|
||||
|
||||
|
||||
EXPANDABLES = ["album", "artist", "playlist", "season", "show"]
|
||||
PLAYLISTS_BROWSE_PAYLOAD = {
|
||||
"title": "Playlists",
|
||||
"media_class": MEDIA_CLASS_PLAYLIST,
|
||||
"media_content_id": "all",
|
||||
"media_content_type": "playlists",
|
||||
"can_play": False,
|
||||
|
@ -19,6 +37,18 @@ SPECIAL_METHODS = {
|
|||
"Recently Added": "recentlyAdded",
|
||||
}
|
||||
|
||||
ITEM_TYPE_MEDIA_CLASS = {
|
||||
"album": MEDIA_CLASS_ALBUM,
|
||||
"artist": MEDIA_CLASS_ARTIST,
|
||||
"episode": MEDIA_CLASS_EPISODE,
|
||||
"movie": MEDIA_CLASS_MOVIE,
|
||||
"playlist": MEDIA_CLASS_PLAYLIST,
|
||||
"season": MEDIA_CLASS_SEASON,
|
||||
"show": MEDIA_CLASS_TV_SHOW,
|
||||
"track": MEDIA_CLASS_TRACK,
|
||||
"video": MEDIA_CLASS_VIDEO,
|
||||
}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -34,11 +64,17 @@ def browse_media(
|
|||
if media is None:
|
||||
return None
|
||||
|
||||
media_info = item_payload(media)
|
||||
try:
|
||||
media_info = item_payload(media)
|
||||
except UnknownMediaType:
|
||||
return None
|
||||
if media_info.can_expand:
|
||||
media_info.children = []
|
||||
for item in media:
|
||||
media_info.children.append(item_payload(item))
|
||||
try:
|
||||
media_info.children.append(item_payload(item))
|
||||
except UnknownMediaType:
|
||||
continue
|
||||
return media_info
|
||||
|
||||
if media_content_id and ":" in media_content_id:
|
||||
|
@ -65,6 +101,7 @@ def browse_media(
|
|||
|
||||
payload = {
|
||||
"title": title,
|
||||
"media_class": MEDIA_CLASS_DIRECTORY,
|
||||
"media_content_id": f"{media_content_id}:{special_folder}",
|
||||
"media_content_type": media_content_type,
|
||||
"can_play": False,
|
||||
|
@ -75,7 +112,10 @@ def browse_media(
|
|||
method = SPECIAL_METHODS[special_folder]
|
||||
items = getattr(library_or_section, method)()
|
||||
for item in items:
|
||||
payload["children"].append(item_payload(item))
|
||||
try:
|
||||
payload["children"].append(item_payload(item))
|
||||
except UnknownMediaType:
|
||||
continue
|
||||
return BrowseMedia(**payload)
|
||||
|
||||
if media_content_type in ["server", None]:
|
||||
|
@ -99,8 +139,14 @@ def browse_media(
|
|||
|
||||
def item_payload(item):
|
||||
"""Create response payload for a single media item."""
|
||||
try:
|
||||
media_class = ITEM_TYPE_MEDIA_CLASS[item.type]
|
||||
except KeyError as err:
|
||||
_LOGGER.debug("Unknown type received: %s", item.type)
|
||||
raise UnknownMediaType from err
|
||||
payload = {
|
||||
"title": item.title,
|
||||
"media_class": media_class,
|
||||
"media_content_id": str(item.ratingKey),
|
||||
"media_content_type": item.type,
|
||||
"can_play": True,
|
||||
|
@ -116,6 +162,7 @@ def library_section_payload(section):
|
|||
"""Create response payload for a single library section."""
|
||||
return BrowseMedia(
|
||||
title=section.title,
|
||||
media_class=MEDIA_CLASS_DIRECTORY,
|
||||
media_content_id=section.key,
|
||||
media_content_type="library",
|
||||
can_play=False,
|
||||
|
@ -128,6 +175,7 @@ def special_library_payload(parent_payload, special_type):
|
|||
title = f"{special_type} ({parent_payload.title})"
|
||||
return BrowseMedia(
|
||||
title=title,
|
||||
media_class=parent_payload.media_class,
|
||||
media_content_id=f"{parent_payload.media_content_id}:{special_type}",
|
||||
media_content_type=parent_payload.media_content_type,
|
||||
can_play=False,
|
||||
|
@ -139,6 +187,7 @@ def server_payload(plex_server):
|
|||
"""Create response payload to describe libraries of the Plex server."""
|
||||
server_info = BrowseMedia(
|
||||
title=plex_server.friendly_name,
|
||||
media_class=MEDIA_CLASS_DIRECTORY,
|
||||
media_content_id=plex_server.machine_identifier,
|
||||
media_content_type="server",
|
||||
can_play=False,
|
||||
|
@ -165,7 +214,10 @@ def library_payload(plex_server, library_id):
|
|||
special_library_payload(library_info, "Recently Added")
|
||||
)
|
||||
for item in library.all():
|
||||
library_info.children.append(item_payload(item))
|
||||
try:
|
||||
library_info.children.append(item_payload(item))
|
||||
except UnknownMediaType:
|
||||
continue
|
||||
return library_info
|
||||
|
||||
|
||||
|
@ -173,5 +225,8 @@ def playlists_payload(plex_server):
|
|||
"""Create response payload for all available playlists."""
|
||||
playlists_info = {**PLAYLISTS_BROWSE_PAYLOAD, "children": []}
|
||||
for playlist in plex_server.playlists():
|
||||
playlists_info["children"].append(item_payload(playlist))
|
||||
try:
|
||||
playlists_info["children"].append(item_payload(playlist))
|
||||
except UnknownMediaType:
|
||||
continue
|
||||
return BrowseMedia(**playlists_info)
|
||||
|
|
|
@ -11,6 +11,11 @@ from homeassistant.components.media_player import (
|
|||
MediaPlayerEntity,
|
||||
)
|
||||
from homeassistant.components.media_player.const import (
|
||||
MEDIA_CLASS_APP,
|
||||
MEDIA_CLASS_APPS,
|
||||
MEDIA_CLASS_CHANNEL,
|
||||
MEDIA_CLASS_CHANNELS,
|
||||
MEDIA_CLASS_DIRECTORY,
|
||||
MEDIA_TYPE_APP,
|
||||
MEDIA_TYPE_APPS,
|
||||
MEDIA_TYPE_CHANNEL,
|
||||
|
@ -79,6 +84,7 @@ def browse_media_library(channels: bool = False) -> BrowseMedia:
|
|||
"""Create response payload to describe contents of a specific library."""
|
||||
library_info = BrowseMedia(
|
||||
title="Media Library",
|
||||
media_class=MEDIA_CLASS_DIRECTORY,
|
||||
media_content_id="library",
|
||||
media_content_type="library",
|
||||
can_play=False,
|
||||
|
@ -89,6 +95,7 @@ def browse_media_library(channels: bool = False) -> BrowseMedia:
|
|||
library_info.children.append(
|
||||
BrowseMedia(
|
||||
title="Apps",
|
||||
media_class=MEDIA_CLASS_APPS,
|
||||
media_content_id="apps",
|
||||
media_content_type=MEDIA_TYPE_APPS,
|
||||
can_expand=True,
|
||||
|
@ -100,6 +107,7 @@ def browse_media_library(channels: bool = False) -> BrowseMedia:
|
|||
library_info.children.append(
|
||||
BrowseMedia(
|
||||
title="Channels",
|
||||
media_class=MEDIA_CLASS_CHANNELS,
|
||||
media_content_id="channels",
|
||||
media_content_type=MEDIA_TYPE_CHANNELS,
|
||||
can_expand=True,
|
||||
|
@ -286,6 +294,7 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity):
|
|||
if media_content_type == MEDIA_TYPE_APPS:
|
||||
response = BrowseMedia(
|
||||
title="Apps",
|
||||
media_class=MEDIA_CLASS_APPS,
|
||||
media_content_id="apps",
|
||||
media_content_type=MEDIA_TYPE_APPS,
|
||||
can_expand=True,
|
||||
|
@ -294,6 +303,7 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity):
|
|||
BrowseMedia(
|
||||
title=app.name,
|
||||
thumbnail=self.coordinator.roku.app_icon_url(app.app_id),
|
||||
media_class=MEDIA_CLASS_APP,
|
||||
media_content_id=app.app_id,
|
||||
media_content_type=MEDIA_TYPE_APP,
|
||||
can_play=True,
|
||||
|
@ -306,6 +316,7 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity):
|
|||
if media_content_type == MEDIA_TYPE_CHANNELS:
|
||||
response = BrowseMedia(
|
||||
title="Channels",
|
||||
media_class=MEDIA_CLASS_CHANNELS,
|
||||
media_content_id="channels",
|
||||
media_content_type=MEDIA_TYPE_CHANNELS,
|
||||
can_expand=True,
|
||||
|
@ -313,6 +324,7 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity):
|
|||
children=[
|
||||
BrowseMedia(
|
||||
title=channel.name,
|
||||
media_class=MEDIA_CLASS_CHANNEL,
|
||||
media_content_id=channel.number,
|
||||
media_content_type=MEDIA_TYPE_CHANNEL,
|
||||
can_play=True,
|
||||
|
|
|
@ -17,6 +17,14 @@ import voluptuous as vol
|
|||
from homeassistant.components.media_player import BrowseMedia, MediaPlayerEntity
|
||||
from homeassistant.components.media_player.const import (
|
||||
ATTR_MEDIA_ENQUEUE,
|
||||
MEDIA_CLASS_ALBUM,
|
||||
MEDIA_CLASS_ARTIST,
|
||||
MEDIA_CLASS_COMPOSER,
|
||||
MEDIA_CLASS_CONTRIBUTING_ARTIST,
|
||||
MEDIA_CLASS_DIRECTORY,
|
||||
MEDIA_CLASS_GENRE,
|
||||
MEDIA_CLASS_PLAYLIST,
|
||||
MEDIA_CLASS_TRACK,
|
||||
MEDIA_TYPE_ALBUM,
|
||||
MEDIA_TYPE_ARTIST,
|
||||
MEDIA_TYPE_COMPOSER,
|
||||
|
@ -103,6 +111,23 @@ EXPANDABLE_MEDIA_TYPES = [
|
|||
SONOS_PLAYLISTS,
|
||||
]
|
||||
|
||||
SONOS_TO_MEDIA_CLASSES = {
|
||||
SONOS_ALBUM: MEDIA_CLASS_ALBUM,
|
||||
SONOS_ALBUM_ARTIST: MEDIA_CLASS_ARTIST,
|
||||
SONOS_ARTIST: MEDIA_CLASS_CONTRIBUTING_ARTIST,
|
||||
SONOS_COMPOSER: MEDIA_CLASS_COMPOSER,
|
||||
SONOS_GENRE: MEDIA_CLASS_GENRE,
|
||||
SONOS_PLAYLISTS: MEDIA_CLASS_PLAYLIST,
|
||||
SONOS_TRACKS: MEDIA_CLASS_TRACK,
|
||||
"object.container.album.musicAlbum": MEDIA_CLASS_ALBUM,
|
||||
"object.container.genre.musicGenre": MEDIA_CLASS_PLAYLIST,
|
||||
"object.container.person.composer": MEDIA_CLASS_PLAYLIST,
|
||||
"object.container.person.musicArtist": MEDIA_CLASS_ARTIST,
|
||||
"object.container.playlistContainer.sameArtist": MEDIA_CLASS_ARTIST,
|
||||
"object.container.playlistContainer": MEDIA_CLASS_PLAYLIST,
|
||||
"object.item.audioItem.musicTrack": MEDIA_CLASS_TRACK,
|
||||
}
|
||||
|
||||
SONOS_TO_MEDIA_TYPES = {
|
||||
SONOS_ALBUM: MEDIA_TYPE_ALBUM,
|
||||
SONOS_ALBUM_ARTIST: MEDIA_TYPE_ARTIST,
|
||||
|
@ -1462,9 +1487,12 @@ def build_item_response(media_library, payload):
|
|||
except IndexError:
|
||||
title = LIBRARY_TITLES_MAPPING[payload["idstring"]]
|
||||
|
||||
media_class = SONOS_TO_MEDIA_CLASSES[MEDIA_TYPES_TO_SONOS[payload["search_type"]]]
|
||||
|
||||
return BrowseMedia(
|
||||
title=title,
|
||||
thumbnail=thumbnail,
|
||||
media_class=media_class,
|
||||
media_content_id=payload["idstring"],
|
||||
media_content_type=payload["search_type"],
|
||||
children=[item_payload(item) for item in media],
|
||||
|
@ -1482,6 +1510,7 @@ def item_payload(item):
|
|||
return BrowseMedia(
|
||||
title=item.title,
|
||||
thumbnail=getattr(item, "album_art_uri", None),
|
||||
media_class=SONOS_TO_MEDIA_CLASSES[get_media_type(item)],
|
||||
media_content_id=get_content_id(item),
|
||||
media_content_type=SONOS_TO_MEDIA_TYPES[get_media_type(item)],
|
||||
can_play=can_play(item.item_class),
|
||||
|
@ -1497,6 +1526,7 @@ def library_payload(media_library):
|
|||
"""
|
||||
return BrowseMedia(
|
||||
title="Music Library",
|
||||
media_class=MEDIA_CLASS_DIRECTORY,
|
||||
media_content_id="library",
|
||||
media_content_type="library",
|
||||
can_play=False,
|
||||
|
|
|
@ -11,6 +11,12 @@ from yarl import URL
|
|||
|
||||
from homeassistant.components.media_player import BrowseMedia, MediaPlayerEntity
|
||||
from homeassistant.components.media_player.const import (
|
||||
MEDIA_CLASS_ALBUM,
|
||||
MEDIA_CLASS_ARTIST,
|
||||
MEDIA_CLASS_DIRECTORY,
|
||||
MEDIA_CLASS_PLAYLIST,
|
||||
MEDIA_CLASS_PODCAST,
|
||||
MEDIA_CLASS_TRACK,
|
||||
MEDIA_TYPE_ALBUM,
|
||||
MEDIA_TYPE_ARTIST,
|
||||
MEDIA_TYPE_EPISODE,
|
||||
|
@ -96,6 +102,29 @@ LIBRARY_MAP = {
|
|||
"new_releases": "New Releases",
|
||||
}
|
||||
|
||||
CONTENT_TYPE_MEDIA_CLASS = {
|
||||
"current_user_playlists": MEDIA_CLASS_PLAYLIST,
|
||||
"current_user_followed_artists": MEDIA_CLASS_ARTIST,
|
||||
"current_user_saved_albums": MEDIA_CLASS_ALBUM,
|
||||
"current_user_saved_tracks": MEDIA_CLASS_TRACK,
|
||||
"current_user_saved_shows": MEDIA_CLASS_PODCAST,
|
||||
"current_user_recently_played": MEDIA_CLASS_TRACK,
|
||||
"current_user_top_artists": MEDIA_CLASS_ARTIST,
|
||||
"current_user_top_tracks": MEDIA_CLASS_TRACK,
|
||||
"featured_playlists": MEDIA_CLASS_PLAYLIST,
|
||||
"categories": MEDIA_CLASS_DIRECTORY,
|
||||
"category_playlists": MEDIA_CLASS_PLAYLIST,
|
||||
"new_releases": MEDIA_CLASS_ALBUM,
|
||||
MEDIA_TYPE_PLAYLIST: MEDIA_CLASS_PLAYLIST,
|
||||
MEDIA_TYPE_ALBUM: MEDIA_CLASS_ALBUM,
|
||||
MEDIA_TYPE_ARTIST: MEDIA_CLASS_ARTIST,
|
||||
MEDIA_TYPE_SHOW: MEDIA_CLASS_PODCAST,
|
||||
}
|
||||
|
||||
|
||||
class MissingMediaInformation(BrowseError):
|
||||
"""Missing media required information."""
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
|
@ -498,24 +527,32 @@ def build_item_response(spotify, user, payload):
|
|||
return None
|
||||
|
||||
if media_content_type == "categories":
|
||||
return BrowseMedia(
|
||||
media_item = BrowseMedia(
|
||||
title=LIBRARY_MAP.get(media_content_id),
|
||||
media_class=CONTENT_TYPE_MEDIA_CLASS[media_content_type],
|
||||
media_content_id=media_content_id,
|
||||
media_content_type=media_content_type,
|
||||
can_play=False,
|
||||
can_expand=True,
|
||||
children=[
|
||||
children=[],
|
||||
)
|
||||
for item in items:
|
||||
try:
|
||||
item_id = item["id"]
|
||||
except KeyError:
|
||||
_LOGGER.debug("Missing id for media item: %s", item)
|
||||
continue
|
||||
media_item.children.append(
|
||||
BrowseMedia(
|
||||
title=item.get("name"),
|
||||
media_content_id=item["id"],
|
||||
media_class=MEDIA_CLASS_PLAYLIST,
|
||||
media_content_id=item_id,
|
||||
media_content_type="category_playlists",
|
||||
thumbnail=fetch_image_url(item, key="icons"),
|
||||
can_play=False,
|
||||
can_expand=True,
|
||||
)
|
||||
for item in items
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
if title is None:
|
||||
if "name" in media:
|
||||
|
@ -525,12 +562,18 @@ def build_item_response(spotify, user, payload):
|
|||
|
||||
response = {
|
||||
"title": title,
|
||||
"media_class": CONTENT_TYPE_MEDIA_CLASS[media_content_type],
|
||||
"media_content_id": media_content_id,
|
||||
"media_content_type": media_content_type,
|
||||
"can_play": media_content_type in PLAYABLE_MEDIA_TYPES,
|
||||
"children": [item_payload(item) for item in items],
|
||||
"children": [],
|
||||
"can_expand": True,
|
||||
}
|
||||
for item in items:
|
||||
try:
|
||||
response["children"].append(item_payload(item))
|
||||
except MissingMediaInformation:
|
||||
continue
|
||||
|
||||
if "images" in media:
|
||||
response["thumbnail"] = fetch_image_url(media)
|
||||
|
@ -546,20 +589,31 @@ def item_payload(item):
|
|||
|
||||
Used by async_browse_media.
|
||||
"""
|
||||
try:
|
||||
media_type = item["type"]
|
||||
media_id = item["uri"]
|
||||
except KeyError as err:
|
||||
_LOGGER.debug("Missing type or uri for media item: %s", item)
|
||||
raise MissingMediaInformation from err
|
||||
|
||||
can_expand = item["type"] not in [
|
||||
can_expand = media_type not in [
|
||||
MEDIA_TYPE_TRACK,
|
||||
MEDIA_TYPE_EPISODE,
|
||||
]
|
||||
|
||||
payload = {
|
||||
"title": item.get("name"),
|
||||
"media_content_id": item["uri"],
|
||||
"media_content_type": item["type"],
|
||||
"can_play": item["type"] in PLAYABLE_MEDIA_TYPES,
|
||||
"media_content_id": media_id,
|
||||
"media_content_type": media_type,
|
||||
"can_play": media_type in PLAYABLE_MEDIA_TYPES,
|
||||
"can_expand": can_expand,
|
||||
}
|
||||
|
||||
payload = {
|
||||
**payload,
|
||||
"media_class": CONTENT_TYPE_MEDIA_CLASS[media_type],
|
||||
}
|
||||
|
||||
if "images" in item:
|
||||
payload["thumbnail"] = fetch_image_url(item)
|
||||
elif MEDIA_TYPE_ALBUM in item:
|
||||
|
@ -576,6 +630,7 @@ def library_payload():
|
|||
"""
|
||||
library_info = {
|
||||
"title": "Media Library",
|
||||
"media_class": MEDIA_CLASS_DIRECTORY,
|
||||
"media_content_id": "library",
|
||||
"media_content_type": "library",
|
||||
"can_play": False,
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import pytest
|
||||
|
||||
from homeassistant.components import media_source
|
||||
from homeassistant.components.media_player.const import MEDIA_CLASS_DIRECTORY
|
||||
from homeassistant.components.media_player.errors import BrowseError
|
||||
from homeassistant.components.media_source import const
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
@ -77,6 +78,7 @@ async def test_websocket_browse_media(hass, hass_ws_client):
|
|||
domain=const.DOMAIN,
|
||||
identifier="/media",
|
||||
title="Local Media",
|
||||
media_class=MEDIA_CLASS_DIRECTORY,
|
||||
media_content_type="listing",
|
||||
can_play=False,
|
||||
can_expand=True,
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
"""Test Media Source model methods."""
|
||||
from homeassistant.components.media_player.const import MEDIA_TYPE_MUSIC
|
||||
from homeassistant.components.media_player.const import (
|
||||
MEDIA_CLASS_DIRECTORY,
|
||||
MEDIA_CLASS_MUSIC,
|
||||
MEDIA_TYPE_MUSIC,
|
||||
)
|
||||
from homeassistant.components.media_source import const, models
|
||||
|
||||
|
||||
|
@ -8,6 +12,7 @@ async def test_browse_media_as_dict():
|
|||
base = models.BrowseMediaSource(
|
||||
domain=const.DOMAIN,
|
||||
identifier="media",
|
||||
media_class=MEDIA_CLASS_DIRECTORY,
|
||||
media_content_type="folder",
|
||||
title="media/",
|
||||
can_play=False,
|
||||
|
@ -17,6 +22,7 @@ async def test_browse_media_as_dict():
|
|||
models.BrowseMediaSource(
|
||||
domain=const.DOMAIN,
|
||||
identifier="media/test.mp3",
|
||||
media_class=MEDIA_CLASS_MUSIC,
|
||||
media_content_type=MEDIA_TYPE_MUSIC,
|
||||
title="test.mp3",
|
||||
can_play=True,
|
||||
|
@ -26,12 +32,14 @@ async def test_browse_media_as_dict():
|
|||
|
||||
item = base.as_dict()
|
||||
assert item["title"] == "media/"
|
||||
assert item["media_class"] == MEDIA_CLASS_DIRECTORY
|
||||
assert item["media_content_type"] == "folder"
|
||||
assert item["media_content_id"] == f"{const.URI_SCHEME}{const.DOMAIN}/media"
|
||||
assert not item["can_play"]
|
||||
assert item["can_expand"]
|
||||
assert len(item["children"]) == 1
|
||||
assert item["children"][0]["title"] == "test.mp3"
|
||||
assert item["children"][0]["media_class"] == MEDIA_CLASS_MUSIC
|
||||
|
||||
|
||||
async def test_browse_media_parent_no_children():
|
||||
|
@ -39,6 +47,7 @@ async def test_browse_media_parent_no_children():
|
|||
base = models.BrowseMediaSource(
|
||||
domain=const.DOMAIN,
|
||||
identifier="media",
|
||||
media_class=MEDIA_CLASS_DIRECTORY,
|
||||
media_content_type="folder",
|
||||
title="media/",
|
||||
can_play=False,
|
||||
|
@ -47,6 +56,7 @@ async def test_browse_media_parent_no_children():
|
|||
|
||||
item = base.as_dict()
|
||||
assert item["title"] == "media/"
|
||||
assert item["media_class"] == MEDIA_CLASS_DIRECTORY
|
||||
assert item["media_content_type"] == "folder"
|
||||
assert item["media_content_id"] == f"{const.URI_SCHEME}{const.DOMAIN}/media"
|
||||
assert not item["can_play"]
|
||||
|
|
Loading…
Add table
Reference in a new issue