mirror of
https://github.com/home-assistant/core.git
synced 2025-04-19 14:57:52 +00:00
Add search to media_player (#140321)
* Add search to media_player * rename attr * Add searchable property * add pagination parameters * Add suggested changes * Apply suggestions * Fix cast tests * Fix first set of components * update snapshot * More tests * more test fixes * Rename attr * first own test * Add to google test * Add service test * Rename search query arg * Add required feature to search service * remove kwarg * Update homeassistant/components/media_player/__init__.py Co-authored-by: Marcel van der Veldt <m.vanderveldt@outlook.com> * fix hue test --------- Co-authored-by: Marcel van der Veldt <m.vanderveldt@outlook.com> Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
fe248a2ebd
commit
bf69d4e0a8
@ -41,6 +41,7 @@ async def async_setup_entry(
|
||||
DemoTVShowPlayer(),
|
||||
DemoBrowsePlayer("Browse"),
|
||||
DemoGroupPlayer("Group"),
|
||||
DemoSearchPlayer("Search"),
|
||||
]
|
||||
)
|
||||
|
||||
@ -95,6 +96,8 @@ NETFLIX_PLAYER_SUPPORT = (
|
||||
|
||||
BROWSE_PLAYER_SUPPORT = MediaPlayerEntityFeature.BROWSE_MEDIA
|
||||
|
||||
SEARCH_PLAYER_SUPPORT = MediaPlayerEntityFeature.SEARCH_MEDIA
|
||||
|
||||
|
||||
class AbstractDemoPlayer(MediaPlayerEntity):
|
||||
"""A demo media players."""
|
||||
@ -398,3 +401,9 @@ class DemoGroupPlayer(AbstractDemoPlayer):
|
||||
| MediaPlayerEntityFeature.GROUPING
|
||||
| MediaPlayerEntityFeature.TURN_OFF
|
||||
)
|
||||
|
||||
|
||||
class DemoSearchPlayer(AbstractDemoPlayer):
|
||||
"""A Demo media player that supports searching."""
|
||||
|
||||
_attr_supported_features = SEARCH_PLAYER_SUPPORT
|
||||
|
@ -68,7 +68,12 @@ from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.loader import bind_hass
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
from .browse_media import BrowseMedia, async_process_play_media_url # noqa: F401
|
||||
from .browse_media import ( # noqa: F401
|
||||
BrowseMedia,
|
||||
SearchMedia,
|
||||
SearchMediaQuery,
|
||||
async_process_play_media_url,
|
||||
)
|
||||
from .const import ( # noqa: F401
|
||||
_DEPRECATED_MEDIA_CLASS_DIRECTORY,
|
||||
_DEPRECATED_SUPPORT_BROWSE_MEDIA,
|
||||
@ -107,10 +112,12 @@ from .const import ( # noqa: F401
|
||||
ATTR_MEDIA_ENQUEUE,
|
||||
ATTR_MEDIA_EPISODE,
|
||||
ATTR_MEDIA_EXTRA,
|
||||
ATTR_MEDIA_FILTER_CLASSES,
|
||||
ATTR_MEDIA_PLAYLIST,
|
||||
ATTR_MEDIA_POSITION,
|
||||
ATTR_MEDIA_POSITION_UPDATED_AT,
|
||||
ATTR_MEDIA_REPEAT,
|
||||
ATTR_MEDIA_SEARCH_QUERY,
|
||||
ATTR_MEDIA_SEASON,
|
||||
ATTR_MEDIA_SEEK_POSITION,
|
||||
ATTR_MEDIA_SERIES_TITLE,
|
||||
@ -128,6 +135,7 @@ from .const import ( # noqa: F401
|
||||
SERVICE_CLEAR_PLAYLIST,
|
||||
SERVICE_JOIN,
|
||||
SERVICE_PLAY_MEDIA,
|
||||
SERVICE_SEARCH_MEDIA,
|
||||
SERVICE_SELECT_SOUND_MODE,
|
||||
SERVICE_SELECT_SOURCE,
|
||||
SERVICE_UNJOIN,
|
||||
@ -137,7 +145,7 @@ from .const import ( # noqa: F401
|
||||
MediaType,
|
||||
RepeatMode,
|
||||
)
|
||||
from .errors import BrowseError
|
||||
from .errors import BrowseError, SearchError
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -291,6 +299,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
)
|
||||
|
||||
websocket_api.async_register_command(hass, websocket_browse_media)
|
||||
websocket_api.async_register_command(hass, websocket_search_media)
|
||||
hass.http.register_view(MediaPlayerImageView(component))
|
||||
|
||||
await component.async_setup(config)
|
||||
@ -447,6 +456,22 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"async_browse_media",
|
||||
supports_response=SupportsResponse.ONLY,
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_SEARCH_MEDIA,
|
||||
{
|
||||
vol.Optional(ATTR_MEDIA_CONTENT_TYPE): cv.string,
|
||||
vol.Optional(ATTR_MEDIA_CONTENT_ID): cv.string,
|
||||
vol.Required(ATTR_MEDIA_SEARCH_QUERY): cv.string,
|
||||
vol.Optional(ATTR_MEDIA_FILTER_CLASSES): vol.All(
|
||||
cv.ensure_list,
|
||||
[vol.In([m.value for m in MediaClass])],
|
||||
lambda x: {MediaClass(item) for item in x},
|
||||
),
|
||||
},
|
||||
"async_internal_search_media",
|
||||
[MediaPlayerEntityFeature.SEARCH_MEDIA],
|
||||
SupportsResponse.ONLY,
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_SHUFFLE_SET,
|
||||
{vol.Required(ATTR_MEDIA_SHUFFLE): cv.boolean},
|
||||
@ -1157,6 +1182,29 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
async def async_internal_search_media(
|
||||
self,
|
||||
search_query: str,
|
||||
media_content_type: MediaType | str | None = None,
|
||||
media_content_id: str | None = None,
|
||||
media_filter_classes: list[MediaClass] | None = None,
|
||||
) -> SearchMedia:
|
||||
return await self.async_search_media(
|
||||
query=SearchMediaQuery(
|
||||
search_query=search_query,
|
||||
media_content_type=media_content_type,
|
||||
media_content_id=media_content_id,
|
||||
media_filter_classes=media_filter_classes,
|
||||
)
|
||||
)
|
||||
|
||||
async def async_search_media(
|
||||
self,
|
||||
query: SearchMediaQuery,
|
||||
) -> SearchMedia:
|
||||
"""Search the media player."""
|
||||
raise NotImplementedError
|
||||
|
||||
def join_players(self, group_members: list[str]) -> None:
|
||||
"""Join `group_members` as a player group with the current player."""
|
||||
raise NotImplementedError
|
||||
@ -1360,6 +1408,75 @@ async def websocket_browse_media(
|
||||
connection.send_result(msg["id"], result)
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required("type"): "media_player/search_media",
|
||||
vol.Required("entity_id"): cv.entity_id,
|
||||
vol.Inclusive(
|
||||
ATTR_MEDIA_CONTENT_TYPE,
|
||||
"media_ids",
|
||||
"media_content_type and media_content_id must be provided together",
|
||||
): str,
|
||||
vol.Inclusive(
|
||||
ATTR_MEDIA_CONTENT_ID,
|
||||
"media_ids",
|
||||
"media_content_type and media_content_id must be provided together",
|
||||
): str,
|
||||
vol.Required(ATTR_MEDIA_SEARCH_QUERY): str,
|
||||
vol.Optional(ATTR_MEDIA_FILTER_CLASSES): vol.All(
|
||||
cv.ensure_list,
|
||||
[vol.In([m.value for m in MediaClass])],
|
||||
lambda x: {MediaClass(item) for item in x},
|
||||
),
|
||||
}
|
||||
)
|
||||
@websocket_api.async_response
|
||||
async def websocket_search_media(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.connection.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""Search media available to the media_player entity.
|
||||
|
||||
To use, media_player integrations can implement
|
||||
MediaPlayerEntity.async_search_media()
|
||||
"""
|
||||
player = hass.data[DATA_COMPONENT].get_entity(msg["entity_id"])
|
||||
|
||||
if player is None:
|
||||
connection.send_error(msg["id"], "entity_not_found", "Entity not found")
|
||||
return
|
||||
|
||||
if MediaPlayerEntityFeature.SEARCH_MEDIA not in player.supported_features_compat:
|
||||
connection.send_message(
|
||||
websocket_api.error_message(
|
||||
msg["id"], ERR_NOT_SUPPORTED, "Player does not support searching media"
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
media_content_type = msg.get(ATTR_MEDIA_CONTENT_TYPE)
|
||||
media_content_id = msg.get(ATTR_MEDIA_CONTENT_ID)
|
||||
query = str(msg.get(ATTR_MEDIA_SEARCH_QUERY))
|
||||
media_filter_classes = msg.get(ATTR_MEDIA_FILTER_CLASSES, [])
|
||||
|
||||
try:
|
||||
payload = await player.async_internal_search_media(
|
||||
query,
|
||||
media_content_type,
|
||||
media_content_id,
|
||||
media_filter_classes,
|
||||
)
|
||||
except SearchError as err:
|
||||
connection.send_message(
|
||||
websocket_api.error_message(msg["id"], ERR_UNKNOWN_ERROR, str(err))
|
||||
)
|
||||
return
|
||||
|
||||
result = payload.as_dict()
|
||||
connection.send_result(msg["id"], result)
|
||||
|
||||
|
||||
_FETCH_TIMEOUT = aiohttp.ClientTimeout(total=10)
|
||||
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Any
|
||||
@ -109,6 +110,7 @@ class BrowseMedia:
|
||||
children_media_class: MediaClass | str | None = None,
|
||||
thumbnail: str | None = None,
|
||||
not_shown: int = 0,
|
||||
can_search: bool = False,
|
||||
) -> None:
|
||||
"""Initialize browse media item."""
|
||||
self.media_class = media_class
|
||||
@ -121,6 +123,7 @@ class BrowseMedia:
|
||||
self.children_media_class = children_media_class
|
||||
self.thumbnail = thumbnail
|
||||
self.not_shown = not_shown
|
||||
self.can_search = can_search
|
||||
|
||||
def as_dict(self, *, parent: bool = True) -> dict[str, Any]:
|
||||
"""Convert Media class to browse media dictionary."""
|
||||
@ -135,6 +138,7 @@ class BrowseMedia:
|
||||
"children_media_class": self.children_media_class,
|
||||
"can_play": self.can_play,
|
||||
"can_expand": self.can_expand,
|
||||
"can_search": self.can_search,
|
||||
"thumbnail": self.thumbnail,
|
||||
}
|
||||
|
||||
@ -163,3 +167,27 @@ class BrowseMedia:
|
||||
def __repr__(self) -> str:
|
||||
"""Return representation of browse media."""
|
||||
return f"<BrowseMedia {self.title} ({self.media_class})>"
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class SearchMedia:
|
||||
"""Represent search results."""
|
||||
|
||||
version: int = field(default=1)
|
||||
result: list[BrowseMedia]
|
||||
|
||||
def as_dict(self, *, parent: bool = True) -> dict[str, Any]:
|
||||
"""Convert SearchMedia class to browse media dictionary."""
|
||||
return {
|
||||
"result": [item.as_dict(parent=parent) for item in self.result],
|
||||
}
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class SearchMediaQuery:
|
||||
"""Represent a search media file."""
|
||||
|
||||
search_query: str
|
||||
media_content_type: MediaType | str | None = field(default=None)
|
||||
media_content_id: str | None = None
|
||||
media_filter_classes: list[MediaClass] | None = field(default=None)
|
||||
|
@ -26,6 +26,8 @@ ATTR_MEDIA_ARTIST = "media_artist"
|
||||
ATTR_MEDIA_CHANNEL = "media_channel"
|
||||
ATTR_MEDIA_CONTENT_ID = "media_content_id"
|
||||
ATTR_MEDIA_CONTENT_TYPE = "media_content_type"
|
||||
ATTR_MEDIA_SEARCH_QUERY = "search_query"
|
||||
ATTR_MEDIA_FILTER_CLASSES = "media_filter_classes"
|
||||
ATTR_MEDIA_DURATION = "media_duration"
|
||||
ATTR_MEDIA_ENQUEUE = "enqueue"
|
||||
ATTR_MEDIA_EXTRA = "extra"
|
||||
@ -174,6 +176,7 @@ SERVICE_CLEAR_PLAYLIST = "clear_playlist"
|
||||
SERVICE_JOIN = "join"
|
||||
SERVICE_PLAY_MEDIA = "play_media"
|
||||
SERVICE_BROWSE_MEDIA = "browse_media"
|
||||
SERVICE_SEARCH_MEDIA = "search_media"
|
||||
SERVICE_SELECT_SOUND_MODE = "select_sound_mode"
|
||||
SERVICE_SELECT_SOURCE = "select_source"
|
||||
SERVICE_UNJOIN = "unjoin"
|
||||
@ -220,6 +223,7 @@ class MediaPlayerEntityFeature(IntFlag):
|
||||
GROUPING = 524288
|
||||
MEDIA_ANNOUNCE = 1048576
|
||||
MEDIA_ENQUEUE = 2097152
|
||||
SEARCH_MEDIA = 4194304
|
||||
|
||||
|
||||
# These SUPPORT_* constants are deprecated as of Home Assistant 2022.5.
|
||||
|
@ -9,3 +9,7 @@ class MediaPlayerException(HomeAssistantError):
|
||||
|
||||
class BrowseError(MediaPlayerException):
|
||||
"""Error while browsing."""
|
||||
|
||||
|
||||
class SearchError(MediaPlayerException):
|
||||
"""Error while searching."""
|
||||
|
@ -355,6 +355,7 @@ async def test_browse_media(
|
||||
"children_media_class": "app",
|
||||
"can_play": False,
|
||||
"can_expand": True,
|
||||
"can_search": False,
|
||||
"thumbnail": None,
|
||||
"not_shown": 0,
|
||||
"children": [
|
||||
@ -366,6 +367,7 @@ async def test_browse_media(
|
||||
"children_media_class": None,
|
||||
"can_play": False,
|
||||
"can_expand": False,
|
||||
"can_search": False,
|
||||
"thumbnail": "https://www.youtube.com/icon.png",
|
||||
},
|
||||
{
|
||||
@ -376,6 +378,7 @@ async def test_browse_media(
|
||||
"children_media_class": None,
|
||||
"can_play": False,
|
||||
"can_expand": False,
|
||||
"can_search": False,
|
||||
"thumbnail": "",
|
||||
},
|
||||
],
|
||||
|
@ -1323,6 +1323,7 @@ async def test_async_play_media_url_m3u(
|
||||
"media_content_id": "media-source://media_source/local/test.mp3",
|
||||
"can_play": True,
|
||||
"can_expand": False,
|
||||
"can_search": False,
|
||||
"thumbnail": None,
|
||||
"children_media_class": None,
|
||||
},
|
||||
@ -1337,6 +1338,7 @@ async def test_async_play_media_url_m3u(
|
||||
"media_content_id": ("media-source://media_source/local/test.mp4"),
|
||||
"can_play": True,
|
||||
"can_expand": False,
|
||||
"can_search": False,
|
||||
"thumbnail": None,
|
||||
"children_media_class": None,
|
||||
},
|
||||
|
@ -4,6 +4,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'directory',
|
||||
'media_content_id': '',
|
||||
@ -18,6 +19,7 @@
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'music',
|
||||
'media_content_id': '1',
|
||||
@ -28,6 +30,7 @@
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'music',
|
||||
'media_content_id': '2',
|
||||
|
@ -1037,6 +1037,7 @@ async def test_entity_browse_media(
|
||||
),
|
||||
"can_play": True,
|
||||
"can_expand": False,
|
||||
"can_search": False,
|
||||
"thumbnail": None,
|
||||
"children_media_class": None,
|
||||
}
|
||||
@ -1049,6 +1050,7 @@ async def test_entity_browse_media(
|
||||
"media_content_id": "media-source://media_source/local/test.mp3",
|
||||
"can_play": True,
|
||||
"can_expand": False,
|
||||
"can_search": False,
|
||||
"thumbnail": None,
|
||||
"children_media_class": None,
|
||||
}
|
||||
@ -1107,6 +1109,7 @@ async def test_entity_browse_media_audio_only(
|
||||
"media_content_id": "media-source://media_source/local/test.mp3",
|
||||
"can_play": True,
|
||||
"can_expand": False,
|
||||
"can_search": False,
|
||||
"thumbnail": None,
|
||||
"children_media_class": None,
|
||||
}
|
||||
@ -2208,6 +2211,7 @@ async def test_cast_platform_browse_media(
|
||||
"media_content_id": "",
|
||||
"can_play": False,
|
||||
"can_expand": True,
|
||||
"can_search": False,
|
||||
"thumbnail": "https://brands.home-assistant.io/_/spotify/logo.png",
|
||||
"children_media_class": None,
|
||||
}
|
||||
@ -2232,6 +2236,7 @@ async def test_cast_platform_browse_media(
|
||||
"media_content_id": "",
|
||||
"can_play": True,
|
||||
"can_expand": False,
|
||||
"can_search": False,
|
||||
"children_media_class": None,
|
||||
"thumbnail": None,
|
||||
"children": [],
|
||||
|
@ -1058,6 +1058,7 @@ async def test_browse_media(
|
||||
),
|
||||
"can_play": True,
|
||||
"can_expand": False,
|
||||
"can_search": False,
|
||||
"thumbnail": None,
|
||||
"children_media_class": None,
|
||||
}
|
||||
@ -1070,6 +1071,7 @@ async def test_browse_media(
|
||||
"media_content_id": "media-source://media_source/local/test.mp3",
|
||||
"can_play": True,
|
||||
"can_expand": False,
|
||||
"can_search": False,
|
||||
"thumbnail": None,
|
||||
"children_media_class": None,
|
||||
}
|
||||
@ -1153,6 +1155,7 @@ async def test_browse_media_unfiltered(
|
||||
),
|
||||
"can_play": True,
|
||||
"can_expand": False,
|
||||
"can_search": False,
|
||||
"thumbnail": None,
|
||||
"children_media_class": None,
|
||||
}
|
||||
@ -1163,6 +1166,7 @@ async def test_browse_media_unfiltered(
|
||||
"media_content_id": "media-source://media_source/local/test.mp3",
|
||||
"can_play": True,
|
||||
"can_expand": False,
|
||||
"can_search": False,
|
||||
"thumbnail": None,
|
||||
"children_media_class": None,
|
||||
}
|
||||
|
@ -103,6 +103,7 @@ ENTITY_IDS_BY_NUMBER = {
|
||||
"26": "light.living_room_rgbww_lights",
|
||||
"27": "media_player.group",
|
||||
"28": "media_player.browse",
|
||||
"29": "media_player.search",
|
||||
}
|
||||
|
||||
ENTITY_NUMBERS_BY_ID = {v: k for k, v in ENTITY_IDS_BY_NUMBER.items()}
|
||||
|
@ -184,6 +184,7 @@ async def test_browse_media(
|
||||
"media_content_id": "media-source://media_source/local/test.mp3",
|
||||
"can_play": True,
|
||||
"can_expand": False,
|
||||
"can_search": False,
|
||||
"thumbnail": None,
|
||||
"children_media_class": None,
|
||||
}
|
||||
|
@ -259,6 +259,13 @@ DEMO_DEVICES = [
|
||||
"type": "action.devices.types.SETTOP",
|
||||
"willReportState": False,
|
||||
},
|
||||
{
|
||||
"id": "media_player.search",
|
||||
"name": {"name": "Search"},
|
||||
"traits": ["action.devices.traits.MediaState", "action.devices.traits.OnOff"],
|
||||
"type": "action.devices.types.SETTOP",
|
||||
"willReportState": False,
|
||||
},
|
||||
{
|
||||
"id": "fan.living_room_fan",
|
||||
"name": {"name": "Living Room Fan"},
|
||||
|
@ -3,10 +3,12 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'track',
|
||||
'media_content_id': 'heos://media/1/station?name=Today%27s+Hits+Radio&image_url=&playable=True&browsable=False&media_id=123456789',
|
||||
@ -28,6 +30,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
]),
|
||||
'children_media_class': None,
|
||||
@ -43,10 +46,12 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'music',
|
||||
'media_content_id': 'media-source://media_source/local/test.mp3',
|
||||
@ -68,10 +73,12 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'directory',
|
||||
'media_content_id': 'heos://media/1/music_service?name=Pandora&image_url=&available=True&service_username=user',
|
||||
@ -82,6 +89,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'directory',
|
||||
'media_content_id': 'heos://media/3/music_service?name=TuneIn&image_url=&available=False',
|
||||
@ -92,6 +100,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': 'music',
|
||||
'media_class': 'directory',
|
||||
'media_content_id': 'media-source://media_source/local/.',
|
||||
@ -113,10 +122,12 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'directory',
|
||||
'media_content_id': 'heos://media/1/music_service?name=Pandora&image_url=&available=True&service_username=user',
|
||||
@ -127,6 +138,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'directory',
|
||||
'media_content_id': 'heos://media/3/music_service?name=TuneIn&image_url=&available=False',
|
||||
@ -148,6 +160,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
]),
|
||||
'children_media_class': 'directory',
|
||||
|
@ -15,6 +15,7 @@
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children': None,
|
||||
'children_media_class': None,
|
||||
'domain': 'jellyfin',
|
||||
@ -31,6 +32,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': None,
|
||||
'children_media_class': None,
|
||||
'domain': 'jellyfin',
|
||||
@ -47,6 +49,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': None,
|
||||
'children_media_class': None,
|
||||
'domain': 'jellyfin',
|
||||
@ -63,6 +66,7 @@
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children': None,
|
||||
'children_media_class': None,
|
||||
'domain': 'jellyfin',
|
||||
@ -85,6 +89,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': None,
|
||||
'children_media_class': None,
|
||||
'domain': 'jellyfin',
|
||||
@ -101,6 +106,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': None,
|
||||
'children_media_class': None,
|
||||
'domain': 'jellyfin',
|
||||
@ -117,6 +123,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': None,
|
||||
'children_media_class': None,
|
||||
'domain': 'jellyfin',
|
||||
@ -133,6 +140,7 @@
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children': None,
|
||||
'children_media_class': None,
|
||||
'domain': 'jellyfin',
|
||||
|
@ -279,6 +279,7 @@ async def test_browse_media(
|
||||
"media_content_id": "COLLECTION-FOLDER-UUID",
|
||||
"can_play": False,
|
||||
"can_expand": True,
|
||||
"can_search": False,
|
||||
"thumbnail": "http://localhost/Items/c22fd826-17fc-44f4-9b04-1eb3e8fb9173/Images/Backdrop.jpg",
|
||||
"children_media_class": None,
|
||||
}
|
||||
@ -307,6 +308,7 @@ async def test_browse_media(
|
||||
"media_content_id": "EPISODE-UUID",
|
||||
"can_play": True,
|
||||
"can_expand": False,
|
||||
"can_search": False,
|
||||
"thumbnail": "http://localhost/Items/c22fd826-17fc-44f4-9b04-1eb3e8fb9173/Images/Backdrop.jpg",
|
||||
"children_media_class": None,
|
||||
}
|
||||
|
@ -12,13 +12,20 @@ from homeassistant.components import media_player
|
||||
from homeassistant.components.media_player import (
|
||||
ATTR_MEDIA_CONTENT_ID,
|
||||
ATTR_MEDIA_CONTENT_TYPE,
|
||||
ATTR_MEDIA_FILTER_CLASSES,
|
||||
ATTR_MEDIA_SEARCH_QUERY,
|
||||
BrowseMedia,
|
||||
MediaClass,
|
||||
MediaPlayerEnqueue,
|
||||
MediaPlayerEntity,
|
||||
MediaPlayerEntityFeature,
|
||||
SearchMedia,
|
||||
SearchMediaQuery,
|
||||
)
|
||||
from homeassistant.components.media_player.const import (
|
||||
SERVICE_BROWSE_MEDIA,
|
||||
SERVICE_SEARCH_MEDIA,
|
||||
)
|
||||
from homeassistant.components.media_player.const import SERVICE_BROWSE_MEDIA
|
||||
from homeassistant.components.websocket_api import TYPE_RESULT
|
||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF
|
||||
from homeassistant.core import HomeAssistant
|
||||
@ -47,6 +54,7 @@ def _create_tuples(enum: type[Enum], constant_prefix: str) -> list[tuple[Enum, s
|
||||
not in [
|
||||
MediaPlayerEntityFeature.MEDIA_ANNOUNCE,
|
||||
MediaPlayerEntityFeature.MEDIA_ENQUEUE,
|
||||
MediaPlayerEntityFeature.SEARCH_MEDIA,
|
||||
]
|
||||
]
|
||||
|
||||
@ -315,6 +323,7 @@ async def test_media_browse(
|
||||
"media_content_id": "mock-id",
|
||||
"can_play": False,
|
||||
"can_expand": True,
|
||||
"can_search": False,
|
||||
"children_media_class": None,
|
||||
"thumbnail": None,
|
||||
"not_shown": 0,
|
||||
@ -411,6 +420,119 @@ async def test_media_browse_service(hass: HomeAssistant) -> None:
|
||||
assert browse_res.children[1].media_content_type == "album"
|
||||
|
||||
|
||||
async def test_media_search(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test browsing media."""
|
||||
await async_setup_component(
|
||||
hass, "media_player", {"media_player": {"platform": "demo"}}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.demo.media_player.DemoSearchPlayer.async_search_media",
|
||||
return_value=SearchMedia(
|
||||
result=[
|
||||
BrowseMedia(
|
||||
media_class=MediaClass.DIRECTORY,
|
||||
media_content_id="mock-id",
|
||||
media_content_type="mock-type",
|
||||
title="Mock Title",
|
||||
can_play=False,
|
||||
can_expand=True,
|
||||
)
|
||||
]
|
||||
),
|
||||
) as mock_search_media:
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 7,
|
||||
"type": "media_player/search_media",
|
||||
"entity_id": "media_player.search",
|
||||
"media_content_type": "album",
|
||||
"media_content_id": "abcd",
|
||||
"search_query": "query",
|
||||
"media_filter_classes": ["album"],
|
||||
}
|
||||
)
|
||||
|
||||
msg = await client.receive_json()
|
||||
|
||||
assert msg["id"] == 7
|
||||
assert msg["type"] == TYPE_RESULT
|
||||
assert msg["success"]
|
||||
assert msg["result"]["result"] == [
|
||||
{
|
||||
"title": "Mock Title",
|
||||
"media_class": "directory",
|
||||
"media_content_type": "mock-type",
|
||||
"media_content_id": "mock-id",
|
||||
"children_media_class": None,
|
||||
"can_play": False,
|
||||
"can_expand": True,
|
||||
"can_search": False,
|
||||
"thumbnail": None,
|
||||
"not_shown": 0,
|
||||
"children": [],
|
||||
}
|
||||
]
|
||||
assert mock_search_media.mock_calls[0].kwargs["query"] == SearchMediaQuery(
|
||||
search_query="query",
|
||||
media_content_type="album",
|
||||
media_content_id="abcd",
|
||||
media_filter_classes={MediaClass.ALBUM},
|
||||
)
|
||||
|
||||
|
||||
async def test_media_search_service(hass: HomeAssistant) -> None:
|
||||
"""Test browsing media."""
|
||||
await async_setup_component(
|
||||
hass, "media_player", {"media_player": {"platform": "demo"}}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
expected = [
|
||||
BrowseMedia(
|
||||
media_class=MediaClass.DIRECTORY,
|
||||
media_content_id="mock-id",
|
||||
media_content_type="mock-type",
|
||||
title="Mock Title",
|
||||
can_play=False,
|
||||
can_expand=True,
|
||||
children=[],
|
||||
)
|
||||
]
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.demo.media_player.DemoSearchPlayer.async_search_media",
|
||||
return_value=SearchMedia(result=expected),
|
||||
) as mock_search_media:
|
||||
result = await hass.services.async_call(
|
||||
"media_player",
|
||||
SERVICE_SEARCH_MEDIA,
|
||||
{
|
||||
ATTR_ENTITY_ID: "media_player.search",
|
||||
ATTR_MEDIA_CONTENT_TYPE: "album",
|
||||
ATTR_MEDIA_CONTENT_ID: "title=Album*",
|
||||
ATTR_MEDIA_SEARCH_QUERY: "query",
|
||||
ATTR_MEDIA_FILTER_CLASSES: ["album"],
|
||||
},
|
||||
blocking=True,
|
||||
return_response=True,
|
||||
)
|
||||
|
||||
search_res: SearchMedia = result["media_player.search"]
|
||||
assert search_res.version == 1
|
||||
assert search_res.result == expected
|
||||
assert mock_search_media.mock_calls[0].kwargs["query"] == SearchMediaQuery(
|
||||
search_query="query",
|
||||
media_content_type="album",
|
||||
media_content_id="title=Album*",
|
||||
media_filter_classes={MediaClass.ALBUM},
|
||||
)
|
||||
|
||||
|
||||
async def test_group_members_available_when_off(hass: HomeAssistant) -> None:
|
||||
"""Test that group_members are still available when media_player is off."""
|
||||
await async_setup_component(
|
||||
|
@ -104,6 +104,7 @@ async def test_async_browse_media_success(
|
||||
"media_content_id": "media-source://motioneye",
|
||||
"can_play": False,
|
||||
"can_expand": True,
|
||||
"can_search": False,
|
||||
"children_media_class": "directory",
|
||||
"thumbnail": None,
|
||||
"children": [
|
||||
@ -116,6 +117,7 @@ async def test_async_browse_media_success(
|
||||
),
|
||||
"can_play": False,
|
||||
"can_expand": True,
|
||||
"can_search": False,
|
||||
"thumbnail": None,
|
||||
"children_media_class": "directory",
|
||||
}
|
||||
@ -132,6 +134,7 @@ async def test_async_browse_media_success(
|
||||
"media_content_id": "media-source://motioneye/74565ad414754616000674c87bdc876c",
|
||||
"can_play": False,
|
||||
"can_expand": True,
|
||||
"can_search": False,
|
||||
"children_media_class": "directory",
|
||||
"thumbnail": None,
|
||||
"children": [
|
||||
@ -145,6 +148,7 @@ async def test_async_browse_media_success(
|
||||
),
|
||||
"can_play": False,
|
||||
"can_expand": True,
|
||||
"can_search": False,
|
||||
"thumbnail": None,
|
||||
"children_media_class": "directory",
|
||||
}
|
||||
@ -164,6 +168,7 @@ async def test_async_browse_media_success(
|
||||
),
|
||||
"can_play": False,
|
||||
"can_expand": True,
|
||||
"can_search": False,
|
||||
"children_media_class": "directory",
|
||||
"thumbnail": None,
|
||||
"children": [
|
||||
@ -177,6 +182,7 @@ async def test_async_browse_media_success(
|
||||
),
|
||||
"can_play": False,
|
||||
"can_expand": True,
|
||||
"can_search": False,
|
||||
"thumbnail": None,
|
||||
"children_media_class": "video",
|
||||
},
|
||||
@ -190,6 +196,7 @@ async def test_async_browse_media_success(
|
||||
),
|
||||
"can_play": False,
|
||||
"can_expand": True,
|
||||
"can_search": False,
|
||||
"thumbnail": None,
|
||||
"children_media_class": "image",
|
||||
},
|
||||
@ -212,6 +219,7 @@ async def test_async_browse_media_success(
|
||||
),
|
||||
"can_play": False,
|
||||
"can_expand": True,
|
||||
"can_search": False,
|
||||
"children_media_class": "video",
|
||||
"thumbnail": None,
|
||||
"children": [
|
||||
@ -225,6 +233,7 @@ async def test_async_browse_media_success(
|
||||
),
|
||||
"can_play": False,
|
||||
"can_expand": True,
|
||||
"can_search": False,
|
||||
"thumbnail": None,
|
||||
"children_media_class": "directory",
|
||||
}
|
||||
@ -247,6 +256,7 @@ async def test_async_browse_media_success(
|
||||
),
|
||||
"can_play": False,
|
||||
"can_expand": True,
|
||||
"can_search": False,
|
||||
"children_media_class": "video",
|
||||
"thumbnail": None,
|
||||
"children": [
|
||||
@ -261,6 +271,7 @@ async def test_async_browse_media_success(
|
||||
),
|
||||
"can_play": True,
|
||||
"can_expand": False,
|
||||
"can_search": False,
|
||||
"thumbnail": "http://movie",
|
||||
"children_media_class": None,
|
||||
},
|
||||
@ -275,6 +286,7 @@ async def test_async_browse_media_success(
|
||||
),
|
||||
"can_play": True,
|
||||
"can_expand": False,
|
||||
"can_search": False,
|
||||
"thumbnail": "http://movie",
|
||||
"children_media_class": None,
|
||||
},
|
||||
@ -289,6 +301,7 @@ async def test_async_browse_media_success(
|
||||
),
|
||||
"can_play": True,
|
||||
"can_expand": False,
|
||||
"can_search": False,
|
||||
"thumbnail": "http://movie",
|
||||
"children_media_class": None,
|
||||
},
|
||||
@ -327,6 +340,7 @@ async def test_async_browse_media_images_success(
|
||||
),
|
||||
"can_play": False,
|
||||
"can_expand": True,
|
||||
"can_search": False,
|
||||
"children_media_class": "image",
|
||||
"thumbnail": None,
|
||||
"children": [
|
||||
@ -341,6 +355,7 @@ async def test_async_browse_media_images_success(
|
||||
),
|
||||
"can_play": False,
|
||||
"can_expand": False,
|
||||
"can_search": False,
|
||||
"thumbnail": "http://image",
|
||||
"children_media_class": None,
|
||||
}
|
||||
@ -487,6 +502,7 @@ async def test_async_resolve_media_failure(
|
||||
),
|
||||
"can_play": False,
|
||||
"can_expand": True,
|
||||
"can_search": False,
|
||||
"children_media_class": "video",
|
||||
"thumbnail": None,
|
||||
"children": [],
|
||||
|
@ -3,10 +3,12 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'album',
|
||||
'media_content_id': 'object.container.album.musicAlbum',
|
||||
@ -17,6 +19,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'track',
|
||||
'media_content_id': 'object.item.audioItem.audioBook',
|
||||
@ -27,6 +30,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'genre',
|
||||
'media_content_id': 'object.item.audioItem.audioBroadcast',
|
||||
@ -48,10 +52,12 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'album',
|
||||
'media_content_id': 'FV:2/8',
|
||||
@ -73,10 +79,12 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'track',
|
||||
'media_content_id': 'FV:2/66',
|
||||
@ -99,6 +107,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'contributing_artist',
|
||||
'media_content_id': 'A:ARTIST',
|
||||
@ -109,6 +118,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'artist',
|
||||
'media_content_id': 'A:ALBUMARTIST',
|
||||
@ -119,6 +129,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'album',
|
||||
'media_content_id': 'A:ALBUM',
|
||||
@ -129,6 +140,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'genre',
|
||||
'media_content_id': 'A:GENRE',
|
||||
@ -139,6 +151,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'composer',
|
||||
'media_content_id': 'A:COMPOSER',
|
||||
@ -149,6 +162,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'track',
|
||||
'media_content_id': 'A:TRACKS',
|
||||
@ -159,6 +173,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'playlist',
|
||||
'media_content_id': 'A:PLAYLISTS',
|
||||
@ -173,6 +188,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'album',
|
||||
'media_content_id': "A:ALBUM/A%20Hard%20Day's%20Night",
|
||||
@ -183,6 +199,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'album',
|
||||
'media_content_id': 'A:ALBUM/Abbey%20Road',
|
||||
@ -193,6 +210,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'album',
|
||||
'media_content_id': 'A:ALBUM/Between%20Good%20And%20Evil',
|
||||
@ -203,6 +221,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'album',
|
||||
'media_content_id': "A:ALBUM/Special%20Characters,'()+",
|
||||
@ -217,6 +236,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'directory',
|
||||
'media_content_id': '',
|
||||
@ -227,6 +247,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'directory',
|
||||
'media_content_id': '',
|
||||
|
@ -3,10 +3,12 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.PLAYLIST: 'playlist'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/current_user_playlists',
|
||||
@ -17,6 +19,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.ARTIST: 'artist'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/current_user_followed_artists',
|
||||
@ -27,6 +30,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.ALBUM: 'album'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/current_user_saved_albums',
|
||||
@ -37,6 +41,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/current_user_saved_tracks',
|
||||
@ -47,6 +52,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.PODCAST: 'podcast'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/current_user_saved_shows',
|
||||
@ -57,6 +63,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/current_user_recently_played',
|
||||
@ -67,6 +74,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.ARTIST: 'artist'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/current_user_top_artists',
|
||||
@ -77,6 +85,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/current_user_top_tracks',
|
||||
@ -87,6 +96,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.ALBUM: 'album'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/new_releases',
|
||||
@ -108,10 +118,12 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_class': <MediaClass.PLAYLIST: 'playlist'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:playlist:4WkWJ0EjHEFASDevhM8oPw',
|
||||
@ -122,6 +134,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_class': <MediaClass.PLAYLIST: 'playlist'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:playlist:1RHirWgH1weMsBLi4KOK9d',
|
||||
@ -143,10 +156,12 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_class': <MediaClass.PLAYLIST: 'playlist'>,
|
||||
'media_content_id': 'spotify://32oesphrnacjcf7vw5bf6odx3/spotify:playlist:4WkWJ0EjHEFASDevhM8oPw',
|
||||
@ -157,6 +172,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_class': <MediaClass.PLAYLIST: 'playlist'>,
|
||||
'media_content_id': 'spotify://32oesphrnacjcf7vw5bf6odx3/spotify:playlist:1RHirWgH1weMsBLi4KOK9d',
|
||||
@ -178,10 +194,12 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': <MediaClass.APP: 'app'>,
|
||||
'media_content_id': 'spotify://01J5TX5A0FF6G5V0QJX6HBC94T',
|
||||
@ -192,6 +210,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': <MediaClass.APP: 'app'>,
|
||||
'media_content_id': 'spotify://32oesphrnacjcf7vw5bf6odx3',
|
||||
@ -213,10 +232,12 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:track:6akJGriy4njdP8fZTPGjwz',
|
||||
@ -227,6 +248,7 @@
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:track:7N02bJK1amhplZ8yAapRS5',
|
||||
@ -248,10 +270,12 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_class': <MediaClass.ALBUM: 'album'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:album:56jg3KJcYmfL7RzYmG2O1Q',
|
||||
@ -262,6 +286,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_class': <MediaClass.ALBUM: 'album'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:album:1l86t4bTNT2j1X0ZBCIv6R',
|
||||
@ -283,10 +308,12 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.ALBUM: 'album'>,
|
||||
'media_class': <MediaClass.ARTIST: 'artist'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:artist:0lLY20XpZ9yDobkbHI7u1y',
|
||||
@ -297,6 +324,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.ALBUM: 'album'>,
|
||||
'media_class': <MediaClass.ARTIST: 'artist'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:artist:0p4nmQO2msCgU4IF37Wi3j',
|
||||
@ -318,10 +346,12 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_class': <MediaClass.PLAYLIST: 'playlist'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:playlist:4WkWJ0EjHEFASDevhM8oPw',
|
||||
@ -332,6 +362,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_class': <MediaClass.PLAYLIST: 'playlist'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:playlist:1RHirWgH1weMsBLi4KOK9d',
|
||||
@ -353,10 +384,12 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:track:71dMjqJ8UJV700zYs5YZCh',
|
||||
@ -367,6 +400,7 @@
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:track:71dMjqJ8UJV700zYs5YZCh',
|
||||
@ -388,10 +422,12 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_class': <MediaClass.ALBUM: 'album'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:album:57MSBg5pBQZH5bfLVDmeuP',
|
||||
@ -402,6 +438,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_class': <MediaClass.ALBUM: 'album'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:album:3DQueEd1Ft9PHWgovDzPKh',
|
||||
@ -423,10 +460,12 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.EPISODE: 'episode'>,
|
||||
'media_class': <MediaClass.PODCAST: 'podcast'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:show:5OzkclFjD6iAjtAuo7aIYt',
|
||||
@ -437,6 +476,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.EPISODE: 'episode'>,
|
||||
'media_class': <MediaClass.PODCAST: 'podcast'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:show:6XYRres0KZtnTqKcLavWR2',
|
||||
@ -458,10 +498,12 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:track:2pj2A25YQK4uMxhZheNx7R',
|
||||
@ -472,6 +514,7 @@
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:track:2lKOI1nwP5qZtZC7TGQVY8',
|
||||
@ -493,10 +536,12 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.ALBUM: 'album'>,
|
||||
'media_class': <MediaClass.ARTIST: 'artist'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:artist:74Yus6IHfa3tWZzXXAYtS2',
|
||||
@ -507,6 +552,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.ALBUM: 'album'>,
|
||||
'media_class': <MediaClass.ARTIST: 'artist'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:artist:6s5ubAp65wXoTZefE01RNR',
|
||||
@ -528,10 +574,12 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:track:3oRoMXsP2NRzm51lldj1RO',
|
||||
@ -542,6 +590,7 @@
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:track:69zgu5rlAie3IPZOEXLxyS',
|
||||
@ -563,10 +612,12 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_class': <MediaClass.ALBUM: 'album'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:album:5SGtrmYbIo0Dsg4kJ4qjM6',
|
||||
@ -577,6 +628,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_class': <MediaClass.ALBUM: 'album'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:album:713lZ7AF55fEFSQgcttj9y',
|
||||
@ -598,10 +650,12 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:track:4rzfv0JLZfVhOhbSQ8o5jZ',
|
||||
@ -612,6 +666,7 @@
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:track:5o3jMYOSbaVz3tkgwhELSV',
|
||||
@ -622,6 +677,7 @@
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:track:4Cy0NHJ8Gh0xMdwyM9RkQm',
|
||||
@ -632,6 +688,7 @@
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:track:6hvFrZNocdt2FcKGCSY5NI',
|
||||
@ -642,6 +699,7 @@
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:track:2E2znCPaS8anQe21GLxcvJ',
|
||||
@ -652,6 +710,7 @@
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': <MediaClass.EPISODE: 'episode'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:episode:3o0RYoo5iOMKSmEbunsbvW',
|
||||
@ -673,10 +732,12 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': <MediaClass.EPISODE: 'episode'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:episode:3ssmxnilHYaKhwRWoBGMbU',
|
||||
@ -687,6 +748,7 @@
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'can_search': False,
|
||||
'children_media_class': None,
|
||||
'media_class': <MediaClass.EPISODE: 'episode'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:episode:1bbj9aqeeZ3UMUlcWN0S03',
|
||||
|
@ -3,6 +3,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_type': '',
|
||||
@ -15,6 +16,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_type': '',
|
||||
@ -39,6 +41,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_type': '',
|
||||
@ -51,6 +54,7 @@
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'can_search': False,
|
||||
'children_media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_type': '',
|
||||
|
Loading…
x
Reference in New Issue
Block a user