mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 06:07:17 +00:00
Expose media_player async_browse_media as service (#116452)
* initial commit * make fields optional * x * ruff issues * ruff issues * ruff issues * ruff issues * update example * update description * use constants * Update homeassistant/components/media_player/strings.json Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * update service call metadata * update description * patch the demo * Update homeassistant/components/media_player/strings.json Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * revert unrelated change * update test metadata * update test metadata * change patch target to be more specific --------- Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
da1e3c29ed
commit
1393f417ed
@ -52,7 +52,7 @@ from homeassistant.const import ( # noqa: F401
|
|||||||
STATE_PLAYING,
|
STATE_PLAYING,
|
||||||
STATE_STANDBY,
|
STATE_STANDBY,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant, SupportsResponse
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.deprecation import (
|
from homeassistant.helpers.deprecation import (
|
||||||
@ -124,6 +124,7 @@ from .const import ( # noqa: F401
|
|||||||
CONTENT_AUTH_EXPIRY_TIME,
|
CONTENT_AUTH_EXPIRY_TIME,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
REPEAT_MODES,
|
REPEAT_MODES,
|
||||||
|
SERVICE_BROWSE_MEDIA,
|
||||||
SERVICE_CLEAR_PLAYLIST,
|
SERVICE_CLEAR_PLAYLIST,
|
||||||
SERVICE_JOIN,
|
SERVICE_JOIN,
|
||||||
SERVICE_PLAY_MEDIA,
|
SERVICE_PLAY_MEDIA,
|
||||||
@ -201,6 +202,12 @@ MEDIA_PLAYER_PLAY_MEDIA_SCHEMA = {
|
|||||||
vol.Optional(ATTR_MEDIA_EXTRA, default={}): dict,
|
vol.Optional(ATTR_MEDIA_EXTRA, default={}): dict,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MEDIA_PLAYER_BROWSE_MEDIA_SCHEMA = {
|
||||||
|
vol.Optional(ATTR_MEDIA_CONTENT_TYPE): cv.string,
|
||||||
|
vol.Optional(ATTR_MEDIA_CONTENT_ID): cv.string,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ATTR_TO_PROPERTY = [
|
ATTR_TO_PROPERTY = [
|
||||||
ATTR_MEDIA_VOLUME_LEVEL,
|
ATTR_MEDIA_VOLUME_LEVEL,
|
||||||
ATTR_MEDIA_VOLUME_MUTED,
|
ATTR_MEDIA_VOLUME_MUTED,
|
||||||
@ -431,6 +438,15 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
"async_play_media",
|
"async_play_media",
|
||||||
[MediaPlayerEntityFeature.PLAY_MEDIA],
|
[MediaPlayerEntityFeature.PLAY_MEDIA],
|
||||||
)
|
)
|
||||||
|
component.async_register_entity_service(
|
||||||
|
SERVICE_BROWSE_MEDIA,
|
||||||
|
{
|
||||||
|
vol.Optional(ATTR_MEDIA_CONTENT_TYPE): cv.string,
|
||||||
|
vol.Optional(ATTR_MEDIA_CONTENT_ID): cv.string,
|
||||||
|
},
|
||||||
|
"async_browse_media",
|
||||||
|
supports_response=SupportsResponse.ONLY,
|
||||||
|
)
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_SHUFFLE_SET,
|
SERVICE_SHUFFLE_SET,
|
||||||
{vol.Required(ATTR_MEDIA_SHUFFLE): cv.boolean},
|
{vol.Required(ATTR_MEDIA_SHUFFLE): cv.boolean},
|
||||||
|
@ -173,6 +173,7 @@ _DEPRECATED_MEDIA_TYPE_VIDEO = DeprecatedConstantEnum(MediaType.VIDEO, "2025.10"
|
|||||||
SERVICE_CLEAR_PLAYLIST = "clear_playlist"
|
SERVICE_CLEAR_PLAYLIST = "clear_playlist"
|
||||||
SERVICE_JOIN = "join"
|
SERVICE_JOIN = "join"
|
||||||
SERVICE_PLAY_MEDIA = "play_media"
|
SERVICE_PLAY_MEDIA = "play_media"
|
||||||
|
SERVICE_BROWSE_MEDIA = "browse_media"
|
||||||
SERVICE_SELECT_SOUND_MODE = "select_sound_mode"
|
SERVICE_SELECT_SOUND_MODE = "select_sound_mode"
|
||||||
SERVICE_SELECT_SOURCE = "select_source"
|
SERVICE_SELECT_SOURCE = "select_source"
|
||||||
SERVICE_UNJOIN = "unjoin"
|
SERVICE_UNJOIN = "unjoin"
|
||||||
|
@ -32,6 +32,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
|
"browse_media": {
|
||||||
|
"service": "mdi:folder-search"
|
||||||
|
},
|
||||||
"clear_playlist": {
|
"clear_playlist": {
|
||||||
"service": "mdi:playlist-remove"
|
"service": "mdi:playlist-remove"
|
||||||
},
|
},
|
||||||
|
@ -165,6 +165,22 @@ play_media:
|
|||||||
selector:
|
selector:
|
||||||
boolean:
|
boolean:
|
||||||
|
|
||||||
|
browse_media:
|
||||||
|
target:
|
||||||
|
entity:
|
||||||
|
domain: media_player
|
||||||
|
fields:
|
||||||
|
media_content_type:
|
||||||
|
required: false
|
||||||
|
example: "music"
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
media_content_id:
|
||||||
|
required: false
|
||||||
|
example: "A:ALBUMARTIST/Beatles"
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
|
||||||
select_source:
|
select_source:
|
||||||
target:
|
target:
|
||||||
entity:
|
entity:
|
||||||
|
@ -260,6 +260,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"browse_media": {
|
||||||
|
"name": "Browse media",
|
||||||
|
"description": "Browses the available media.",
|
||||||
|
"fields": {
|
||||||
|
"media_content_id": {
|
||||||
|
"name": "Content ID",
|
||||||
|
"description": "The ID of the content to browse. Integration dependent."
|
||||||
|
},
|
||||||
|
"media_content_type": {
|
||||||
|
"name": "Content type",
|
||||||
|
"description": "The type of the content to browse, such as image, music, tv show, video, episode, channel, or playlist."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"select_source": {
|
"select_source": {
|
||||||
"name": "Select source",
|
"name": "Select source",
|
||||||
"description": "Sends the media player the command to change input source.",
|
"description": "Sends the media player the command to change input source.",
|
||||||
|
@ -10,12 +10,15 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from homeassistant.components import media_player
|
from homeassistant.components import media_player
|
||||||
from homeassistant.components.media_player import (
|
from homeassistant.components.media_player import (
|
||||||
|
ATTR_MEDIA_CONTENT_ID,
|
||||||
|
ATTR_MEDIA_CONTENT_TYPE,
|
||||||
BrowseMedia,
|
BrowseMedia,
|
||||||
MediaClass,
|
MediaClass,
|
||||||
MediaPlayerEnqueue,
|
MediaPlayerEnqueue,
|
||||||
MediaPlayerEntity,
|
MediaPlayerEntity,
|
||||||
MediaPlayerEntityFeature,
|
MediaPlayerEntityFeature,
|
||||||
)
|
)
|
||||||
|
from homeassistant.components.media_player.const import SERVICE_BROWSE_MEDIA
|
||||||
from homeassistant.components.websocket_api import TYPE_RESULT
|
from homeassistant.components.websocket_api import TYPE_RESULT
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF
|
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@ -339,6 +342,75 @@ async def test_media_browse(
|
|||||||
assert msg["result"] == {"bla": "yo"}
|
assert msg["result"] == {"bla": "yo"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_media_browse_service(hass: HomeAssistant) -> None:
|
||||||
|
"""Test browsing media using service call."""
|
||||||
|
await async_setup_component(
|
||||||
|
hass, "media_player", {"media_player": {"platform": "demo"}}
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.demo.media_player.DemoBrowsePlayer.async_browse_media",
|
||||||
|
return_value=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=[
|
||||||
|
BrowseMedia(
|
||||||
|
media_class=MediaClass.ALBUM,
|
||||||
|
media_content_id="album1 content id",
|
||||||
|
media_content_type="album",
|
||||||
|
title="Album 1",
|
||||||
|
can_play=True,
|
||||||
|
can_expand=True,
|
||||||
|
),
|
||||||
|
BrowseMedia(
|
||||||
|
media_class=MediaClass.ALBUM,
|
||||||
|
media_content_id="album2 content id",
|
||||||
|
media_content_type="album",
|
||||||
|
title="Album 2",
|
||||||
|
can_play=True,
|
||||||
|
can_expand=True,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
) as mock_browse_media:
|
||||||
|
result = await hass.services.async_call(
|
||||||
|
"media_player",
|
||||||
|
SERVICE_BROWSE_MEDIA,
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: "media_player.browse",
|
||||||
|
ATTR_MEDIA_CONTENT_TYPE: "album",
|
||||||
|
ATTR_MEDIA_CONTENT_ID: "title=Album*",
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
return_response=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_browse_media.assert_called_with(
|
||||||
|
media_content_type="album", media_content_id="title=Album*"
|
||||||
|
)
|
||||||
|
browse_res: BrowseMedia = result["media_player.browse"]
|
||||||
|
assert browse_res.title == "Mock Title"
|
||||||
|
assert browse_res.media_class == "directory"
|
||||||
|
assert browse_res.media_content_type == "mock-type"
|
||||||
|
assert browse_res.media_content_id == "mock-id"
|
||||||
|
assert browse_res.can_play is False
|
||||||
|
assert browse_res.can_expand is True
|
||||||
|
assert len(browse_res.children) == 2
|
||||||
|
assert browse_res.children[0].title == "Album 1"
|
||||||
|
assert browse_res.children[0].media_class == "album"
|
||||||
|
assert browse_res.children[0].media_content_id == "album1 content id"
|
||||||
|
assert browse_res.children[0].media_content_type == "album"
|
||||||
|
assert browse_res.children[1].title == "Album 2"
|
||||||
|
assert browse_res.children[1].media_class == "album"
|
||||||
|
assert browse_res.children[1].media_content_id == "album2 content id"
|
||||||
|
assert browse_res.children[1].media_content_type == "album"
|
||||||
|
|
||||||
|
|
||||||
async def test_group_members_available_when_off(hass: HomeAssistant) -> None:
|
async def test_group_members_available_when_off(hass: HomeAssistant) -> None:
|
||||||
"""Test that group_members are still available when media_player is off."""
|
"""Test that group_members are still available when media_player is off."""
|
||||||
await async_setup_component(
|
await async_setup_component(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user