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:
Josef Zweck 2025-04-16 23:09:16 +02:00 committed by GitHub
parent fe248a2ebd
commit bf69d4e0a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 439 additions and 3 deletions

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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.

View File

@ -9,3 +9,7 @@ class MediaPlayerException(HomeAssistantError):
class BrowseError(MediaPlayerException):
"""Error while browsing."""
class SearchError(MediaPlayerException):
"""Error while searching."""

View File

@ -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": "",
},
],

View File

@ -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,
},

View File

@ -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',

View File

@ -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": [],

View File

@ -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,
}

View File

@ -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()}

View File

@ -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,
}

View File

@ -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"},

View File

@ -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',

View File

@ -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',

View File

@ -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,
}

View File

@ -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(

View File

@ -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": [],

View File

@ -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': '',

View File

@ -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',

View File

@ -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': '',