Browse media support in universal media player (#85668)
Allow forward and override browse media in universal media player
This commit is contained in:
parent
27ebee1501
commit
6474297d1f
2 changed files with 128 additions and 49 deletions
|
@ -45,6 +45,7 @@ from homeassistant.components.media_player import (
|
|||
MediaPlayerEntityFeature,
|
||||
MediaPlayerState,
|
||||
)
|
||||
from homeassistant.components.media_player.browse_media import BrowseMedia
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_ENTITY_PICTURE,
|
||||
|
@ -78,6 +79,7 @@ from homeassistant.const import (
|
|||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.event import (
|
||||
TrackTemplate,
|
||||
|
@ -93,6 +95,7 @@ ATTR_ACTIVE_CHILD = "active_child"
|
|||
CONF_ATTRS = "attributes"
|
||||
CONF_CHILDREN = "children"
|
||||
CONF_COMMANDS = "commands"
|
||||
CONF_BROWSE_MEDIA_ENTITY = "browse_media_entity"
|
||||
|
||||
STATES_ORDER = [
|
||||
STATE_UNKNOWN,
|
||||
|
@ -119,6 +122,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|||
vol.Optional(CONF_ATTRS, default={}): vol.Or(
|
||||
cv.ensure_list(ATTRS_SCHEMA), ATTRS_SCHEMA
|
||||
),
|
||||
vol.Optional(CONF_BROWSE_MEDIA_ENTITY): cv.string,
|
||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_STATE_TEMPLATE): cv.template,
|
||||
|
@ -136,17 +140,7 @@ async def async_setup_platform(
|
|||
"""Set up the universal media players."""
|
||||
await async_setup_reload_service(hass, "universal", ["media_player"])
|
||||
|
||||
player = UniversalMediaPlayer(
|
||||
hass,
|
||||
config.get(CONF_NAME),
|
||||
config.get(CONF_CHILDREN),
|
||||
config.get(CONF_COMMANDS),
|
||||
config.get(CONF_ATTRS),
|
||||
config.get(CONF_UNIQUE_ID),
|
||||
config.get(CONF_DEVICE_CLASS),
|
||||
config.get(CONF_STATE_TEMPLATE),
|
||||
)
|
||||
|
||||
player = UniversalMediaPlayer(hass, config)
|
||||
async_add_entities([player])
|
||||
|
||||
|
||||
|
@ -158,30 +152,25 @@ class UniversalMediaPlayer(MediaPlayerEntity):
|
|||
def __init__(
|
||||
self,
|
||||
hass,
|
||||
name,
|
||||
children,
|
||||
commands,
|
||||
attributes,
|
||||
unique_id=None,
|
||||
device_class=None,
|
||||
state_template=None,
|
||||
config,
|
||||
):
|
||||
"""Initialize the Universal media device."""
|
||||
self.hass = hass
|
||||
self._name = name
|
||||
self._children = children
|
||||
self._cmds = commands
|
||||
self._name = config.get(CONF_NAME)
|
||||
self._children = config.get(CONF_CHILDREN)
|
||||
self._cmds = config.get(CONF_COMMANDS)
|
||||
self._attrs = {}
|
||||
for key, val in attributes.items():
|
||||
for key, val in config.get(CONF_ATTRS).items():
|
||||
attr = list(map(str.strip, val.split("|", 1)))
|
||||
if len(attr) == 1:
|
||||
attr.append(None)
|
||||
self._attrs[key] = attr
|
||||
self._child_state = None
|
||||
self._state_template_result = None
|
||||
self._state_template = state_template
|
||||
self._device_class = device_class
|
||||
self._attr_unique_id = unique_id
|
||||
self._state_template = config.get(CONF_STATE_TEMPLATE)
|
||||
self._device_class = config.get(CONF_DEVICE_CLASS)
|
||||
self._attr_unique_id = config.get(CONF_UNIQUE_ID)
|
||||
self._browse_media_entity = config.get(CONF_BROWSE_MEDIA_ENTITY)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Subscribe to children and template state changes."""
|
||||
|
@ -497,6 +486,9 @@ class UniversalMediaPlayer(MediaPlayerEntity):
|
|||
if SERVICE_PLAY_MEDIA in self._cmds:
|
||||
flags |= MediaPlayerEntityFeature.PLAY_MEDIA
|
||||
|
||||
if self._browse_media_entity:
|
||||
flags |= MediaPlayerEntityFeature.BROWSE_MEDIA
|
||||
|
||||
if SERVICE_CLEAR_PLAYLIST in self._cmds:
|
||||
flags |= MediaPlayerEntityFeature.CLEAR_PLAYLIST
|
||||
|
||||
|
@ -628,6 +620,20 @@ class UniversalMediaPlayer(MediaPlayerEntity):
|
|||
# Delegate to turn_on or turn_off by default
|
||||
await super().async_toggle()
|
||||
|
||||
async def async_browse_media(
|
||||
self,
|
||||
media_content_type: str | None = None,
|
||||
media_content_id: str | None = None,
|
||||
) -> BrowseMedia:
|
||||
"""Return a BrowseMedia instance."""
|
||||
entity_id = self._browse_media_entity
|
||||
if not entity_id and self._child_state:
|
||||
entity_id = self._child_state.entity_id
|
||||
component: EntityComponent[MediaPlayerEntity] = self.hass.data[DOMAIN]
|
||||
if entity_id and (entity := component.get_entity(entity_id)):
|
||||
return await entity.async_browse_media(media_content_type, media_content_id)
|
||||
raise NotImplementedError()
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update state in HA."""
|
||||
self._child_state = None
|
||||
|
|
|
@ -9,7 +9,8 @@ from homeassistant import config as hass_config
|
|||
import homeassistant.components.input_number as input_number
|
||||
import homeassistant.components.input_select as input_select
|
||||
import homeassistant.components.media_player as media_player
|
||||
from homeassistant.components.media_player import MediaPlayerEntityFeature
|
||||
from homeassistant.components.media_player import MediaClass, MediaPlayerEntityFeature
|
||||
from homeassistant.components.media_player.browse_media import BrowseMedia
|
||||
import homeassistant.components.switch as switch
|
||||
import homeassistant.components.universal.media_player as universal
|
||||
from homeassistant.const import (
|
||||
|
@ -36,6 +37,15 @@ CONFIG_CHILDREN_ONLY = {
|
|||
],
|
||||
}
|
||||
|
||||
MOCK_BROWSE_MEDIA = BrowseMedia(
|
||||
media_class=MediaClass.APP,
|
||||
media_content_id="mock-id",
|
||||
media_content_type="mock-type",
|
||||
title="Mock Title",
|
||||
can_play=False,
|
||||
can_expand=True,
|
||||
)
|
||||
|
||||
|
||||
def validate_config(config):
|
||||
"""Use the platform schema to validate configuration."""
|
||||
|
@ -376,7 +386,7 @@ async def test_master_state(hass: HomeAssistant) -> None:
|
|||
"""Test master state property."""
|
||||
config = validate_config(CONFIG_CHILDREN_ONLY)
|
||||
|
||||
ump = universal.UniversalMediaPlayer(hass, **config)
|
||||
ump = universal.UniversalMediaPlayer(hass, config)
|
||||
|
||||
assert ump.master_state is None
|
||||
|
||||
|
@ -387,7 +397,7 @@ async def test_master_state_with_attrs(
|
|||
"""Test master state property."""
|
||||
config = validate_config(config_children_and_attr)
|
||||
|
||||
ump = universal.UniversalMediaPlayer(hass, **config)
|
||||
ump = universal.UniversalMediaPlayer(hass, config)
|
||||
|
||||
assert ump.master_state == STATE_OFF
|
||||
hass.states.async_set(mock_states.mock_state_switch_id, STATE_ON)
|
||||
|
@ -402,7 +412,7 @@ async def test_master_state_with_bad_attrs(
|
|||
config["attributes"]["state"] = "bad.entity_id"
|
||||
config = validate_config(config)
|
||||
|
||||
ump = universal.UniversalMediaPlayer(hass, **config)
|
||||
ump = universal.UniversalMediaPlayer(hass, config)
|
||||
|
||||
assert ump.master_state == STATE_OFF
|
||||
|
||||
|
@ -411,7 +421,7 @@ async def test_active_child_state(hass: HomeAssistant, mock_states) -> None:
|
|||
"""Test active child state property."""
|
||||
config = validate_config(CONFIG_CHILDREN_ONLY)
|
||||
|
||||
ump = universal.UniversalMediaPlayer(hass, **config)
|
||||
ump = universal.UniversalMediaPlayer(hass, config)
|
||||
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
||||
await ump.async_update()
|
||||
|
||||
|
@ -452,7 +462,7 @@ async def test_name(hass: HomeAssistant) -> None:
|
|||
"""Test name property."""
|
||||
config = validate_config(CONFIG_CHILDREN_ONLY)
|
||||
|
||||
ump = universal.UniversalMediaPlayer(hass, **config)
|
||||
ump = universal.UniversalMediaPlayer(hass, config)
|
||||
|
||||
assert config["name"] == ump.name
|
||||
|
||||
|
@ -461,7 +471,7 @@ async def test_polling(hass: HomeAssistant) -> None:
|
|||
"""Test should_poll property."""
|
||||
config = validate_config(CONFIG_CHILDREN_ONLY)
|
||||
|
||||
ump = universal.UniversalMediaPlayer(hass, **config)
|
||||
ump = universal.UniversalMediaPlayer(hass, config)
|
||||
|
||||
assert ump.should_poll is False
|
||||
|
||||
|
@ -470,7 +480,7 @@ async def test_state_children_only(hass: HomeAssistant, mock_states) -> None:
|
|||
"""Test media player state with only children."""
|
||||
config = validate_config(CONFIG_CHILDREN_ONLY)
|
||||
|
||||
ump = universal.UniversalMediaPlayer(hass, **config)
|
||||
ump = universal.UniversalMediaPlayer(hass, config)
|
||||
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
||||
await ump.async_update()
|
||||
|
||||
|
@ -489,7 +499,7 @@ async def test_state_with_children_and_attrs(
|
|||
"""Test media player with children and master state."""
|
||||
config = validate_config(config_children_and_attr)
|
||||
|
||||
ump = universal.UniversalMediaPlayer(hass, **config)
|
||||
ump = universal.UniversalMediaPlayer(hass, config)
|
||||
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
||||
await ump.async_update()
|
||||
|
||||
|
@ -514,7 +524,7 @@ async def test_volume_level(hass: HomeAssistant, mock_states) -> None:
|
|||
"""Test volume level property."""
|
||||
config = validate_config(CONFIG_CHILDREN_ONLY)
|
||||
|
||||
ump = universal.UniversalMediaPlayer(hass, **config)
|
||||
ump = universal.UniversalMediaPlayer(hass, config)
|
||||
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
||||
await ump.async_update()
|
||||
|
||||
|
@ -538,7 +548,7 @@ async def test_media_image_url(hass: HomeAssistant, mock_states) -> None:
|
|||
test_url = "test_url"
|
||||
config = validate_config(CONFIG_CHILDREN_ONLY)
|
||||
|
||||
ump = universal.UniversalMediaPlayer(hass, **config)
|
||||
ump = universal.UniversalMediaPlayer(hass, config)
|
||||
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
||||
await ump.async_update()
|
||||
|
||||
|
@ -558,7 +568,7 @@ async def test_is_volume_muted_children_only(hass: HomeAssistant, mock_states) -
|
|||
"""Test is volume muted property w/ children only."""
|
||||
config = validate_config(CONFIG_CHILDREN_ONLY)
|
||||
|
||||
ump = universal.UniversalMediaPlayer(hass, **config)
|
||||
ump = universal.UniversalMediaPlayer(hass, config)
|
||||
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
||||
await ump.async_update()
|
||||
|
||||
|
@ -583,7 +593,7 @@ async def test_sound_mode_list_children_and_attr(
|
|||
"""Test sound mode list property w/ children and attrs."""
|
||||
config = validate_config(config_children_and_attr)
|
||||
|
||||
ump = universal.UniversalMediaPlayer(hass, **config)
|
||||
ump = universal.UniversalMediaPlayer(hass, config)
|
||||
|
||||
assert ump.sound_mode_list == "['music', 'movie']"
|
||||
|
||||
|
@ -599,7 +609,7 @@ async def test_source_list_children_and_attr(
|
|||
"""Test source list property w/ children and attrs."""
|
||||
config = validate_config(config_children_and_attr)
|
||||
|
||||
ump = universal.UniversalMediaPlayer(hass, **config)
|
||||
ump = universal.UniversalMediaPlayer(hass, config)
|
||||
|
||||
assert ump.source_list == "['dvd', 'htpc']"
|
||||
|
||||
|
@ -613,7 +623,7 @@ async def test_sound_mode_children_and_attr(
|
|||
"""Test sound modeproperty w/ children and attrs."""
|
||||
config = validate_config(config_children_and_attr)
|
||||
|
||||
ump = universal.UniversalMediaPlayer(hass, **config)
|
||||
ump = universal.UniversalMediaPlayer(hass, config)
|
||||
|
||||
assert ump.sound_mode == "music"
|
||||
|
||||
|
@ -627,7 +637,7 @@ async def test_source_children_and_attr(
|
|||
"""Test source property w/ children and attrs."""
|
||||
config = validate_config(config_children_and_attr)
|
||||
|
||||
ump = universal.UniversalMediaPlayer(hass, **config)
|
||||
ump = universal.UniversalMediaPlayer(hass, config)
|
||||
|
||||
assert ump.source == "dvd"
|
||||
|
||||
|
@ -641,7 +651,7 @@ async def test_volume_level_children_and_attr(
|
|||
"""Test volume level property w/ children and attrs."""
|
||||
config = validate_config(config_children_and_attr)
|
||||
|
||||
ump = universal.UniversalMediaPlayer(hass, **config)
|
||||
ump = universal.UniversalMediaPlayer(hass, config)
|
||||
|
||||
assert ump.volume_level == 0
|
||||
|
||||
|
@ -655,7 +665,7 @@ async def test_is_volume_muted_children_and_attr(
|
|||
"""Test is volume muted property w/ children and attrs."""
|
||||
config = validate_config(config_children_and_attr)
|
||||
|
||||
ump = universal.UniversalMediaPlayer(hass, **config)
|
||||
ump = universal.UniversalMediaPlayer(hass, config)
|
||||
|
||||
assert not ump.is_volume_muted
|
||||
|
||||
|
@ -669,7 +679,7 @@ async def test_supported_features_children_only(
|
|||
"""Test supported media commands with only children."""
|
||||
config = validate_config(CONFIG_CHILDREN_ONLY)
|
||||
|
||||
ump = universal.UniversalMediaPlayer(hass, **config)
|
||||
ump = universal.UniversalMediaPlayer(hass, config)
|
||||
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
||||
await ump.async_update()
|
||||
|
||||
|
@ -709,9 +719,10 @@ async def test_supported_features_children_and_cmds(
|
|||
"play_media": excmd,
|
||||
"clear_playlist": excmd,
|
||||
}
|
||||
config["browse_media_entity"] = "media_player.test"
|
||||
config = validate_config(config)
|
||||
|
||||
ump = universal.UniversalMediaPlayer(hass, **config)
|
||||
ump = universal.UniversalMediaPlayer(hass, config)
|
||||
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
||||
await ump.async_update()
|
||||
|
||||
|
@ -737,6 +748,7 @@ async def test_supported_features_children_and_cmds(
|
|||
| MediaPlayerEntityFeature.PREVIOUS_TRACK
|
||||
| MediaPlayerEntityFeature.PLAY_MEDIA
|
||||
| MediaPlayerEntityFeature.CLEAR_PLAYLIST
|
||||
| MediaPlayerEntityFeature.BROWSE_MEDIA
|
||||
)
|
||||
|
||||
assert check_flags == ump.supported_features
|
||||
|
@ -926,7 +938,7 @@ async def test_supported_features_play_pause(
|
|||
config["commands"] = {"media_play_pause": excmd}
|
||||
config = validate_config(config)
|
||||
|
||||
ump = universal.UniversalMediaPlayer(hass, **config)
|
||||
ump = universal.UniversalMediaPlayer(hass, config)
|
||||
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
||||
await ump.async_update()
|
||||
|
||||
|
@ -946,7 +958,7 @@ async def test_service_call_no_active_child(
|
|||
"""Test a service call to children with no active child."""
|
||||
config = validate_config(config_children_and_attr)
|
||||
|
||||
ump = universal.UniversalMediaPlayer(hass, **config)
|
||||
ump = universal.UniversalMediaPlayer(hass, config)
|
||||
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
||||
await ump.async_update()
|
||||
|
||||
|
@ -966,7 +978,7 @@ async def test_service_call_to_child(hass: HomeAssistant, mock_states) -> None:
|
|||
"""Test service calls that should be routed to a child."""
|
||||
config = validate_config(CONFIG_CHILDREN_ONLY)
|
||||
|
||||
ump = universal.UniversalMediaPlayer(hass, **config)
|
||||
ump = universal.UniversalMediaPlayer(hass, config)
|
||||
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
||||
await ump.async_update()
|
||||
|
||||
|
@ -1045,7 +1057,7 @@ async def test_service_call_to_command(hass: HomeAssistant, mock_states) -> None
|
|||
|
||||
service = async_mock_service(hass, "test", "turn_off")
|
||||
|
||||
ump = universal.UniversalMediaPlayer(hass, **config)
|
||||
ump = universal.UniversalMediaPlayer(hass, config)
|
||||
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
||||
await ump.async_update()
|
||||
|
||||
|
@ -1084,6 +1096,67 @@ async def test_state_template(hass: HomeAssistant) -> None:
|
|||
assert hass.states.get("media_player.tv").state == STATE_OFF
|
||||
|
||||
|
||||
async def test_browse_media(hass: HomeAssistant):
|
||||
"""Test browse media."""
|
||||
await async_setup_component(
|
||||
hass, "media_player", {"media_player": {"platform": "demo"}}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
config = {
|
||||
"name": "test",
|
||||
"platform": "universal",
|
||||
"children": [
|
||||
"media_player.bedroom",
|
||||
],
|
||||
}
|
||||
config = validate_config(config)
|
||||
ump = universal.UniversalMediaPlayer(hass, config)
|
||||
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
||||
await ump.async_update()
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.demo.media_player.MediaPlayerEntity.supported_features",
|
||||
MediaPlayerEntityFeature.BROWSE_MEDIA,
|
||||
), patch(
|
||||
"homeassistant.components.demo.media_player.MediaPlayerEntity.async_browse_media",
|
||||
return_value=MOCK_BROWSE_MEDIA,
|
||||
):
|
||||
result = await ump.async_browse_media()
|
||||
assert result == MOCK_BROWSE_MEDIA
|
||||
|
||||
|
||||
async def test_browse_media_override(hass: HomeAssistant):
|
||||
"""Test browse media override."""
|
||||
await async_setup_component(
|
||||
hass, "media_player", {"media_player": {"platform": "demo"}}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
config = {
|
||||
"name": "test",
|
||||
"platform": "universal",
|
||||
"children": [
|
||||
"media_player.mock1",
|
||||
],
|
||||
"browse_media_entity": "media_player.bedroom",
|
||||
}
|
||||
config = validate_config(config)
|
||||
ump = universal.UniversalMediaPlayer(hass, config)
|
||||
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
||||
await ump.async_update()
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.demo.media_player.MediaPlayerEntity.supported_features",
|
||||
MediaPlayerEntityFeature.BROWSE_MEDIA,
|
||||
), patch(
|
||||
"homeassistant.components.demo.media_player.MediaPlayerEntity.async_browse_media",
|
||||
return_value=MOCK_BROWSE_MEDIA,
|
||||
):
|
||||
result = await ump.async_browse_media()
|
||||
assert result == MOCK_BROWSE_MEDIA
|
||||
|
||||
|
||||
async def test_device_class(hass: HomeAssistant) -> None:
|
||||
"""Test device_class property."""
|
||||
hass.states.async_set("sensor.test_sensor", "on")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue