[media_player] Use register_apply_action for the media player actions (#19514)

This commit is contained in:
J. Nick Koston
2026-09-23 23:22:20 +01:00
committed by GitHub
parent 62b501049f
commit aaed14d018
4 changed files with 58 additions and 139 deletions
+36 -60
View File
@@ -152,7 +152,7 @@ CONF_ON_PAUSE = "on_pause"
CONF_ON_ANNOUNCEMENT = "on_announcement"
CONF_MEDIA_URL = "media_url"
# Command actions that all share the same schema and codegen handler
# Command actions that all share the same schema and only differ in the command sent
_COMMAND_ACTIONS = [
"play",
"pause",
@@ -201,16 +201,7 @@ _STATE_CONDITIONS = [
"muted",
]
# Special action classes with custom schemas/handlers
PlayMediaAction = media_player_ns.class_(
"PlayMediaAction", automation.Action, cg.Parented.template(MediaPlayer)
)
EnqueueMediaAction = media_player_ns.class_(
"EnqueueMediaAction", automation.Action, cg.Parented.template(MediaPlayer)
)
VolumeSetAction = media_player_ns.class_(
"VolumeSetAction", automation.Action, cg.Parented.template(MediaPlayer)
)
MediaPlayerCommand = media_player_ns.enum("MediaPlayerCommand", is_class=True)
_CALLBACK_AUTOMATIONS = (
@@ -329,59 +320,50 @@ _MEDIA_URL_ACTION_SCHEMA = cv.maybe_simple_value(
)
async def _media_action_handler(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
media_url = await cg.templatable(config[CONF_MEDIA_URL], args, cg.std_string)
announcement = await cg.templatable(config[CONF_ANNOUNCEMENT], args, cg.bool_)
cg.add(var.set_media_url(media_url))
cg.add(var.set_announcement(announcement))
return var
_ANNOUNCEMENT_FIELD = automation.ApplyField(
CONF_ANNOUNCEMENT, "set_announcement", cg.bool_
)
_MEDIA_URL_FIELD = automation.ApplyField(CONF_MEDIA_URL, "set_media_url", cg.std_string)
automation.register_action(
def _set_command(command_name: str) -> automation.ApplyCall:
command = getattr(
MediaPlayerCommand, f"MEDIA_PLAYER_COMMAND_{command_name.upper()}"
)
return automation.ApplyCall(f"set_command({command})")
automation.register_apply_action(
"media_player.play_media",
PlayMediaAction,
_MEDIA_URL_ACTION_SCHEMA,
synchronous=True,
)(_media_action_handler)
_MEDIA_URL_FIELD,
_ANNOUNCEMENT_FIELD,
call="make_call",
)
automation.register_action(
automation.register_apply_action(
"media_player.enqueue",
EnqueueMediaAction,
_MEDIA_URL_ACTION_SCHEMA,
synchronous=True,
)(_media_action_handler)
_set_command("enqueue"),
_MEDIA_URL_FIELD,
_ANNOUNCEMENT_FIELD,
call="make_call",
)
for _action_name in _COMMAND_ACTIONS:
automation.register_apply_action(
f"media_player.{_action_name}",
MEDIA_PLAYER_ACTION_SCHEMA,
_set_command(_action_name),
_ANNOUNCEMENT_FIELD,
call="make_call",
)
def _snake_to_camel(name):
return "".join(word.capitalize() for word in name.split("_"))
def _register_command_actions():
async def handler(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
announcement = await cg.templatable(config[CONF_ANNOUNCEMENT], args, cg.bool_)
cg.add(var.set_announcement(announcement))
return var
for action_name in _COMMAND_ACTIONS:
class_name = f"{_snake_to_camel(action_name)}Action"
action_class = media_player_ns.class_(
class_name, automation.Action, cg.Parented.template(MediaPlayer)
)
automation.register_action(
f"media_player.{action_name}",
action_class,
MEDIA_PLAYER_ACTION_SCHEMA,
synchronous=True,
)(handler)
_register_command_actions()
def _register_state_conditions():
async def handler(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
@@ -401,9 +383,8 @@ def _register_state_conditions():
_register_state_conditions()
@automation.register_action(
automation.register_apply_action(
"media_player.volume_set",
VolumeSetAction,
cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(MediaPlayer),
@@ -411,14 +392,9 @@ _register_state_conditions()
},
key=CONF_VOLUME,
),
synchronous=True,
automation.ApplyField(CONF_VOLUME, "set_volume", cg.float_),
call="make_call",
)
async def media_player_volume_set_action(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
volume = await cg.templatable(config[CONF_VOLUME], args, cg.float_)
cg.add(var.set_volume(volume))
return var
@coroutine_with_priority(CoroPriority.CORE)
@@ -5,76 +5,6 @@
namespace esphome::media_player {
template<MediaPlayerCommand Command, typename... Ts>
class MediaPlayerCommandAction final : public Action<Ts...>, public Parented<MediaPlayer> {
public:
TEMPLATABLE_VALUE(bool, announcement);
void play(const Ts &...x) override {
this->parent_->make_call().set_command(Command).set_announcement(this->announcement_.value(x...)).perform();
}
};
template<typename... Ts>
using PlayAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_PLAY, Ts...>;
template<typename... Ts>
using PauseAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_PAUSE, Ts...>;
template<typename... Ts>
using StopAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_STOP, Ts...>;
template<typename... Ts>
using ToggleAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_TOGGLE, Ts...>;
template<typename... Ts>
using VolumeUpAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_VOLUME_UP, Ts...>;
template<typename... Ts>
using VolumeDownAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_VOLUME_DOWN, Ts...>;
template<typename... Ts>
using TurnOnAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_TURN_ON, Ts...>;
template<typename... Ts>
using TurnOffAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_TURN_OFF, Ts...>;
template<typename... Ts>
using NextAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_NEXT, Ts...>;
template<typename... Ts>
using PreviousAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_PREVIOUS, Ts...>;
template<typename... Ts>
using MuteAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_MUTE, Ts...>;
template<typename... Ts>
using UnmuteAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_UNMUTE, Ts...>;
template<typename... Ts>
using RepeatOffAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_REPEAT_OFF, Ts...>;
template<typename... Ts>
using RepeatOneAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_REPEAT_ONE, Ts...>;
template<typename... Ts>
using RepeatAllAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_REPEAT_ALL, Ts...>;
template<typename... Ts>
using ShuffleAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_SHUFFLE, Ts...>;
template<typename... Ts>
using UnshuffleAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_UNSHUFFLE, Ts...>;
template<typename... Ts>
using GroupJoinAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_GROUP_JOIN, Ts...>;
template<typename... Ts>
using ClearPlaylistAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST, Ts...>;
template<MediaPlayerCommand Command, typename... Ts>
class MediaPlayerMediaAction final : public Action<Ts...>, public Parented<MediaPlayer> {
TEMPLATABLE_VALUE(std::string, media_url)
TEMPLATABLE_VALUE(bool, announcement)
void play(const Ts &...x) override {
auto call = this->parent_->make_call();
if constexpr (Command != MediaPlayerCommand::MEDIA_PLAYER_COMMAND_PLAY)
call.set_command(Command);
call.set_media_url(this->media_url_.value(x...)).set_announcement(this->announcement_.value(x...)).perform();
}
};
template<typename... Ts>
using PlayMediaAction = MediaPlayerMediaAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_PLAY, Ts...>;
template<typename... Ts>
using EnqueueMediaAction = MediaPlayerMediaAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_ENQUEUE, Ts...>;
template<typename... Ts> class VolumeSetAction final : public Action<Ts...>, public Parented<MediaPlayer> {
TEMPLATABLE_VALUE(float, volume)
void play(const Ts &...x) override { this->parent_->make_call().set_volume(this->volume_.value(x...)).perform(); }
};
/// Callback forwarder that triggers an Automation<> on any state change.
/// Pointer-sized (single Automation* field) to fit inline in Callback::ctx_.
struct StateAnyForwarder {
+13 -9
View File
@@ -1,28 +1,30 @@
i2s_audio:
i2s_lrclk_pin: 13
i2s_bclk_pin: 14
i2s_mclk_pin: 15
speaker:
- platform: i2s_audio
id: test_speaker
i2s_dout_pin: 18
i2s_audio_id: i2s_audio_bus
i2s_dout_pin: ${i2s_dout_pin}
dac_type: external
media_player:
- platform: speaker
id: test_media_player
name: None
speaker: test_speaker
announcement_pipeline:
speaker: test_speaker
format: NONE
on_state:
- media_player.play:
- media_player.play_media: http://localhost/media.mp3
- media_player.play_media: !lambda 'return "http://localhost/media.mp3";'
- media_player.play_media: !lambda |-
return id(test_media_player).is_muted() ? "http://localhost/muted.mp3" : "http://localhost/media.mp3";
on_idle:
- media_player.pause:
on_play:
- media_player.stop:
- media_player.stop:
announcement: true
- media_player.stop:
announcement: !lambda 'return id(test_media_player).is_muted();'
on_announcement:
- media_player.play:
on_turn_on:
@@ -61,8 +63,10 @@ media_player:
- media_player.volume_up:
- media_player.volume_down:
- media_player.volume_set: 50%
- media_player.volume_set: !lambda 'return id(test_media_player).volume / 2;'
- media_player.enqueue: http://localhost/media.mp3
- media_player.enqueue: !lambda 'return "http://localhost/media.mp3";'
- media_player.enqueue: !lambda |-
return id(test_media_player).is_muted() ? "http://localhost/muted.mp3" : "http://localhost/media.mp3";
- media_player.enqueue:
media_url: http://localhost/media.mp3
announcement: true
@@ -0,0 +1,9 @@
substitutions:
i2s_dout_pin: GPIO18
packages:
i2s_audio: !include ../../test_build_components/common/i2s_audio/esp32-idf.yaml
media_player: !include common.yaml
wifi:
ap: