[media_player] Migrate triggers to callback automation (#15200)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
J. Nick Koston
2026-03-27 08:20:46 -10:00
committed by GitHub
co-authored by Claude Opus 4.6
parent dea8fdd906
commit 2e42547d32
5 changed files with 44 additions and 47 deletions
+22 -20
View File
@@ -9,7 +9,6 @@ from esphome.const import (
CONF_ON_STATE,
CONF_ON_TURN_OFF,
CONF_ON_TURN_ON,
CONF_TRIGGER_ID,
CONF_VOLUME,
)
from esphome.core import CORE
@@ -65,15 +64,19 @@ _COMMAND_ACTIONS = [
"clear_playlist",
]
# State triggers: (config_key, C++ class name)
StateAnyForwarder = media_player_ns.class_("StateAnyForwarder")
StateEnterForwarder = media_player_ns.class_("StateEnterForwarder")
MediaPlayerState = media_player_ns.enum("MediaPlayerState")
# State triggers: (config_key, state enum or None for any-state)
_STATE_TRIGGERS = [
(CONF_ON_STATE, "StateTrigger"),
(CONF_ON_IDLE, "IdleTrigger"),
(CONF_ON_PLAY, "PlayTrigger"),
(CONF_ON_PAUSE, "PauseTrigger"),
(CONF_ON_ANNOUNCEMENT, "AnnouncementTrigger"),
(CONF_ON_TURN_ON, "OnTrigger"),
(CONF_ON_TURN_OFF, "OffTrigger"),
(CONF_ON_STATE, None),
(CONF_ON_IDLE, MediaPlayerState.MEDIA_PLAYER_STATE_IDLE),
(CONF_ON_PLAY, MediaPlayerState.MEDIA_PLAYER_STATE_PLAYING),
(CONF_ON_PAUSE, MediaPlayerState.MEDIA_PLAYER_STATE_PAUSED),
(CONF_ON_ANNOUNCEMENT, MediaPlayerState.MEDIA_PLAYER_STATE_ANNOUNCING),
(CONF_ON_TURN_ON, MediaPlayerState.MEDIA_PLAYER_STATE_ON),
(CONF_ON_TURN_OFF, MediaPlayerState.MEDIA_PLAYER_STATE_OFF),
]
# State conditions that all share the same schema and codegen handler
@@ -98,10 +101,15 @@ VolumeSetAction = media_player_ns.class_(
@setup_entity("media_player")
async def setup_media_player_core_(var, config):
for conf_key, _ in _STATE_TRIGGERS:
for conf_key, state_enum in _STATE_TRIGGERS:
for conf in config.get(conf_key, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
if state_enum is None:
forwarder = StateAnyForwarder
else:
forwarder = StateEnterForwarder.template(state_enum)
await automation.build_callback_automation(
var, "add_on_state_callback", [], conf, forwarder=forwarder
)
async def register_media_player(var, config):
@@ -120,14 +128,8 @@ async def new_media_player(config, *args):
_MEDIA_PLAYER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
{
cv.Optional(conf_key): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
media_player_ns.class_(class_name, automation.Trigger.template())
),
}
)
for conf_key, class_name in _STATE_TRIGGERS
cv.Optional(conf_key): automation.validate_automation({})
for conf_key, _ in _STATE_TRIGGERS
}
)
+18 -23
View File
@@ -71,32 +71,27 @@ template<typename... Ts> class VolumeSetAction : public Action<Ts...>, public Pa
void play(const Ts &...x) override { this->parent_->make_call().set_volume(this->volume_.value(x...)).perform(); }
};
class StateTrigger : public Trigger<> {
public:
explicit StateTrigger(MediaPlayer *player) {
player->add_on_state_callback([this]() { this->trigger(); });
/// Callback forwarder that triggers an Automation<> on any state change.
/// Pointer-sized (single Automation* field) to fit inline in Callback::ctx_.
struct StateAnyForwarder {
Automation<> *automation;
void operator()(MediaPlayerState /*state*/) const { this->automation->trigger(); }
};
/// Callback forwarder that triggers an Automation<> only when a specific media player state is entered.
/// Pointer-sized (single Automation* field) to fit inline in Callback::ctx_.
template<MediaPlayerState State> struct StateEnterForwarder {
Automation<> *automation;
void operator()(MediaPlayerState state) const {
if (state == State)
this->automation->trigger();
}
};
template<MediaPlayerState State> class MediaPlayerStateTrigger : public Trigger<> {
public:
explicit MediaPlayerStateTrigger(MediaPlayer *player) : player_(player) {
player->add_on_state_callback([this]() {
if (this->player_->state == State)
this->trigger();
});
}
protected:
MediaPlayer *player_;
};
using IdleTrigger = MediaPlayerStateTrigger<MediaPlayerState::MEDIA_PLAYER_STATE_IDLE>;
using PlayTrigger = MediaPlayerStateTrigger<MediaPlayerState::MEDIA_PLAYER_STATE_PLAYING>;
using PauseTrigger = MediaPlayerStateTrigger<MediaPlayerState::MEDIA_PLAYER_STATE_PAUSED>;
using AnnouncementTrigger = MediaPlayerStateTrigger<MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING>;
using OnTrigger = MediaPlayerStateTrigger<MediaPlayerState::MEDIA_PLAYER_STATE_ON>;
using OffTrigger = MediaPlayerStateTrigger<MediaPlayerState::MEDIA_PLAYER_STATE_OFF>;
static_assert(sizeof(StateAnyForwarder) <= sizeof(void *));
static_assert(std::is_trivially_copyable_v<StateAnyForwarder>);
static_assert(sizeof(StateEnterForwarder<MediaPlayerState::MEDIA_PLAYER_STATE_IDLE>) <= sizeof(void *));
static_assert(std::is_trivially_copyable_v<StateEnterForwarder<MediaPlayerState::MEDIA_PLAYER_STATE_IDLE>>);
template<typename... Ts> class IsIdleCondition : public Condition<Ts...>, public Parented<MediaPlayer> {
public:
@@ -199,7 +199,7 @@ MediaPlayerCall &MediaPlayerCall::set_announcement(bool announce) {
}
void MediaPlayer::publish_state() {
this->state_callback_.call();
this->state_callback_.call(this->state);
#if defined(USE_MEDIA_PLAYER) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_media_player_update(this);
#endif
@@ -168,7 +168,7 @@ class MediaPlayer : public EntityBase {
virtual void control(const MediaPlayerCall &call) = 0;
LazyCallbackManager<void()> state_callback_{};
LazyCallbackManager<void(MediaPlayerState)> state_callback_{};
};
} // namespace media_player
@@ -39,8 +39,8 @@ void VoiceAssistant::setup() {
#ifdef USE_MEDIA_PLAYER
if (this->media_player_ != nullptr) {
this->media_player_->add_on_state_callback([this]() {
switch (this->media_player_->state) {
this->media_player_->add_on_state_callback([this](media_player::MediaPlayerState state) {
switch (state) {
case media_player::MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING:
if (this->media_player_response_state_ == MediaPlayerResponseState::URL_SENT) {
// State changed to announcing after receiving the url