Add ANNOUNCING state to media_player. (#6691)

This commit is contained in:
Mischa Siekmann 2024-05-14 11:40:08 +02:00 committed by Jesse Hills
parent 128fad57b3
commit 28a09cc0d0
No known key found for this signature in database
GPG key ID: BEAAE804EFD8E83A
11 changed files with 76 additions and 7 deletions

View file

@ -1147,6 +1147,9 @@ message MediaPlayerCommandRequest {
bool has_media_url = 6; bool has_media_url = 6;
string media_url = 7; string media_url = 7;
bool has_announcement = 8;
bool announcement = 9;
} }
// ==================== BLUETOOTH ==================== // ==================== BLUETOOTH ====================

View file

@ -1002,7 +1002,11 @@ bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_pla
MediaPlayerStateResponse resp{}; MediaPlayerStateResponse resp{};
resp.key = media_player->get_object_id_hash(); resp.key = media_player->get_object_id_hash();
resp.state = static_cast<enums::MediaPlayerState>(media_player->state);
media_player::MediaPlayerState report_state = media_player->state == media_player::MEDIA_PLAYER_STATE_ANNOUNCING
? media_player::MEDIA_PLAYER_STATE_PLAYING
: media_player->state;
resp.state = static_cast<enums::MediaPlayerState>(report_state);
resp.volume = media_player->volume; resp.volume = media_player->volume;
resp.muted = media_player->is_muted(); resp.muted = media_player->is_muted();
return this->send_media_player_state_response(resp); return this->send_media_player_state_response(resp);
@ -1038,6 +1042,9 @@ void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) {
if (msg.has_media_url) { if (msg.has_media_url) {
call.set_media_url(msg.media_url); call.set_media_url(msg.media_url);
} }
if (msg.has_announcement) {
call.set_announcement(msg.announcement);
}
call.perform(); call.perform();
} }
#endif #endif

View file

@ -5253,6 +5253,14 @@ bool MediaPlayerCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt val
this->has_media_url = value.as_bool(); this->has_media_url = value.as_bool();
return true; return true;
} }
case 8: {
this->has_announcement = value.as_bool();
return true;
}
case 9: {
this->announcement = value.as_bool();
return true;
}
default: default:
return false; return false;
} }
@ -5289,6 +5297,8 @@ void MediaPlayerCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(5, this->volume); buffer.encode_float(5, this->volume);
buffer.encode_bool(6, this->has_media_url); buffer.encode_bool(6, this->has_media_url);
buffer.encode_string(7, this->media_url); buffer.encode_string(7, this->media_url);
buffer.encode_bool(8, this->has_announcement);
buffer.encode_bool(9, this->announcement);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void MediaPlayerCommandRequest::dump_to(std::string &out) const { void MediaPlayerCommandRequest::dump_to(std::string &out) const {
@ -5323,6 +5333,14 @@ void MediaPlayerCommandRequest::dump_to(std::string &out) const {
out.append(" media_url: "); out.append(" media_url: ");
out.append("'").append(this->media_url).append("'"); out.append("'").append(this->media_url).append("'");
out.append("\n"); out.append("\n");
out.append(" has_announcement: ");
out.append(YESNO(this->has_announcement));
out.append("\n");
out.append(" announcement: ");
out.append(YESNO(this->announcement));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif

View file

@ -1298,6 +1298,8 @@ class MediaPlayerCommandRequest : public ProtoMessage {
float volume{0.0f}; float volume{0.0f};
bool has_media_url{false}; bool has_media_url{false};
std::string media_url{}; std::string media_url{};
bool has_announcement{false};
bool announcement{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;

View file

@ -10,6 +10,11 @@ namespace i2s_audio {
static const char *const TAG = "audio"; static const char *const TAG = "audio";
void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) { void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
media_player::MediaPlayerState play_state = media_player::MEDIA_PLAYER_STATE_PLAYING;
if (call.get_announcement().has_value()) {
play_state = call.get_announcement().value() ? media_player::MEDIA_PLAYER_STATE_ANNOUNCING
: media_player::MEDIA_PLAYER_STATE_PLAYING;
}
if (call.get_media_url().has_value()) { if (call.get_media_url().has_value()) {
this->current_url_ = call.get_media_url(); this->current_url_ = call.get_media_url();
if (this->i2s_state_ != I2S_STATE_STOPPED && this->audio_ != nullptr) { if (this->i2s_state_ != I2S_STATE_STOPPED && this->audio_ != nullptr) {
@ -17,7 +22,7 @@ void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
this->audio_->stopSong(); this->audio_->stopSong();
} }
this->audio_->connecttohost(this->current_url_.value().c_str()); this->audio_->connecttohost(this->current_url_.value().c_str());
this->state = media_player::MEDIA_PLAYER_STATE_PLAYING; this->state = play_state;
} else { } else {
this->start(); this->start();
} }
@ -35,7 +40,7 @@ void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
case media_player::MEDIA_PLAYER_COMMAND_PLAY: case media_player::MEDIA_PLAYER_COMMAND_PLAY:
if (!this->audio_->isRunning()) if (!this->audio_->isRunning())
this->audio_->pauseResume(); this->audio_->pauseResume();
this->state = media_player::MEDIA_PLAYER_STATE_PLAYING; this->state = play_state;
break; break;
case media_player::MEDIA_PLAYER_COMMAND_PAUSE: case media_player::MEDIA_PLAYER_COMMAND_PAUSE:
if (this->audio_->isRunning()) if (this->audio_->isRunning())
@ -126,7 +131,9 @@ void I2SAudioMediaPlayer::loop() {
void I2SAudioMediaPlayer::play_() { void I2SAudioMediaPlayer::play_() {
this->audio_->loop(); this->audio_->loop();
if (this->state == media_player::MEDIA_PLAYER_STATE_PLAYING && !this->audio_->isRunning()) { if ((this->state == media_player::MEDIA_PLAYER_STATE_PLAYING ||
this->state == media_player::MEDIA_PLAYER_STATE_ANNOUNCING) &&
!this->audio_->isRunning()) {
this->stop(); this->stop();
} }
} }
@ -164,6 +171,10 @@ void I2SAudioMediaPlayer::start_() {
if (this->current_url_.has_value()) { if (this->current_url_.has_value()) {
this->audio_->connecttohost(this->current_url_.value().c_str()); this->audio_->connecttohost(this->current_url_.value().c_str());
this->state = media_player::MEDIA_PLAYER_STATE_PLAYING; this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
if (this->is_announcement_.has_value()) {
this->state = this->is_announcement_.value() ? media_player::MEDIA_PLAYER_STATE_ANNOUNCING
: media_player::MEDIA_PLAYER_STATE_PLAYING;
}
this->publish_state(); this->publish_state();
} }
} }

View file

@ -78,6 +78,7 @@ class I2SAudioMediaPlayer : public Component, public media_player::MediaPlayer,
HighFrequencyLoopRequester high_freq_; HighFrequencyLoopRequester high_freq_;
optional<std::string> current_url_{}; optional<std::string> current_url_{};
optional<bool> is_announcement_{};
}; };
} // namespace i2s_audio } // namespace i2s_audio

View file

@ -51,12 +51,16 @@ VolumeSetAction = media_player_ns.class_(
CONF_ON_PLAY = "on_play" CONF_ON_PLAY = "on_play"
CONF_ON_PAUSE = "on_pause" CONF_ON_PAUSE = "on_pause"
CONF_ON_ANNOUNCEMENT = "on_announcement"
CONF_MEDIA_URL = "media_url" CONF_MEDIA_URL = "media_url"
StateTrigger = media_player_ns.class_("StateTrigger", automation.Trigger.template()) StateTrigger = media_player_ns.class_("StateTrigger", automation.Trigger.template())
IdleTrigger = media_player_ns.class_("IdleTrigger", automation.Trigger.template()) IdleTrigger = media_player_ns.class_("IdleTrigger", automation.Trigger.template())
PlayTrigger = media_player_ns.class_("PlayTrigger", automation.Trigger.template()) PlayTrigger = media_player_ns.class_("PlayTrigger", automation.Trigger.template())
PauseTrigger = media_player_ns.class_("PauseTrigger", automation.Trigger.template()) PauseTrigger = media_player_ns.class_("PauseTrigger", automation.Trigger.template())
AnnoucementTrigger = media_player_ns.class_(
"AnnouncementTrigger", automation.Trigger.template()
)
IsIdleCondition = media_player_ns.class_("IsIdleCondition", automation.Condition) IsIdleCondition = media_player_ns.class_("IsIdleCondition", automation.Condition)
IsPlayingCondition = media_player_ns.class_("IsPlayingCondition", automation.Condition) IsPlayingCondition = media_player_ns.class_("IsPlayingCondition", automation.Condition)
@ -75,6 +79,9 @@ async def setup_media_player_core_(var, config):
for conf in config.get(CONF_ON_PAUSE, []): for conf in config.get(CONF_ON_PAUSE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf) await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_ANNOUNCEMENT, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
async def register_media_player(var, config): async def register_media_player(var, config):
@ -106,6 +113,11 @@ MEDIA_PLAYER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PauseTrigger), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PauseTrigger),
} }
), ),
cv.Optional(CONF_ON_ANNOUNCEMENT): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(AnnoucementTrigger),
}
),
} }
) )

View file

@ -52,6 +52,7 @@ class StateTrigger : public Trigger<> {
MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(IdleTrigger, IDLE) MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(IdleTrigger, IDLE)
MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(PlayTrigger, PLAYING) MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(PlayTrigger, PLAYING)
MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(PauseTrigger, PAUSED) MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(PauseTrigger, PAUSED)
MEDIA_PLAYER_SIMPLE_STATE_TRIGGER(AnnouncementTrigger, ANNOUNCING)
template<typename... Ts> class IsIdleCondition : public Condition<Ts...>, public Parented<MediaPlayer> { template<typename... Ts> class IsIdleCondition : public Condition<Ts...>, public Parented<MediaPlayer> {
public: public:

View file

@ -15,6 +15,8 @@ const char *media_player_state_to_string(MediaPlayerState state) {
return "PLAYING"; return "PLAYING";
case MEDIA_PLAYER_STATE_PAUSED: case MEDIA_PLAYER_STATE_PAUSED:
return "PAUSED"; return "PAUSED";
case MEDIA_PLAYER_STATE_ANNOUNCING:
return "ANNOUNCING";
case MEDIA_PLAYER_STATE_NONE: case MEDIA_PLAYER_STATE_NONE:
default: default:
return "UNKNOWN"; return "UNKNOWN";
@ -68,6 +70,9 @@ void MediaPlayerCall::perform() {
if (this->volume_.has_value()) { if (this->volume_.has_value()) {
ESP_LOGD(TAG, " Volume: %.2f", this->volume_.value()); ESP_LOGD(TAG, " Volume: %.2f", this->volume_.value());
} }
if (this->announcement_.has_value()) {
ESP_LOGD(TAG, " Announcement: %s", this->announcement_.value() ? "yes" : "no");
}
this->parent_->control(*this); this->parent_->control(*this);
} }
@ -108,6 +113,11 @@ MediaPlayerCall &MediaPlayerCall::set_volume(float volume) {
return *this; return *this;
} }
MediaPlayerCall &MediaPlayerCall::set_announcement(bool announce) {
this->announcement_ = announce;
return *this;
}
void MediaPlayer::add_on_state_callback(std::function<void()> &&callback) { void MediaPlayer::add_on_state_callback(std::function<void()> &&callback) {
this->state_callback_.add(std::move(callback)); this->state_callback_.add(std::move(callback));
} }

View file

@ -10,7 +10,8 @@ enum MediaPlayerState : uint8_t {
MEDIA_PLAYER_STATE_NONE = 0, MEDIA_PLAYER_STATE_NONE = 0,
MEDIA_PLAYER_STATE_IDLE = 1, MEDIA_PLAYER_STATE_IDLE = 1,
MEDIA_PLAYER_STATE_PLAYING = 2, MEDIA_PLAYER_STATE_PLAYING = 2,
MEDIA_PLAYER_STATE_PAUSED = 3 MEDIA_PLAYER_STATE_PAUSED = 3,
MEDIA_PLAYER_STATE_ANNOUNCING = 4
}; };
const char *media_player_state_to_string(MediaPlayerState state); const char *media_player_state_to_string(MediaPlayerState state);
@ -51,12 +52,14 @@ class MediaPlayerCall {
MediaPlayerCall &set_media_url(const std::string &url); MediaPlayerCall &set_media_url(const std::string &url);
MediaPlayerCall &set_volume(float volume); MediaPlayerCall &set_volume(float volume);
MediaPlayerCall &set_announcement(bool announce);
void perform(); void perform();
const optional<MediaPlayerCommand> &get_command() const { return command_; } const optional<MediaPlayerCommand> &get_command() const { return command_; }
const optional<std::string> &get_media_url() const { return media_url_; } const optional<std::string> &get_media_url() const { return media_url_; }
const optional<float> &get_volume() const { return volume_; } const optional<float> &get_volume() const { return volume_; }
const optional<bool> &get_announcement() const { return announcement_; }
protected: protected:
void validate_(); void validate_();
@ -64,6 +67,7 @@ class MediaPlayerCall {
optional<MediaPlayerCommand> command_; optional<MediaPlayerCommand> command_;
optional<std::string> media_url_; optional<std::string> media_url_;
optional<float> volume_; optional<float> volume_;
optional<bool> announcement_;
}; };
class MediaPlayer : public EntityBase { class MediaPlayer : public EntityBase {

View file

@ -318,7 +318,7 @@ void VoiceAssistant::loop() {
#endif #endif
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
if (this->media_player_ != nullptr) { if (this->media_player_ != nullptr) {
playing = (this->media_player_->state == media_player::MediaPlayerState::MEDIA_PLAYER_STATE_PLAYING); playing = (this->media_player_->state == media_player::MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING);
} }
#endif #endif
if (playing) { if (playing) {
@ -640,7 +640,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
this->defer([this, url]() { this->defer([this, url]() {
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
if (this->media_player_ != nullptr) { if (this->media_player_ != nullptr) {
this->media_player_->make_call().set_media_url(url).perform(); this->media_player_->make_call().set_media_url(url).set_announcement(true).perform();
} }
#endif #endif
this->tts_end_trigger_->trigger(url); this->tts_end_trigger_->trigger(url);