Check supported features in media player reproduce state (#70055)

This commit is contained in:
Paulus Schoutsen 2022-04-14 12:44:41 -07:00 committed by GitHub
parent 7474e2f96a
commit a5134d9ba2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 140 additions and 31 deletions

View File

@ -6,6 +6,7 @@ from collections.abc import Iterable
from typing import Any from typing import Any
from homeassistant.const import ( from homeassistant.const import (
ATTR_SUPPORTED_FEATURES,
SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PLAY,
SERVICE_MEDIA_STOP, SERVICE_MEDIA_STOP,
@ -33,6 +34,7 @@ from .const import (
SERVICE_PLAY_MEDIA, SERVICE_PLAY_MEDIA,
SERVICE_SELECT_SOUND_MODE, SERVICE_SELECT_SOUND_MODE,
SERVICE_SELECT_SOURCE, SERVICE_SELECT_SOURCE,
MediaPlayerEntityFeature,
) )
# mypy: allow-untyped-defs # mypy: allow-untyped-defs
@ -46,6 +48,8 @@ async def _async_reproduce_states(
reproduce_options: dict[str, Any] | None = None, reproduce_options: dict[str, Any] | None = None,
) -> None: ) -> None:
"""Reproduce component states.""" """Reproduce component states."""
cur_state = hass.states.get(state.entity_id)
features = cur_state.attributes[ATTR_SUPPORTED_FEATURES] if cur_state else 0
async def call_service(service: str, keys: Iterable) -> None: async def call_service(service: str, keys: Iterable) -> None:
"""Call service with set of attributes given.""" """Call service with set of attributes given."""
@ -59,28 +63,48 @@ async def _async_reproduce_states(
) )
if state.state == STATE_OFF: if state.state == STATE_OFF:
await call_service(SERVICE_TURN_OFF, []) if features & MediaPlayerEntityFeature.TURN_OFF:
await call_service(SERVICE_TURN_OFF, [])
# entities that are off have no other attributes to restore # entities that are off have no other attributes to restore
return return
if state.state in ( if (
STATE_ON, state.state
STATE_PLAYING, in (
STATE_IDLE, STATE_ON,
STATE_PAUSED, STATE_PLAYING,
STATE_IDLE,
STATE_PAUSED,
)
and features & MediaPlayerEntityFeature.TURN_ON
): ):
await call_service(SERVICE_TURN_ON, []) await call_service(SERVICE_TURN_ON, [])
if ATTR_MEDIA_VOLUME_LEVEL in state.attributes: cur_state = hass.states.get(state.entity_id)
features = cur_state.attributes[ATTR_SUPPORTED_FEATURES] if cur_state else 0
if (
ATTR_MEDIA_VOLUME_LEVEL in state.attributes
and features & MediaPlayerEntityFeature.VOLUME_SET
):
await call_service(SERVICE_VOLUME_SET, [ATTR_MEDIA_VOLUME_LEVEL]) await call_service(SERVICE_VOLUME_SET, [ATTR_MEDIA_VOLUME_LEVEL])
if ATTR_MEDIA_VOLUME_MUTED in state.attributes: if (
ATTR_MEDIA_VOLUME_MUTED in state.attributes
and features & MediaPlayerEntityFeature.VOLUME_MUTE
):
await call_service(SERVICE_VOLUME_MUTE, [ATTR_MEDIA_VOLUME_MUTED]) await call_service(SERVICE_VOLUME_MUTE, [ATTR_MEDIA_VOLUME_MUTED])
if ATTR_INPUT_SOURCE in state.attributes: if (
ATTR_INPUT_SOURCE in state.attributes
and features & MediaPlayerEntityFeature.SELECT_SOURCE
):
await call_service(SERVICE_SELECT_SOURCE, [ATTR_INPUT_SOURCE]) await call_service(SERVICE_SELECT_SOURCE, [ATTR_INPUT_SOURCE])
if ATTR_SOUND_MODE in state.attributes: if (
ATTR_SOUND_MODE in state.attributes
and features & MediaPlayerEntityFeature.SELECT_SOUND_MODE
):
await call_service(SERVICE_SELECT_SOUND_MODE, [ATTR_SOUND_MODE]) await call_service(SERVICE_SELECT_SOUND_MODE, [ATTR_SOUND_MODE])
already_playing = False already_playing = False
@ -88,18 +112,25 @@ async def _async_reproduce_states(
if (ATTR_MEDIA_CONTENT_TYPE in state.attributes) and ( if (ATTR_MEDIA_CONTENT_TYPE in state.attributes) and (
ATTR_MEDIA_CONTENT_ID in state.attributes ATTR_MEDIA_CONTENT_ID in state.attributes
): ):
await call_service( if features & MediaPlayerEntityFeature.PLAY_MEDIA:
SERVICE_PLAY_MEDIA, await call_service(
[ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_ENQUEUE], SERVICE_PLAY_MEDIA,
) [ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_ENQUEUE],
)
already_playing = True already_playing = True
if state.state == STATE_PLAYING and not already_playing: if (
not already_playing
and state.state == STATE_PLAYING
and features & MediaPlayerEntityFeature.PLAY
):
await call_service(SERVICE_MEDIA_PLAY, []) await call_service(SERVICE_MEDIA_PLAY, [])
elif state.state == STATE_IDLE: elif state.state == STATE_IDLE:
await call_service(SERVICE_MEDIA_STOP, []) if features & MediaPlayerEntityFeature.STOP:
await call_service(SERVICE_MEDIA_STOP, [])
elif state.state == STATE_PAUSED: elif state.state == STATE_PAUSED:
await call_service(SERVICE_MEDIA_PAUSE, []) if features & MediaPlayerEntityFeature.PAUSE:
await call_service(SERVICE_MEDIA_PAUSE, [])
async def async_reproduce_states( async def async_reproduce_states(

View File

@ -14,9 +14,11 @@ from homeassistant.components.media_player.const import (
SERVICE_PLAY_MEDIA, SERVICE_PLAY_MEDIA,
SERVICE_SELECT_SOUND_MODE, SERVICE_SELECT_SOUND_MODE,
SERVICE_SELECT_SOURCE, SERVICE_SELECT_SOURCE,
MediaPlayerEntityFeature,
) )
from homeassistant.components.media_player.reproduce_state import async_reproduce_states from homeassistant.components.media_player.reproduce_state import async_reproduce_states
from homeassistant.const import ( from homeassistant.const import (
ATTR_SUPPORTED_FEATURES,
SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PLAY,
SERVICE_MEDIA_STOP, SERVICE_MEDIA_STOP,
@ -39,31 +41,47 @@ ENTITY_2 = "media_player.test2"
@pytest.mark.parametrize( @pytest.mark.parametrize(
"service,state", "service,state,supported_feature",
[ [
(SERVICE_TURN_ON, STATE_ON), (SERVICE_TURN_ON, STATE_ON, MediaPlayerEntityFeature.TURN_ON),
(SERVICE_TURN_OFF, STATE_OFF), (SERVICE_TURN_OFF, STATE_OFF, MediaPlayerEntityFeature.TURN_OFF),
(SERVICE_MEDIA_PLAY, STATE_PLAYING), (SERVICE_MEDIA_PLAY, STATE_PLAYING, MediaPlayerEntityFeature.PLAY),
(SERVICE_MEDIA_STOP, STATE_IDLE), (SERVICE_MEDIA_STOP, STATE_IDLE, MediaPlayerEntityFeature.STOP),
(SERVICE_MEDIA_PAUSE, STATE_PAUSED), (SERVICE_MEDIA_PAUSE, STATE_PAUSED, MediaPlayerEntityFeature.PAUSE),
], ],
) )
async def test_state(hass, service, state): async def test_state(hass, service, state, supported_feature):
"""Test that we can turn a state into a service call.""" """Test that we can turn a state into a service call."""
calls_1 = async_mock_service(hass, DOMAIN, service) calls_1 = async_mock_service(hass, DOMAIN, service)
if service != SERVICE_TURN_ON: if service != SERVICE_TURN_ON:
async_mock_service(hass, DOMAIN, SERVICE_TURN_ON) async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
# Don't support the feature won't call the service
hass.states.async_set(ENTITY_1, "something", {ATTR_SUPPORTED_FEATURES: 0})
await async_reproduce_states(hass, [State(ENTITY_1, state)]) await async_reproduce_states(hass, [State(ENTITY_1, state)])
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(calls_1) == 0
hass.states.async_set(
ENTITY_1, "something", {ATTR_SUPPORTED_FEATURES: supported_feature}
)
await async_reproduce_states(hass, [State(ENTITY_1, state)])
assert len(calls_1) == 1 assert len(calls_1) == 1
assert calls_1[0].data == {"entity_id": ENTITY_1} assert calls_1[0].data == {"entity_id": ENTITY_1}
async def test_turn_on_with_mode(hass): async def test_turn_on_with_mode(hass):
"""Test that state with additional attributes call multiple services.""" """Test that state with additional attributes call multiple services."""
hass.states.async_set(
ENTITY_1,
"something",
{
ATTR_SUPPORTED_FEATURES: MediaPlayerEntityFeature.TURN_ON
| MediaPlayerEntityFeature.SELECT_SOUND_MODE
},
)
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON) calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
calls_2 = async_mock_service(hass, DOMAIN, SERVICE_SELECT_SOUND_MODE) calls_2 = async_mock_service(hass, DOMAIN, SERVICE_SELECT_SOUND_MODE)
@ -82,6 +100,13 @@ async def test_turn_on_with_mode(hass):
async def test_multiple_same_state(hass): async def test_multiple_same_state(hass):
"""Test that multiple states with same state gets calls.""" """Test that multiple states with same state gets calls."""
for entity in ENTITY_1, ENTITY_2:
hass.states.async_set(
entity,
"something",
{ATTR_SUPPORTED_FEATURES: MediaPlayerEntityFeature.TURN_ON},
)
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON) calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
await async_reproduce_states(hass, [State(ENTITY_1, "on"), State(ENTITY_2, "on")]) await async_reproduce_states(hass, [State(ENTITY_1, "on"), State(ENTITY_2, "on")])
@ -96,6 +121,16 @@ async def test_multiple_same_state(hass):
async def test_multiple_different_state(hass): async def test_multiple_different_state(hass):
"""Test that multiple states with different state gets calls.""" """Test that multiple states with different state gets calls."""
for entity in ENTITY_1, ENTITY_2:
hass.states.async_set(
entity,
"something",
{
ATTR_SUPPORTED_FEATURES: MediaPlayerEntityFeature.TURN_ON
| MediaPlayerEntityFeature.TURN_OFF
},
)
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON) calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
calls_2 = async_mock_service(hass, DOMAIN, SERVICE_TURN_OFF) calls_2 = async_mock_service(hass, DOMAIN, SERVICE_TURN_OFF)
@ -111,6 +146,12 @@ async def test_multiple_different_state(hass):
async def test_state_with_context(hass): async def test_state_with_context(hass):
"""Test that context is forwarded.""" """Test that context is forwarded."""
hass.states.async_set(
ENTITY_1,
"something",
{ATTR_SUPPORTED_FEATURES: MediaPlayerEntityFeature.TURN_ON},
)
calls = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON) calls = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
context = Context() context = Context()
@ -126,6 +167,16 @@ async def test_state_with_context(hass):
async def test_attribute_no_state(hass): async def test_attribute_no_state(hass):
"""Test that no state service call is made with none state.""" """Test that no state service call is made with none state."""
hass.states.async_set(
ENTITY_1,
"something",
{
ATTR_SUPPORTED_FEATURES: MediaPlayerEntityFeature.TURN_ON
| MediaPlayerEntityFeature.TURN_OFF
| MediaPlayerEntityFeature.SELECT_SOUND_MODE
},
)
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON) calls_1 = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON)
calls_2 = async_mock_service(hass, DOMAIN, SERVICE_TURN_OFF) calls_2 = async_mock_service(hass, DOMAIN, SERVICE_TURN_OFF)
calls_3 = async_mock_service(hass, DOMAIN, SERVICE_SELECT_SOUND_MODE) calls_3 = async_mock_service(hass, DOMAIN, SERVICE_SELECT_SOUND_MODE)
@ -145,16 +196,38 @@ async def test_attribute_no_state(hass):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"service,attribute", "service,attribute,supported_feature",
[ [
(SERVICE_VOLUME_SET, ATTR_MEDIA_VOLUME_LEVEL), (
(SERVICE_VOLUME_MUTE, ATTR_MEDIA_VOLUME_MUTED), SERVICE_VOLUME_SET,
(SERVICE_SELECT_SOURCE, ATTR_INPUT_SOURCE), ATTR_MEDIA_VOLUME_LEVEL,
(SERVICE_SELECT_SOUND_MODE, ATTR_SOUND_MODE), MediaPlayerEntityFeature.VOLUME_SET,
),
(
SERVICE_VOLUME_MUTE,
ATTR_MEDIA_VOLUME_MUTED,
MediaPlayerEntityFeature.VOLUME_MUTE,
),
(
SERVICE_SELECT_SOURCE,
ATTR_INPUT_SOURCE,
MediaPlayerEntityFeature.SELECT_SOURCE,
),
(
SERVICE_SELECT_SOUND_MODE,
ATTR_SOUND_MODE,
MediaPlayerEntityFeature.SELECT_SOUND_MODE,
),
], ],
) )
async def test_attribute(hass, service, attribute): async def test_attribute(hass, service, attribute, supported_feature):
"""Test that service call is made for each attribute.""" """Test that service call is made for each attribute."""
hass.states.async_set(
ENTITY_1,
"something",
{ATTR_SUPPORTED_FEATURES: supported_feature},
)
calls_1 = async_mock_service(hass, DOMAIN, service) calls_1 = async_mock_service(hass, DOMAIN, service)
value = "dummy" value = "dummy"
@ -168,7 +241,12 @@ async def test_attribute(hass, service, attribute):
async def test_play_media(hass): async def test_play_media(hass):
"""Test that no state service call is made with none state.""" """Test playing media."""
hass.states.async_set(
ENTITY_1,
"something",
{ATTR_SUPPORTED_FEATURES: MediaPlayerEntityFeature.PLAY_MEDIA},
)
calls_1 = async_mock_service(hass, DOMAIN, SERVICE_PLAY_MEDIA) calls_1 = async_mock_service(hass, DOMAIN, SERVICE_PLAY_MEDIA)
value_1 = "dummy_1" value_1 = "dummy_1"