Use media_selector for media_player.play_media (#150721)

This commit is contained in:
karwosts
2025-08-27 03:39:33 -07:00
committed by GitHub
parent 81a5b4a684
commit 4821c9ec29
4 changed files with 82 additions and 16 deletions

View File

@@ -161,6 +161,8 @@ CACHE_LOCK: Final = "lock"
CACHE_URL: Final = "url" CACHE_URL: Final = "url"
CACHE_CONTENT: Final = "content" CACHE_CONTENT: Final = "content"
ATTR_MEDIA = "media"
class MediaPlayerEnqueue(StrEnum): class MediaPlayerEnqueue(StrEnum):
"""Enqueue types for playing media.""" """Enqueue types for playing media."""
@@ -200,6 +202,24 @@ _DEPRECATED_DEVICE_CLASS_RECEIVER = DeprecatedConstantEnum(
DEVICE_CLASSES = [cls.value for cls in MediaPlayerDeviceClass] DEVICE_CLASSES = [cls.value for cls in MediaPlayerDeviceClass]
def _promote_media_fields(data: dict[str, Any]) -> dict[str, Any]:
"""If 'media' key exists, promote its fields to the top level."""
if ATTR_MEDIA in data and isinstance(data[ATTR_MEDIA], dict):
if ATTR_MEDIA_CONTENT_TYPE in data or ATTR_MEDIA_CONTENT_ID in data:
raise vol.Invalid(
f"Play media cannot contain '{ATTR_MEDIA}' and '{ATTR_MEDIA_CONTENT_ID}' or '{ATTR_MEDIA_CONTENT_TYPE}'"
)
media_data = data[ATTR_MEDIA]
if ATTR_MEDIA_CONTENT_TYPE in media_data:
data[ATTR_MEDIA_CONTENT_TYPE] = media_data[ATTR_MEDIA_CONTENT_TYPE]
if ATTR_MEDIA_CONTENT_ID in media_data:
data[ATTR_MEDIA_CONTENT_ID] = media_data[ATTR_MEDIA_CONTENT_ID]
del data[ATTR_MEDIA]
return data
MEDIA_PLAYER_PLAY_MEDIA_SCHEMA = { MEDIA_PLAYER_PLAY_MEDIA_SCHEMA = {
vol.Required(ATTR_MEDIA_CONTENT_TYPE): cv.string, vol.Required(ATTR_MEDIA_CONTENT_TYPE): cv.string,
vol.Required(ATTR_MEDIA_CONTENT_ID): cv.string, vol.Required(ATTR_MEDIA_CONTENT_ID): cv.string,
@@ -436,6 +456,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
component.async_register_entity_service( component.async_register_entity_service(
SERVICE_PLAY_MEDIA, SERVICE_PLAY_MEDIA,
vol.All( vol.All(
_promote_media_fields,
cv.make_entity_service_schema(MEDIA_PLAYER_PLAY_MEDIA_SCHEMA), cv.make_entity_service_schema(MEDIA_PLAYER_PLAY_MEDIA_SCHEMA),
_rewrite_enqueue, _rewrite_enqueue,
_rename_keys( _rename_keys(

View File

@@ -131,17 +131,13 @@ play_media:
supported_features: supported_features:
- media_player.MediaPlayerEntityFeature.PLAY_MEDIA - media_player.MediaPlayerEntityFeature.PLAY_MEDIA
fields: fields:
media_content_id: media:
required: true required: true
example: "https://home-assistant.io/images/cast/splash.png"
selector: selector:
text: media:
example:
media_content_type: media_content_id: "https://home-assistant.io/images/cast/splash.png"
required: true media_content_type: "music"
example: "music"
selector:
text:
enqueue: enqueue:
filter: filter:

View File

@@ -242,13 +242,9 @@
"name": "Play media", "name": "Play media",
"description": "Starts playing specified media.", "description": "Starts playing specified media.",
"fields": { "fields": {
"media_content_id": { "media": {
"name": "Content ID", "name": "Media",
"description": "The ID of the content to play. Platform dependent." "description": "The media selected to play."
},
"media_content_type": {
"name": "Content type",
"description": "The type of the content to play, such as image, music, tv show, video, episode, channel, or playlist."
}, },
"enqueue": { "enqueue": {
"name": "Enqueue", "name": "Enqueue",

View File

@@ -654,3 +654,56 @@ async def test_get_async_get_browse_image_quoting(
url = player.get_browse_image_url("album", media_content_id) url = player.get_browse_image_url("album", media_content_id)
await client.get(url) await client.get(url)
mock_browse_image.assert_called_with("album", media_content_id, None) mock_browse_image.assert_called_with("album", media_content_id, None)
async def test_play_media_via_selector(hass: HomeAssistant) -> None:
"""Test that play_media data under 'media' is remapped to top level keys for backward compatibility."""
await async_setup_component(
hass, "media_player", {"media_player": {"platform": "demo"}}
)
await hass.async_block_till_done()
# Fake group support for DemoYoutubePlayer
with patch(
"homeassistant.components.demo.media_player.DemoYoutubePlayer.play_media",
) as mock_play_media:
await hass.services.async_call(
"media_player",
"play_media",
{
"entity_id": "media_player.bedroom",
"media": {
"media_content_type": "music",
"media_content_id": "1234",
},
},
blocking=True,
)
await hass.services.async_call(
"media_player",
"play_media",
{
"entity_id": "media_player.bedroom",
"media_content_type": "music",
"media_content_id": "1234",
},
blocking=True,
)
assert len(mock_play_media.mock_calls) == 2
assert mock_play_media.mock_calls[0].args == mock_play_media.mock_calls[1].args
with pytest.raises(vol.Invalid, match="Play media cannot contain 'media'"):
await hass.services.async_call(
"media_player",
"play_media",
{
"media_content_id": "1234",
"entity_id": "media_player.bedroom",
"media": {
"media_content_type": "music",
"media_content_id": "1234",
},
},
blocking=True,
)