From e901dc4ec413c21c317f4f7e1e70573c19b3d2ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Costa?= Date: Wed, 16 Apr 2025 19:43:38 +0100 Subject: [PATCH 1/8] Move _attr_should_poll to base Whirlpool entity class (#143100) --- homeassistant/components/whirlpool/climate.py | 1 - homeassistant/components/whirlpool/entity.py | 1 + homeassistant/components/whirlpool/sensor.py | 2 -- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/whirlpool/climate.py b/homeassistant/components/whirlpool/climate.py index 6829dca3004..0cc9e8bca84 100644 --- a/homeassistant/components/whirlpool/climate.py +++ b/homeassistant/components/whirlpool/climate.py @@ -75,7 +75,6 @@ class AirConEntity(WhirlpoolEntity, ClimateEntity): _attr_hvac_modes = SUPPORTED_HVAC_MODES _attr_max_temp = SUPPORTED_MAX_TEMP _attr_min_temp = SUPPORTED_MIN_TEMP - _attr_should_poll = False _attr_supported_features = ( ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE diff --git a/homeassistant/components/whirlpool/entity.py b/homeassistant/components/whirlpool/entity.py index 3f2fc81d358..a53fe0af263 100644 --- a/homeassistant/components/whirlpool/entity.py +++ b/homeassistant/components/whirlpool/entity.py @@ -12,6 +12,7 @@ class WhirlpoolEntity(Entity): """Base class for Whirlpool entities.""" _attr_has_entity_name = True + _attr_should_poll = False def __init__(self, appliance: Appliance, unique_id_suffix: str = "") -> None: """Initialize the entity.""" diff --git a/homeassistant/components/whirlpool/sensor.py b/homeassistant/components/whirlpool/sensor.py index c41fda4197f..60dd215ebb5 100644 --- a/homeassistant/components/whirlpool/sensor.py +++ b/homeassistant/components/whirlpool/sensor.py @@ -174,8 +174,6 @@ async def async_setup_entry( class WhirlpoolSensor(WhirlpoolEntity, SensorEntity): """A class for the Whirlpool sensors.""" - _attr_should_poll = False - def __init__( self, appliance: Appliance, description: WhirlpoolSensorEntityDescription ) -> None: From 0ec4652b524bc283c2d8ad9609b7fd3b3689ef27 Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Wed, 16 Apr 2025 20:44:24 +0200 Subject: [PATCH 2/8] Use common state for "Manual", unify intercardinal directions in `netatmo` (#143062) In US English the intercardinal directions (Northeast, Southwest, etc.) are written in single words, not using hyphens. That can be adapted in Lokalise for Home Assistant's "English (United Kingdom)" UI language. Making them identical in both occurrences also resolves the missing sentence-casing of "North-East" etc. --- homeassistant/components/netatmo/strings.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/netatmo/strings.json b/homeassistant/components/netatmo/strings.json index afa8a670704..580b49ea646 100644 --- a/homeassistant/components/netatmo/strings.json +++ b/homeassistant/components/netatmo/strings.json @@ -29,10 +29,10 @@ "public_weather": { "data": { "area_name": "Name of the area", - "lat_ne": "North-East corner latitude", - "lon_ne": "North-East corner longitude", - "lat_sw": "South-West corner latitude", - "lon_sw": "South-West corner longitude", + "lat_ne": "Northeast corner latitude", + "lon_ne": "Northeast corner longitude", + "lat_sw": "Southwest corner latitude", + "lon_sw": "Southwest corner longitude", "mode": "Calculation", "show_on_map": "Show on map" }, @@ -175,7 +175,7 @@ "state": { "frost_guard": "Frost guard", "schedule": "Schedule", - "manual": "Manual" + "manual": "[%key:common::state::manual%]" } } } @@ -206,13 +206,13 @@ "name": "Wind direction", "state": { "n": "North", - "ne": "North-east", + "ne": "Northeast", "e": "East", - "se": "South-east", + "se": "Southeast", "s": "South", - "sw": "South-west", + "sw": "Southwest", "w": "West", - "nw": "North-west" + "nw": "Northwest" } }, "wind_angle": { From 21fabd3afa0acb002bb8211a13129b1ee5b214ce Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Wed, 16 Apr 2025 20:47:07 +0200 Subject: [PATCH 3/8] Use common state for "Manual" in `tolo` (#143104) --- homeassistant/components/tolo/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/tolo/strings.json b/homeassistant/components/tolo/strings.json index c55498b8d92..82b6ecee9e7 100644 --- a/homeassistant/components/tolo/strings.json +++ b/homeassistant/components/tolo/strings.json @@ -59,7 +59,7 @@ "name": "Lamp mode", "state": { "automatic": "Automatic", - "manual": "Manual" + "manual": "[%key:common::state::manual%]" } }, "aroma_therapy_slot": { From 3c1d93f5030c2b41776d44527b08ffbc3bd480a4 Mon Sep 17 00:00:00 2001 From: Guido Schmitz Date: Wed, 16 Apr 2025 21:12:50 +0200 Subject: [PATCH 4/8] Use entity_registry_enabled_by_default fixture in devolo Home Network (#143108) --- .../devolo_home_network/test_device_tracker.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tests/components/devolo_home_network/test_device_tracker.py b/tests/components/devolo_home_network/test_device_tracker.py index 1cce11c36f9..ac86eb54961 100644 --- a/tests/components/devolo_home_network/test_device_tracker.py +++ b/tests/components/devolo_home_network/test_device_tracker.py @@ -4,6 +4,7 @@ from unittest.mock import AsyncMock from devolo_plc_api.exceptions.device import DeviceUnavailable from freezegun.api import FrozenDateTimeFactory +import pytest from syrupy.assertion import SnapshotAssertion from homeassistant.components.device_tracker import DOMAIN as PLATFORM @@ -25,6 +26,7 @@ STATION = CONNECTED_STATIONS[0] SERIAL = DISCOVERY_INFO.properties["SN"] +@pytest.mark.usefixtures("entity_registry_enabled_by_default") async def test_device_tracker( hass: HomeAssistant, mock_device: MockDevice, @@ -42,14 +44,6 @@ async def test_device_tracker( freezer.tick(LONG_UPDATE_INTERVAL) async_fire_time_changed(hass) await hass.async_block_till_done() - - # Enable entity - entity_registry.async_update_entity(state_key, disabled_by=None) - await hass.async_block_till_done() - freezer.tick(LONG_UPDATE_INTERVAL) - async_fire_time_changed(hass) - await hass.async_block_till_done() - assert hass.states.get(state_key) == snapshot # Emulate state change From fa75b477e95eeb5a5caf114984432ae794a98390 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 16 Apr 2025 22:11:14 +0200 Subject: [PATCH 5/8] Add device class for fuel sensor in StarLine integration (#143111) --- homeassistant/components/starline/sensor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/starline/sensor.py b/homeassistant/components/starline/sensor.py index 16988f1a9dc..916d0a9f26b 100644 --- a/homeassistant/components/starline/sensor.py +++ b/homeassistant/components/starline/sensor.py @@ -61,6 +61,7 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( SensorEntityDescription( key="fuel", translation_key="fuel", + device_class=SensorDeviceClass.VOLUME, state_class=SensorStateClass.MEASUREMENT, ), SensorEntityDescription( From 49ad9a8bd5047aaa831ec11f53c688fd8020e4fd Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Wed, 16 Apr 2025 22:28:34 +0200 Subject: [PATCH 6/8] Use common states for "Auto" and "Manual" in `smartthings` (#142976) --- homeassistant/components/smartthings/strings.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/smartthings/strings.json b/homeassistant/components/smartthings/strings.json index dfcaa094d1b..fb88aa5e4a0 100644 --- a/homeassistant/components/smartthings/strings.json +++ b/homeassistant/components/smartthings/strings.json @@ -354,11 +354,11 @@ "robot_cleaner_cleaning_mode": { "name": "Cleaning mode", "state": { - "auto": "Auto", + "stop": "[%key:common::action::stop%]", + "auto": "[%key:common::state::auto%]", + "manual": "[%key:common::state::manual%]", "part": "Partial", "repeat": "Repeat", - "manual": "Manual", - "stop": "[%key:common::action::stop%]", "map": "Map" } }, From fe248a2ebda346c37562935149af6c0d13432925 Mon Sep 17 00:00:00 2001 From: Eric Park Date: Wed, 16 Apr 2025 16:41:32 -0400 Subject: [PATCH 7/8] Keep track of last play status update time in Apple TV (#142838) --- homeassistant/components/apple_tv/media_player.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/apple_tv/media_player.py b/homeassistant/components/apple_tv/media_player.py index b68d74e6115..b6d451a9ea0 100644 --- a/homeassistant/components/apple_tv/media_player.py +++ b/homeassistant/components/apple_tv/media_player.py @@ -120,6 +120,7 @@ class AppleTvMediaPlayer( """Initialize the Apple TV media player.""" super().__init__(name, identifier, manager) self._playing: Playing | None = None + self._playing_last_updated: datetime | None = None self._app_list: dict[str, str] = {} @callback @@ -209,6 +210,7 @@ class AppleTvMediaPlayer( This is a callback function from pyatv.interface.PushListener. """ self._playing = playstatus + self._playing_last_updated = dt_util.utcnow() self.async_write_ha_state() @callback @@ -316,7 +318,7 @@ class AppleTvMediaPlayer( def media_position_updated_at(self) -> datetime | None: """Last valid time of media position.""" if self.state in {MediaPlayerState.PLAYING, MediaPlayerState.PAUSED}: - return dt_util.utcnow() + return self._playing_last_updated return None async def async_play_media( From bf69d4e0a817e59f7919c241d502ae4c12ea51dd Mon Sep 17 00:00:00 2001 From: Josef Zweck Date: Wed, 16 Apr 2025 23:09:16 +0200 Subject: [PATCH 8/8] 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 * fix hue test --------- Co-authored-by: Marcel van der Veldt Co-authored-by: Paulus Schoutsen --- homeassistant/components/demo/media_player.py | 9 ++ .../components/media_player/__init__.py | 121 ++++++++++++++++- .../components/media_player/browse_media.py | 28 ++++ .../components/media_player/const.py | 4 + .../components/media_player/errors.py | 4 + .../androidtv_remote/test_media_player.py | 3 + .../bang_olufsen/test_media_player.py | 2 + .../snapshots/test_media_browser.ambr | 3 + tests/components/cast/test_media_player.py | 5 + .../components/dlna_dmr/test_media_player.py | 4 + tests/components/emulated_hue/test_hue_api.py | 1 + .../fully_kiosk/test_media_player.py | 1 + tests/components/google_assistant/__init__.py | 7 + .../heos/snapshots/test_media_player.ambr | 13 ++ .../jellyfin/snapshots/test_media_source.ambr | 8 ++ .../components/jellyfin/test_media_player.py | 2 + tests/components/media_player/test_init.py | 124 +++++++++++++++++- .../components/motioneye/test_media_source.py | 16 +++ .../sonos/snapshots/test_media_browser.ambr | 21 +++ .../spotify/snapshots/test_media_browser.ambr | 62 +++++++++ .../snapshots/test_media_source.ambr | 4 + 21 files changed, 439 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/demo/media_player.py b/homeassistant/components/demo/media_player.py index de2a2cb3937..5cd83722742 100644 --- a/homeassistant/components/demo/media_player.py +++ b/homeassistant/components/demo/media_player.py @@ -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 diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 45d08bea7ce..0979852ecce 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -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) diff --git a/homeassistant/components/media_player/browse_media.py b/homeassistant/components/media_player/browse_media.py index d234050c1b2..ec9d70476a3 100644 --- a/homeassistant/components/media_player/browse_media.py +++ b/homeassistant/components/media_player/browse_media.py @@ -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"" + + +@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) diff --git a/homeassistant/components/media_player/const.py b/homeassistant/components/media_player/const.py index 387fdb05401..8d85d7cd106 100644 --- a/homeassistant/components/media_player/const.py +++ b/homeassistant/components/media_player/const.py @@ -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. diff --git a/homeassistant/components/media_player/errors.py b/homeassistant/components/media_player/errors.py index 5888ba6b5b0..23db94a330e 100644 --- a/homeassistant/components/media_player/errors.py +++ b/homeassistant/components/media_player/errors.py @@ -9,3 +9,7 @@ class MediaPlayerException(HomeAssistantError): class BrowseError(MediaPlayerException): """Error while browsing.""" + + +class SearchError(MediaPlayerException): + """Error while searching.""" diff --git a/tests/components/androidtv_remote/test_media_player.py b/tests/components/androidtv_remote/test_media_player.py index 0ca8a3045fb..2af8aeb2f56 100644 --- a/tests/components/androidtv_remote/test_media_player.py +++ b/tests/components/androidtv_remote/test_media_player.py @@ -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": "", }, ], diff --git a/tests/components/bang_olufsen/test_media_player.py b/tests/components/bang_olufsen/test_media_player.py index 70b826f0b92..a389f9fa818 100644 --- a/tests/components/bang_olufsen/test_media_player.py +++ b/tests/components/bang_olufsen/test_media_player.py @@ -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, }, diff --git a/tests/components/cambridge_audio/snapshots/test_media_browser.ambr b/tests/components/cambridge_audio/snapshots/test_media_browser.ambr index 180d5ed1bb0..9f0fffdac49 100644 --- a/tests/components/cambridge_audio/snapshots/test_media_browser.ambr +++ b/tests/components/cambridge_audio/snapshots/test_media_browser.ambr @@ -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', diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 668ed985154..386b9270571 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -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": [], diff --git a/tests/components/dlna_dmr/test_media_player.py b/tests/components/dlna_dmr/test_media_player.py index a92f7807912..f1ac2d6b1c2 100644 --- a/tests/components/dlna_dmr/test_media_player.py +++ b/tests/components/dlna_dmr/test_media_player.py @@ -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, } diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py index 97dcc782096..0f8ffcbee9f 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -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()} diff --git a/tests/components/fully_kiosk/test_media_player.py b/tests/components/fully_kiosk/test_media_player.py index aa53421616f..e46a50100b2 100644 --- a/tests/components/fully_kiosk/test_media_player.py +++ b/tests/components/fully_kiosk/test_media_player.py @@ -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, } diff --git a/tests/components/google_assistant/__init__.py b/tests/components/google_assistant/__init__.py index 6be58f50469..015c20e8393 100644 --- a/tests/components/google_assistant/__init__.py +++ b/tests/components/google_assistant/__init__.py @@ -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"}, diff --git a/tests/components/heos/snapshots/test_media_player.ambr b/tests/components/heos/snapshots/test_media_player.ambr index d366a7f6317..68ab24c6479 100644 --- a/tests/components/heos/snapshots/test_media_player.ambr +++ b/tests/components/heos/snapshots/test_media_player.ambr @@ -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', diff --git a/tests/components/jellyfin/snapshots/test_media_source.ambr b/tests/components/jellyfin/snapshots/test_media_source.ambr index 6f46aaf3f9b..12398f16b8f 100644 --- a/tests/components/jellyfin/snapshots/test_media_source.ambr +++ b/tests/components/jellyfin/snapshots/test_media_source.ambr @@ -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', diff --git a/tests/components/jellyfin/test_media_player.py b/tests/components/jellyfin/test_media_player.py index c6f015e9bb4..404fdc801ee 100644 --- a/tests/components/jellyfin/test_media_player.py +++ b/tests/components/jellyfin/test_media_player.py @@ -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, } diff --git a/tests/components/media_player/test_init.py b/tests/components/media_player/test_init.py index 1878d7372f6..090ea9f27e2 100644 --- a/tests/components/media_player/test_init.py +++ b/tests/components/media_player/test_init.py @@ -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( diff --git a/tests/components/motioneye/test_media_source.py b/tests/components/motioneye/test_media_source.py index f8a750d50da..c650e2ac59d 100644 --- a/tests/components/motioneye/test_media_source.py +++ b/tests/components/motioneye/test_media_source.py @@ -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": [], diff --git a/tests/components/sonos/snapshots/test_media_browser.ambr b/tests/components/sonos/snapshots/test_media_browser.ambr index 24f08eaf95b..faa06a9adc2 100644 --- a/tests/components/sonos/snapshots/test_media_browser.ambr +++ b/tests/components/sonos/snapshots/test_media_browser.ambr @@ -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': '', diff --git a/tests/components/spotify/snapshots/test_media_browser.ambr b/tests/components/spotify/snapshots/test_media_browser.ambr index 6b217977227..e241893df3b 100644 --- a/tests/components/spotify/snapshots/test_media_browser.ambr +++ b/tests/components/spotify/snapshots/test_media_browser.ambr @@ -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': , 'media_class': , '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': , 'media_class': , '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': , 'media_class': , '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': , 'media_class': , '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': , 'media_class': , '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': , 'media_class': , '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': , 'media_class': , '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': , 'media_class': , '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': , 'media_class': , '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': , 'media_class': , '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': , 'media_class': , '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': , 'media_class': , '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': , 'media_class': , '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': , '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': , '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': , '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': , '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': , 'media_class': , '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': , 'media_class': , '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': , 'media_class': , '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': , 'media_class': , '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': , 'media_class': , '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': , 'media_class': , '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': , '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': , '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': , 'media_class': , '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': , 'media_class': , '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': , 'media_class': , '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': , 'media_class': , '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': , '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': , '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': , 'media_class': , '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': , 'media_class': , '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': , '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': , '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': , 'media_class': , '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': , 'media_class': , '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': , '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': , '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': , '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': , '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': , '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': , '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': , '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': , 'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:episode:1bbj9aqeeZ3UMUlcWN0S03', diff --git a/tests/components/system_bridge/snapshots/test_media_source.ambr b/tests/components/system_bridge/snapshots/test_media_source.ambr index 53e0e8416e9..954332c932a 100644 --- a/tests/components/system_bridge/snapshots/test_media_source.ambr +++ b/tests/components/system_bridge/snapshots/test_media_source.ambr @@ -3,6 +3,7 @@ dict({ 'can_expand': True, 'can_play': False, + 'can_search': False, 'children_media_class': , 'media_class': , 'media_content_type': '', @@ -15,6 +16,7 @@ dict({ 'can_expand': True, 'can_play': False, + 'can_search': False, 'children_media_class': , 'media_class': , 'media_content_type': '', @@ -39,6 +41,7 @@ dict({ 'can_expand': True, 'can_play': False, + 'can_search': False, 'children_media_class': , 'media_class': , 'media_content_type': '', @@ -51,6 +54,7 @@ dict({ 'can_expand': True, 'can_play': False, + 'can_search': False, 'children_media_class': , 'media_class': , 'media_content_type': '',