Compare commits

...

8 Commits

Author SHA1 Message Date
Erik
fb39c86c59 Update to trigger on last_non_buffering_state 2026-03-31 18:06:22 +02:00
Erik
5bc49b4559 Merge remote-tracking branch 'upstream/dev' into add_media_player_triggers 2026-03-31 17:19:00 +02:00
Erik Montnemery
3df1bb3ddb Merge branch 'dev' into add_media_player_triggers 2026-03-31 12:02:07 +02:00
Erik Montnemery
d636f2d6e2 Merge branch 'dev' into add_media_player_triggers 2026-03-23 20:47:26 +01:00
Erik
acbdb3914e Align with upstream changes 2026-01-21 08:38:58 +01:00
Erik Montnemery
f1d3216450 Merge branch 'dev' into add_media_player_triggers 2026-01-20 16:12:06 +01:00
Erik
8c83446732 Merge remote-tracking branch 'upstream/dev' into add_media_player_triggers 2025-12-04 08:26:07 +01:00
Erik
70563da152 Add additional media_player triggers 2025-11-27 08:09:26 +01:00
5 changed files with 191 additions and 50 deletions

View File

@@ -123,8 +123,20 @@
}
},
"triggers": {
"paused_playing": {
"trigger": "mdi:pause"
},
"started_playing": {
"trigger": "mdi:play"
},
"stopped_playing": {
"trigger": "mdi:stop"
},
"turned_off": {
"trigger": "mdi:power"
},
"turned_on": {
"trigger": "mdi:power"
}
}
}

View File

@@ -440,6 +440,26 @@
},
"title": "Media player",
"triggers": {
"paused_playing": {
"description": "Triggers after one or more media players paus playing.",
"fields": {
"behavior": {
"description": "[%key:component::media_player::common::trigger_behavior_description%]",
"name": "[%key:component::media_player::common::trigger_behavior_name%]"
}
},
"name": "Media player paused playing"
},
"started_playing": {
"description": "Triggers after one or more media players start playing.",
"fields": {
"behavior": {
"description": "[%key:component::media_player::common::trigger_behavior_description%]",
"name": "[%key:component::media_player::common::trigger_behavior_name%]"
}
},
"name": "Media player started playing"
},
"stopped_playing": {
"description": "Triggers after one or more media players stop playing media.",
"fields": {
@@ -449,6 +469,26 @@
}
},
"name": "Media player stopped playing"
},
"turned_off": {
"description": "Triggers after one or more media players turn off.",
"fields": {
"behavior": {
"description": "[%key:component::media_player::common::trigger_behavior_description%]",
"name": "[%key:component::media_player::common::trigger_behavior_name%]"
}
},
"name": "Media player turned off"
},
"turned_on": {
"description": "Triggers after one or more media players turn on.",
"fields": {
"behavior": {
"description": "[%key:component::media_player::common::trigger_behavior_description%]",
"name": "[%key:component::media_player::common::trigger_behavior_name%]"
}
},
"name": "Media player turned on"
}
}
}

View File

@@ -1,12 +1,38 @@
"""Provides triggers for media players."""
from homeassistant.core import HomeAssistant
from homeassistant.helpers.automation import DomainSpec
from homeassistant.helpers.trigger import Trigger, make_entity_transition_trigger
from . import MediaPlayerState
from .const import DOMAIN
from .const import ATTR_LAST_NON_BUFFERING_STATE, DOMAIN
_LAST_NON_BUFFERING_SPEC = {
DOMAIN: DomainSpec(value_source=ATTR_LAST_NON_BUFFERING_STATE)
}
TRIGGERS: dict[str, type[Trigger]] = {
"paused_playing": make_entity_transition_trigger(
_LAST_NON_BUFFERING_SPEC,
from_states={
MediaPlayerState.PLAYING,
},
to_states={
MediaPlayerState.PAUSED,
},
),
"started_playing": make_entity_transition_trigger(
_LAST_NON_BUFFERING_SPEC,
from_states={
MediaPlayerState.IDLE,
MediaPlayerState.OFF,
MediaPlayerState.ON,
MediaPlayerState.PAUSED,
},
to_states={
MediaPlayerState.PLAYING,
},
),
"stopped_playing": make_entity_transition_trigger(
DOMAIN,
from_states={
@@ -20,6 +46,32 @@ TRIGGERS: dict[str, type[Trigger]] = {
MediaPlayerState.ON,
},
),
"turned_off": make_entity_transition_trigger(
DOMAIN,
from_states={
MediaPlayerState.BUFFERING,
MediaPlayerState.IDLE,
MediaPlayerState.ON,
MediaPlayerState.PAUSED,
MediaPlayerState.PLAYING,
},
to_states={
MediaPlayerState.OFF,
},
),
"turned_on": make_entity_transition_trigger(
DOMAIN,
from_states={
MediaPlayerState.OFF,
},
to_states={
MediaPlayerState.BUFFERING,
MediaPlayerState.IDLE,
MediaPlayerState.ON,
MediaPlayerState.PAUSED,
MediaPlayerState.PLAYING,
},
),
}

View File

@@ -1,4 +1,4 @@
stopped_playing:
.trigger_common: &trigger_common
target:
entity:
domain: media_player
@@ -13,3 +13,9 @@ stopped_playing:
- first
- last
- any
paused_playing: *trigger_common
started_playing: *trigger_common
stopped_playing: *trigger_common
turned_off: *trigger_common
turned_on: *trigger_common

View File

@@ -5,6 +5,7 @@ from typing import Any
import pytest
from homeassistant.components.media_player import MediaPlayerState
from homeassistant.components.media_player.const import ATTR_LAST_NON_BUFFERING_STATE
from homeassistant.core import HomeAssistant
from tests.components.common import (
@@ -19,12 +20,87 @@ from tests.components.common import (
)
def _mp_state(state: str, last_non_buffering: str) -> tuple[str, dict[str, str]]:
"""Create a media player state tuple with last_non_buffering_state attribute."""
return (state, {ATTR_LAST_NON_BUFFERING_STATE: last_non_buffering})
@pytest.fixture
async def target_media_players(hass: HomeAssistant) -> dict[str, list[str]]:
"""Create multiple media player entities associated with different targets."""
return await target_entities(hass, "media_player")
STATE_TRIGGER_PARAMETRIZATIONS = [
*parametrize_trigger_states(
trigger="media_player.paused_playing",
target_states=[
_mp_state(MediaPlayerState.PAUSED, MediaPlayerState.PAUSED),
],
other_states=[
_mp_state(MediaPlayerState.PLAYING, MediaPlayerState.PLAYING),
# Buffering with last_non_buffering_state=playing is still
# "playing" from the trigger's perspective
_mp_state(MediaPlayerState.BUFFERING, MediaPlayerState.PLAYING),
],
),
*parametrize_trigger_states(
trigger="media_player.started_playing",
target_states=[
_mp_state(MediaPlayerState.PLAYING, MediaPlayerState.PLAYING),
],
other_states=[
_mp_state(MediaPlayerState.IDLE, MediaPlayerState.IDLE),
_mp_state(MediaPlayerState.OFF, MediaPlayerState.OFF),
_mp_state(MediaPlayerState.ON, MediaPlayerState.ON),
_mp_state(MediaPlayerState.PAUSED, MediaPlayerState.PAUSED),
# Buffering with last_non_buffering_state=paused is still
# "paused" from the trigger's perspective
_mp_state(MediaPlayerState.BUFFERING, MediaPlayerState.PAUSED),
],
),
*parametrize_trigger_states(
trigger="media_player.stopped_playing",
target_states=[
MediaPlayerState.IDLE,
MediaPlayerState.OFF,
MediaPlayerState.ON,
],
other_states=[
MediaPlayerState.BUFFERING,
MediaPlayerState.PAUSED,
MediaPlayerState.PLAYING,
],
),
*parametrize_trigger_states(
trigger="media_player.turned_off",
target_states=[
MediaPlayerState.OFF,
],
other_states=[
MediaPlayerState.BUFFERING,
MediaPlayerState.IDLE,
MediaPlayerState.ON,
MediaPlayerState.PAUSED,
MediaPlayerState.PLAYING,
],
),
*parametrize_trigger_states(
trigger="media_player.turned_on",
target_states=[
MediaPlayerState.BUFFERING,
MediaPlayerState.IDLE,
MediaPlayerState.ON,
MediaPlayerState.PAUSED,
MediaPlayerState.PLAYING,
],
other_states=[
MediaPlayerState.OFF,
],
),
]
@pytest.mark.parametrize(
"trigger_key",
[
@@ -44,22 +120,7 @@ async def test_media_player_triggers_gated_by_labs_flag(
parametrize_target_entities("media_player"),
)
@pytest.mark.parametrize(
("trigger", "trigger_options", "states"),
[
*parametrize_trigger_states(
trigger="media_player.stopped_playing",
target_states=[
MediaPlayerState.IDLE,
MediaPlayerState.OFF,
MediaPlayerState.ON,
],
other_states=[
MediaPlayerState.BUFFERING,
MediaPlayerState.PAUSED,
MediaPlayerState.PLAYING,
],
),
],
("trigger", "trigger_options", "states"), STATE_TRIGGER_PARAMETRIZATIONS
)
async def test_media_player_state_trigger_behavior_any(
hass: HomeAssistant,
@@ -90,22 +151,7 @@ async def test_media_player_state_trigger_behavior_any(
parametrize_target_entities("media_player"),
)
@pytest.mark.parametrize(
("trigger", "trigger_options", "states"),
[
*parametrize_trigger_states(
trigger="media_player.stopped_playing",
target_states=[
MediaPlayerState.IDLE,
MediaPlayerState.OFF,
MediaPlayerState.ON,
],
other_states=[
MediaPlayerState.BUFFERING,
MediaPlayerState.PAUSED,
MediaPlayerState.PLAYING,
],
),
],
("trigger", "trigger_options", "states"), STATE_TRIGGER_PARAMETRIZATIONS
)
async def test_media_player_state_trigger_behavior_first(
hass: HomeAssistant,
@@ -136,22 +182,7 @@ async def test_media_player_state_trigger_behavior_first(
parametrize_target_entities("media_player"),
)
@pytest.mark.parametrize(
("trigger", "trigger_options", "states"),
[
*parametrize_trigger_states(
trigger="media_player.stopped_playing",
target_states=[
MediaPlayerState.IDLE,
MediaPlayerState.OFF,
MediaPlayerState.ON,
],
other_states=[
MediaPlayerState.BUFFERING,
MediaPlayerState.PAUSED,
MediaPlayerState.PLAYING,
],
),
],
("trigger", "trigger_options", "states"), STATE_TRIGGER_PARAMETRIZATIONS
)
async def test_media_player_state_trigger_behavior_last(
hass: HomeAssistant,