mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 21:57:51 +00:00
Check supported features in media player reproduce state (#70055)
This commit is contained in:
parent
7474e2f96a
commit
a5134d9ba2
@ -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(
|
||||||
|
@ -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"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user