Add media_player.paused trigger

This commit is contained in:
Claude
2025-11-11 22:45:33 +00:00
parent f7727e8192
commit b46640a1c2
4 changed files with 129 additions and 0 deletions
@@ -389,6 +389,11 @@
}
},
"name": "When a media player starts playing"
},
"paused": {
"description": "Triggers when a media player pauses.",
"description_configured": "Triggers when a media player pauses",
"name": "When a media player pauses"
}
}
}
@@ -9,6 +9,7 @@ from homeassistant.const import (
CONF_OPTIONS,
CONF_TARGET,
STATE_OFF,
STATE_PAUSED,
STATE_PLAYING,
STATE_UNAVAILABLE,
)
@@ -48,6 +49,13 @@ PLAYING_TRIGGER_SCHEMA = vol.Schema(
}
)
PAUSED_TRIGGER_SCHEMA = vol.Schema(
{
vol.Optional(CONF_OPTIONS, default={}): {},
vol.Required(CONF_TARGET): cv.TARGET_FIELDS,
}
)
class MediaPlayerTurnsOnTrigger(Trigger):
"""Trigger for when a media player turns on."""
@@ -259,10 +267,78 @@ class MediaPlayerPlayingTrigger(Trigger):
)
class MediaPlayerPausedTrigger(Trigger):
"""Trigger for when a media player pauses."""
@override
@classmethod
async def async_validate_config(
cls, hass: HomeAssistant, config: ConfigType
) -> ConfigType:
"""Validate config."""
return cast(ConfigType, PAUSED_TRIGGER_SCHEMA(config))
def __init__(self, hass: HomeAssistant, config: TriggerConfig) -> None:
"""Initialize the media player paused trigger."""
super().__init__(hass, config)
if TYPE_CHECKING:
assert config.target is not None
self._target = config.target
@override
async def async_attach_runner(
self, run_action: TriggerActionRunner
) -> CALLBACK_TYPE:
"""Attach the trigger to an action runner."""
@callback
def state_change_listener(
target_state_change_data: TargetStateChangedData,
) -> None:
"""Listen for state changes and call action."""
event = target_state_change_data.state_change_event
entity_id = event.data["entity_id"]
from_state = event.data["old_state"]
to_state = event.data["new_state"]
# Ignore unavailable states
if to_state is None or to_state.state == STATE_UNAVAILABLE:
return
# Trigger when pausing
if (
from_state is not None
and from_state.state != STATE_PAUSED
and to_state.state == STATE_PAUSED
):
run_action(
{
ATTR_ENTITY_ID: entity_id,
"from_state": from_state,
"to_state": to_state,
},
f"media player {entity_id} paused",
event.context,
)
def entity_filter(entities: set[str]) -> set[str]:
"""Filter entities of this domain."""
return {
entity_id
for entity_id in entities
if split_entity_id(entity_id)[0] == DOMAIN
}
return async_track_target_selector_state_change_event(
self._hass, self._target, state_change_listener, entity_filter
)
TRIGGERS: dict[str, type[Trigger]] = {
"turns_on": MediaPlayerTurnsOnTrigger,
"turns_off": MediaPlayerTurnsOffTrigger,
"playing": MediaPlayerPlayingTrigger,
"paused": MediaPlayerPausedTrigger,
}
@@ -21,3 +21,8 @@ playing:
multiple: true
custom_value: true
options: []
paused:
target:
entity:
domain: media_player
@@ -208,3 +208,46 @@ async def test_playing_trigger_with_media_content_type_filter(
)
await hass.async_block_till_done()
assert len(service_calls) == 0
async def test_paused_trigger(
hass: HomeAssistant, service_calls: list[ServiceCall]
) -> None:
"""Test that the paused trigger fires when a media player pauses."""
entity_id = "media_player.test"
await async_setup_component(hass, "media_player", {})
# Set initial state to playing
hass.states.async_set(entity_id, STATE_PLAYING)
await hass.async_block_till_done()
await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: {
"triggers": {
"trigger": "media_player.paused",
"target": {CONF_ENTITY_ID: entity_id},
},
"actions": {
"action": "test.automation",
"data": {
CONF_ENTITY_ID: "{{ trigger.entity_id }}",
},
},
}
},
)
# Pause - should trigger
hass.states.async_set(entity_id, STATE_PAUSED)
await hass.async_block_till_done()
assert len(service_calls) == 1
assert service_calls[0].data[CONF_ENTITY_ID] == entity_id
service_calls.clear()
# Already paused - should not trigger
hass.states.async_set(entity_id, STATE_PAUSED)
await hass.async_block_till_done()
assert len(service_calls) == 0