ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
speaker_media_player.cpp
Go to the documentation of this file.
2
3#ifdef USE_ESP32
4
5#include "esphome/core/log.h"
6
8#ifdef USE_OTA
10#endif
11
12namespace esphome::speaker {
13
14// Framework:
15// - Media player that can handle two streams: one for media and one for announcements
16// - Each stream has an individual speaker component for output
17// - Each stream is handled by an ``AudioPipeline`` object with two parts/tasks
18// - ``AudioReader`` handles reading from an HTTP source or from a PROGMEM flash set at compile time
19// - ``AudioDecoder`` handles decoding the audio file. All formats are limited to two channels and 16 bits per
20// sample.
21// Each format is enabled independently at compile time:
22// - FLAC
23// - MP3 (based on the libhelix decoder)
24// - Ogg Opus
25// - WAV
26// - Each task runs until it is done processing the file or it receives a stop command
27// - Inter-task communication uses a FreeRTOS Event Group
28// - The ``AudioPipeline`` sets up a ring buffer between the reader and decoder tasks. The decoder task outputs audio
29// directly to a speaker component.
30// - The pipelines internal state needs to be processed by regularly calling ``process_state``.
31// - Generic media player commands are received by the ``control`` function. The commands are added to the
32// ``media_control_command_queue_`` to be processed in the component's loop
33// - Local file play back is initiatied with ``play_file`` and adds it to the ``media_control_command_queue_``
34// - Starting a stream intializes the appropriate pipeline or stops it if it is already running
35// - Volume and mute commands are achieved by the ``mute``, ``unmute``, ``set_volume`` functions.
36// - Volume commands are ignored if the media control queue is full to avoid crashing with rapid volume
37// increases/decreases.
38// - These functions all send the appropriate information to the speakers to implement.
39// - Pausing is implemented in the decoder task and is also sent directly to the media speaker component to decrease
40// latency.
41// - The components main loop performs housekeeping:
42// - It reads the media control queue and processes it directly
43// - It determines the overall state of the media player by considering the state of each pipeline
44// - announcement playback takes highest priority
45// - Handles playlists and repeating by starting the appropriate file when a previous file is finished
46// - Logging only happens in the main loop task to reduce task stack memory usage.
47
48static const uint32_t MEDIA_CONTROLS_QUEUE_LENGTH = 20;
49
50static const UBaseType_t MEDIA_PIPELINE_TASK_PRIORITY = 1;
51static const UBaseType_t ANNOUNCEMENT_PIPELINE_TASK_PRIORITY = 1;
52
53ESPHOME_LOG_TAG(TAG, "speaker_media_player");
54
55static constexpr uint32_t UNPAUSE_MEDIA_INTERVAL_ID = 0;
56static constexpr uint32_t NEXT_ANNOUNCEMENT_TIMEOUT_ID = 1;
57static constexpr uint32_t NEXT_MEDIA_TIMEOUT_ID = 2;
58static constexpr uint32_t UNPAUSE_ANNOUNCEMENT_INTERVAL_ID = 3;
59
61#ifdef USE_SPEAKER_MEDIA_PLAYER_ON_OFF
63#else
65#endif
66
67 this->media_control_command_queue_ = xQueueCreate(MEDIA_CONTROLS_QUEUE_LENGTH, sizeof(MediaCallCommand));
68
70
71 VolumeRestoreState volume_restore_state;
72 if (this->pref_.load(&volume_restore_state)) {
73 this->set_volume_(volume_restore_state.volume);
74 this->set_mute_state_(volume_restore_state.is_muted);
75 } else {
76 this->set_volume_(this->volume_initial_);
77 this->set_mute_state_(false);
78 }
79
80#ifdef USE_OTA_STATE_LISTENER
82#endif
83
85 make_unique<AudioPipeline>(this->announcement_speaker_, this->buffer_size_, this->task_stack_in_psram_, "ann",
86 ANNOUNCEMENT_PIPELINE_TASK_PRIORITY);
87
88 if (this->announcement_pipeline_ == nullptr) {
89 ESP_LOGE(TAG, "Failed to create announcement pipeline");
90 this->mark_failed();
91 }
92
93 if (!this->single_pipeline_()) {
94 this->media_pipeline_ = make_unique<AudioPipeline>(this->media_speaker_, this->buffer_size_,
95 this->task_stack_in_psram_, "med", MEDIA_PIPELINE_TASK_PRIORITY);
96
97 if (this->media_pipeline_ == nullptr) {
98 ESP_LOGE(TAG, "Failed to create media pipeline");
99 this->mark_failed();
100 }
101 }
102
103 ESP_LOGI(TAG, "Set up speaker media player");
104}
105
107 switch (pipeline_type) {
109 this->announcement_playlist_delay_ms_ = delay_ms;
110 break;
112 this->media_playlist_delay_ms_ = delay_ms;
113 break;
114 }
115}
116
118 this->media_pipeline_->stop();
119 this->unpause_media_remaining_ = 3;
120 this->set_interval(UNPAUSE_MEDIA_INTERVAL_ID, 50, [this]() {
122 this->cancel_interval(UNPAUSE_MEDIA_INTERVAL_ID);
123 this->media_pipeline_->set_pause_state(false);
124 this->is_paused_ = false;
125 } else if (--this->unpause_media_remaining_ == 0) {
126 this->cancel_interval(UNPAUSE_MEDIA_INTERVAL_ID);
127 }
128 });
129}
130
132 if (!this->is_ready()) {
133 return;
134 }
135
136 MediaCallCommand media_command;
137
138 if (xQueueReceive(this->media_control_command_queue_, &media_command, 0) == pdTRUE) {
139 bool enqueue = media_command.enqueue.has_value() && media_command.enqueue.value();
140
141 if (media_command.url.has_value() || media_command.file.has_value()) {
142#ifdef USE_SPEAKER_MEDIA_PLAYER_ON_OFF
146 }
147#endif
148 PlaylistItem playlist_item;
149 if (media_command.url.has_value()) {
150 playlist_item.url = *media_command.url.value();
151 delete media_command.url.value();
152 }
153 if (media_command.file.has_value()) {
154 playlist_item.file = media_command.file;
155 }
156 playlist_item.is_announcement = media_command.is_announcement;
157
158 if (this->single_pipeline_() || (media_command.announce.has_value() && media_command.announce.value())) {
159 if (!enqueue) {
160 // Enqueued items switch the volume in loop() when they start
161 this->announcement_volume_.item_started(media_command.is_announcement,
162 this->media_pipeline_state_ == AudioPipelineState::PLAYING ||
163 this->announcement_pipeline_state_ != AudioPipelineState::STOPPED,
164 this->volume, this->speaker_volume_applier_());
165 // Ensure the loaded next item doesn't start playing, clear the queue, start the file, and unpause
166 this->cancel_timeout(NEXT_ANNOUNCEMENT_TIMEOUT_ID);
167 this->announcement_playlist_.clear();
168 this->announcement_item_failed_ = false;
169 if (media_command.file.has_value()) {
170 this->announcement_pipeline_->start_file(playlist_item.file.value());
171 } else if (media_command.url.has_value()) {
172 this->announcement_pipeline_->start_url(playlist_item.url.value());
173 }
174 this->announcement_pipeline_->set_pause_state(false);
175 }
176 this->announcement_playlist_.push_back(playlist_item);
177 } else {
178 if (!enqueue) {
179 // Ensure the loaded next item doesn't start playing, clear the queue, start the file, and unpause
180 this->cancel_timeout(NEXT_MEDIA_TIMEOUT_ID);
181 this->media_playlist_.clear();
182 this->media_item_failed_ = false;
183 if (this->is_paused_) {
184 // If paused, stop the media pipeline and unpause it after confirming its stopped. This avoids playing a
185 // short segment of the paused file before starting the new one.
187 } else {
188 // Not paused, just directly start the file
189 if (media_command.file.has_value()) {
190 this->media_pipeline_->start_file(playlist_item.file.value());
191 } else if (media_command.url.has_value()) {
192 this->media_pipeline_->start_url(playlist_item.url.value());
193 }
194 this->media_pipeline_->set_pause_state(false);
195 this->is_paused_ = false;
196 }
197 }
198 this->media_playlist_.push_back(playlist_item);
199 }
200
201 return; // Don't process the new file play command further
202 }
203
204 if (media_command.volume.has_value()) {
205 this->set_volume_(media_command.volume.value());
206 this->publish_state();
207 }
208
209 if (media_command.command.has_value()) {
210 switch (media_command.command.value()) {
212#ifdef USE_SPEAKER_MEDIA_PLAYER_ON_OFF
216 }
217#endif
218 if ((this->media_pipeline_ != nullptr) && (this->is_paused_)) {
219 this->media_pipeline_->set_pause_state(false);
220 }
221 this->is_paused_ = false;
222 break;
224 if ((this->media_pipeline_ != nullptr) && (!this->is_paused_)) {
225 this->media_pipeline_->set_pause_state(true);
226 }
227 this->is_paused_ = true;
228 break;
229#ifdef USE_SPEAKER_MEDIA_PLAYER_ON_OFF
233 this->publish_state();
234 }
235 break;
237 this->is_turn_off_ = true;
238 [[fallthrough]];
239#endif
241 // Pipelines do not stop immediately after calling the stop command, so confirm its stopped before unpausing.
242 // This avoids an audible short segment playing after receiving the stop command in a paused state.
243#ifdef USE_SPEAKER_MEDIA_PLAYER_ON_OFF
244 if (this->single_pipeline_() || (media_command.announce.has_value() && media_command.announce.value()) ||
245 (this->is_turn_off_ && this->announcement_pipeline_state_ != AudioPipelineState::STOPPED)) {
246#else
247 if (this->single_pipeline_() || (media_command.announce.has_value() && media_command.announce.value())) {
248#endif
249 if (this->announcement_pipeline_ != nullptr) {
250 this->cancel_timeout(NEXT_ANNOUNCEMENT_TIMEOUT_ID);
251 this->announcement_playlist_.clear();
252 this->announcement_item_failed_ = false;
253 this->announcement_pipeline_->stop();
255 this->set_interval(UNPAUSE_ANNOUNCEMENT_INTERVAL_ID, 50, [this]() {
257 this->cancel_interval(UNPAUSE_ANNOUNCEMENT_INTERVAL_ID);
258 this->announcement_pipeline_->set_pause_state(false);
259 } else if (--this->unpause_announcement_remaining_ == 0) {
260 this->cancel_interval(UNPAUSE_ANNOUNCEMENT_INTERVAL_ID);
261 }
262 });
263 }
264 } else {
265 if (this->media_pipeline_ != nullptr) {
266 this->cancel_timeout(NEXT_MEDIA_TIMEOUT_ID);
267 this->media_playlist_.clear();
268 this->media_item_failed_ = false;
270 }
271 }
272
273 break;
275 if (this->media_pipeline_ != nullptr) {
276 if (this->is_paused_) {
277 this->media_pipeline_->set_pause_state(false);
278 this->is_paused_ = false;
279 } else {
280 this->media_pipeline_->set_pause_state(true);
281 this->is_paused_ = true;
282 }
283 }
284 break;
286 this->set_mute_state_(true);
287
288 this->publish_state();
289 break;
290 }
292 this->set_mute_state_(false);
293 this->publish_state();
294 break;
296 this->set_volume_(std::min(1.0f, this->volume + this->volume_increment_));
297 this->publish_state();
298 break;
300 this->set_volume_(std::max(0.0f, this->volume - this->volume_increment_));
301 this->publish_state();
302 break;
304 if (this->single_pipeline_() || (media_command.announce.has_value() && media_command.announce.value())) {
305 this->announcement_repeat_one_ = true;
306 } else {
307 this->media_repeat_one_ = true;
308 }
309 break;
311 if (this->single_pipeline_() || (media_command.announce.has_value() && media_command.announce.value())) {
312 this->announcement_repeat_one_ = false;
313 } else {
314 this->media_repeat_one_ = false;
315 }
316 break;
318 if (this->single_pipeline_() || (media_command.announce.has_value() && media_command.announce.value())) {
319 if (this->announcement_playlist_.empty()) {
320 this->announcement_playlist_.resize(1);
321 }
322 } else {
323 if (this->media_playlist_.empty()) {
324 this->media_playlist_.resize(1);
325 }
326 }
327 break;
328 default:
329 break;
330 }
331 }
332 }
333}
334
335#ifdef USE_OTA_STATE_LISTENER
336void SpeakerMediaPlayer::on_ota_global_state(ota::OTAState state, float progress, uint8_t error,
337 ota::OTAComponent *comp) {
338 if (state == ota::OTA_STARTED) {
339 if (this->media_pipeline_ != nullptr) {
340 this->media_pipeline_->suspend_tasks();
341 }
342 if (this->announcement_pipeline_ != nullptr) {
343 this->announcement_pipeline_->suspend_tasks();
344 }
345 } else if (state == ota::OTA_ERROR) {
346 if (this->media_pipeline_ != nullptr) {
347 this->media_pipeline_->resume_tasks();
348 }
349 if (this->announcement_pipeline_ != nullptr) {
350 this->announcement_pipeline_->resume_tasks();
351 }
352 }
353}
354#endif
355
357 this->watch_media_commands_();
358
359 // Determine state of the media player
360 media_player::MediaPlayerState old_state = this->state;
361
362 AudioPipelineState old_media_pipeline_state = this->media_pipeline_state_;
363 if (this->media_pipeline_ != nullptr) {
364 this->media_pipeline_state_ = this->media_pipeline_->process_state();
365 }
366
368 ESP_LOGE(TAG, "The media pipeline's file reader encountered an error.");
369 this->media_item_failed_ = true;
371 ESP_LOGE(TAG, "The media pipeline's audio decoder encountered an error.");
372 this->media_item_failed_ = true;
373 }
374
375 AudioPipelineState old_announcement_pipeline_state = this->announcement_pipeline_state_;
376 if (this->announcement_pipeline_ != nullptr) {
377 this->announcement_pipeline_state_ = this->announcement_pipeline_->process_state();
378 }
379
381 ESP_LOGE(TAG, "The announcement pipeline's file reader encountered an error.");
382 this->announcement_item_failed_ = true;
384 ESP_LOGE(TAG, "The announcement pipeline's audio decoder encountered an error.");
385 this->announcement_item_failed_ = true;
386 }
387
390 } else {
391 if (!this->announcement_playlist_.empty()) {
392 uint32_t timeout_ms = 0;
393 if (this->announcement_item_failed_) {
394 // Drop the item that failed, even with repeat enabled; otherwise it is restarted as soon as the pipeline
395 // stops, which after an error is usually on the next loop
396 this->announcement_item_failed_ = false;
397 this->announcement_playlist_.pop_front();
398 } else if (old_announcement_pipeline_state == AudioPipelineState::PLAYING) {
399 // Finished the current announcement file
400 if (!this->announcement_repeat_one_) {
401 // Pop item off the playlist if repeat is disabled
402 this->announcement_playlist_.pop_front();
403 }
404 // Only delay starting playback if moving on the next playlist item or repeating the current item
405 timeout_ms = this->announcement_playlist_delay_ms_;
406 }
407
408 if (!this->announcement_playlist_.empty()) {
409 // Start the next announcement file
410 PlaylistItem playlist_item = this->announcement_playlist_.front();
411 this->announcement_volume_.item_started(playlist_item.is_announcement,
412 this->media_pipeline_state_ == AudioPipelineState::PLAYING,
413 this->volume, this->speaker_volume_applier_());
414 if (playlist_item.url.has_value()) {
415 this->announcement_pipeline_->start_url(playlist_item.url.value());
416 } else if (playlist_item.file.has_value()) {
417 this->announcement_pipeline_->start_file(playlist_item.file.value());
418 }
419
420 if (timeout_ms > 0) {
421 // Pause pipeline internally to facilitate the delay between items
422 this->announcement_pipeline_->set_pause_state(true);
423 // Internally unpause the pipeline after the delay between playlist items. Announcements do not follow the
424 // media player's pause state.
425 this->set_timeout(NEXT_ANNOUNCEMENT_TIMEOUT_ID, timeout_ms,
426 [this]() { this->announcement_pipeline_->set_pause_state(false); });
427 }
428 }
429 } else {
430 // Nothing left to retry
431 this->announcement_item_failed_ = false;
432 if (this->is_paused_) {
433#ifdef USE_SPEAKER_MEDIA_PLAYER_ON_OFF
434 if (this->state != media_player::MEDIA_PLAYER_STATE_OFF) {
436 }
437#else
439#endif
443 if (!media_playlist_.empty()) {
444 uint32_t timeout_ms = 0;
445 if (this->media_item_failed_) {
446 // Drop the item that failed, even with repeat enabled; otherwise it is restarted as soon as the pipeline
447 // stops. The flag also covers an error that happened while an announcement was playing.
448 this->media_item_failed_ = false;
449 this->media_playlist_.pop_front();
450 } else if (old_media_pipeline_state == AudioPipelineState::PLAYING) {
451 // Finished the current media file
452 if (!this->media_repeat_one_) {
453 // Pop item off the playlist if repeat is disabled
454 this->media_playlist_.pop_front();
455 }
456 // Only delay starting playback if moving on the next playlist item or repeating the current item
457 timeout_ms = this->media_playlist_delay_ms_;
458 }
459 if (!this->media_playlist_.empty()) {
460 PlaylistItem playlist_item = this->media_playlist_.front();
461 if (playlist_item.url.has_value()) {
462 this->media_pipeline_->start_url(playlist_item.url.value());
463 } else if (playlist_item.file.has_value()) {
464 this->media_pipeline_->start_file(playlist_item.file.value());
465 }
466
467 if (timeout_ms > 0) {
468 // Pause pipeline internally to facilitate the delay between items
469 this->media_pipeline_->set_pause_state(true);
470 // Internally unpause the pipeline after the delay between playlist items, if the media player state is
471 // not paused.
472 this->set_timeout(NEXT_MEDIA_TIMEOUT_ID, timeout_ms,
473 [this]() { this->media_pipeline_->set_pause_state(this->is_paused_); });
474 }
475 }
476 } else {
477 // Nothing left to retry
478 this->media_item_failed_ = false;
479#ifdef USE_SPEAKER_MEDIA_PLAYER_ON_OFF
480 if (this->state != media_player::MEDIA_PLAYER_STATE_OFF) {
482 }
483#else
485#endif
486 }
487 }
488 }
489 }
490
495 }
496
497 if (this->state != old_state) {
498 this->publish_state();
499 ESP_LOGD(TAG, "State changed to %s", media_player::media_player_state_to_string(this->state));
500 }
501#ifdef USE_SPEAKER_MEDIA_PLAYER_ON_OFF
502 if (this->is_turn_off_ && (this->state == media_player::MEDIA_PLAYER_STATE_PAUSED ||
504 this->is_turn_off_ = false;
505 if (this->state == media_player::MEDIA_PLAYER_STATE_PAUSED) {
507 this->publish_state();
508 ESP_LOGD(TAG, "State changed to %s", media_player::media_player_state_to_string(this->state));
509 }
511 this->publish_state();
512 ESP_LOGD(TAG, "State changed to %s", media_player::media_player_state_to_string(this->state));
513 }
514#endif
515}
516
517void SpeakerMediaPlayer::play_file(const audio::AudioFile *media_file, bool announcement, bool enqueue) {
518 if (!this->is_ready()) {
519 // Ignore any commands sent before the media player is setup
520 return;
521 }
522
523 MediaCallCommand media_command;
524
525 media_command.file = media_file;
526 media_command.is_announcement = announcement;
527 if (this->single_pipeline_() || announcement) {
528 media_command.announce = true;
529 } else {
530 media_command.announce = false;
531 }
532 media_command.enqueue = enqueue;
533 xQueueSend(this->media_control_command_queue_, &media_command, portMAX_DELAY);
534}
535
536void SpeakerMediaPlayer::control(const media_player::MediaPlayerCall &call) {
537 if (!this->is_ready()) {
538 // Ignore any commands sent before the media player is setup
539 return;
540 }
541
542 MediaCallCommand media_command;
543
544 auto ann = call.get_announcement();
545 media_command.is_announcement = ann.has_value() && *ann;
546 if (this->single_pipeline_() || media_command.is_announcement) {
547 media_command.announce = true;
548 } else {
549 media_command.announce = false;
550 }
551
552 const auto &media_url = call.get_media_url();
553 if (media_url.has_value()) {
554 media_command.url =
555 new std::string(*media_url); // Must be manually deleted after receiving media_command from a queue
556
557 auto cmd = call.get_command();
558 if (cmd.has_value()) {
560 media_command.enqueue = true;
561 }
562 }
563
564 xQueueSend(this->media_control_command_queue_, &media_command, portMAX_DELAY);
565 return;
566 }
567
568 auto vol = call.get_volume();
569 if (vol.has_value()) {
570 media_command.volume = vol;
571 // Wait 0 ticks for queue to be free, volume sets aren't that important!
572 xQueueSend(this->media_control_command_queue_, &media_command, 0);
573 return;
574 }
575
576 auto cmd = call.get_command();
577 if (cmd.has_value()) {
578 media_command.command = cmd;
579 TickType_t ticks_to_wait = portMAX_DELAY;
582 ticks_to_wait = 0; // Wait 0 ticks for queue to be free, volume sets aren't that important!
583 }
584 xQueueSend(this->media_control_command_queue_, &media_command, ticks_to_wait);
585 return;
586 }
587}
588
589media_player::MediaPlayerTraits SpeakerMediaPlayer::get_traits() {
590 auto traits = media_player::MediaPlayerTraits();
591 if (!this->single_pipeline_()) {
592 traits.set_supports_pause(true);
593 }
594#ifdef USE_SPEAKER_MEDIA_PLAYER_ON_OFF
595 traits.set_supports_turn_off_on(true);
596#endif
597
598 if (this->announcement_format_.has_value()) {
599 traits.get_supported_formats().push_back(this->announcement_format_.value());
600 }
601 if (this->media_format_.has_value()) {
602 traits.get_supported_formats().push_back(this->media_format_.value());
603 } else if (this->single_pipeline_() && this->announcement_format_.has_value()) {
604 // Only one pipeline is defined, so use the announcement format (if configured) for the default purpose
605 media_player::MediaPlayerSupportedFormat media_format = this->announcement_format_.value();
607 traits.get_supported_formats().push_back(media_format);
608 }
609
610 return traits;
611};
612
614 VolumeRestoreState volume_restore_state;
615 volume_restore_state.volume = this->volume;
616 volume_restore_state.is_muted = this->is_muted_;
617 this->pref_.save(&volume_restore_state);
618}
619
620void SpeakerMediaPlayer::set_mute_state_(bool mute_state) {
621 if (this->media_speaker_ != nullptr) {
622 this->media_speaker_->set_mute_state(mute_state);
623 }
624 if (this->announcement_speaker_ != nullptr) {
625 this->announcement_speaker_->set_mute_state(mute_state);
626 }
627
628 bool old_mute_state = this->is_muted_;
629 this->is_muted_ = mute_state;
630
632
633 if (old_mute_state != mute_state) {
634 if (mute_state) {
635 this->defer([this]() { this->mute_trigger_.trigger(); });
636 } else {
637 this->defer([this]() { this->unmute_trigger_.trigger(); });
638 }
639 }
640}
641
642void SpeakerMediaPlayer::set_volume_(float volume, bool publish) {
643 // A volume change made while an announcement uses the announcement volume applies right away
645 this->apply_speaker_volume_(volume);
646
647 if (publish) {
648 this->volume = volume;
650 }
651
652 this->defer([this, volume]() { this->volume_trigger_.trigger(volume); });
653}
654
656 // Remap the volume to fit within the configured limits. An effectively zero volume is passed through as zero so
657 // the speaker silences it, otherwise volume_min would make it audible.
658 float bounded_volume = (volume < SILENT_VOLUME_THRESHOLD)
659 ? 0.0f
660 : remap<float, float>(volume, 0.0f, 1.0f, this->volume_min_, this->volume_max_);
661
662 if (this->media_speaker_ != nullptr) {
663 this->media_speaker_->set_volume(bounded_volume);
664 }
665
666 if (this->announcement_speaker_ != nullptr) {
667 this->announcement_speaker_->set_volume(bounded_volume);
668 }
669}
670
671void SpeakerMediaPlayer::set_announcement_volume(float announcement_volume) {
672 this->announcement_volume_.set_volume(announcement_volume, this->speaker_volume_applier_());
673}
674
675} // namespace esphome::speaker
676
677#endif
void mark_failed()
Mark this component as failed.
bool cancel_interval(const char *name)
Cancel an interval function.
Definition component.cpp:92
bool cancel_timeout(const char *name)
Cancel a timeout function.
bool is_ready() const
void set_timeout(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a const char* name.
Definition component.cpp:96
void defer(const char *name, std::function< void()> &&f)
Defer a callback to the next loop() call with a const char* name.
void set_interval(const char *name, uint32_t interval, std::function< void()> &&f)
Set an interval function with a const char* name.
Definition component.cpp:88
ESPPreferenceObject make_entity_preference(uint32_t version=0)
Create a preference object for storing this entity's state/settings.
void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE
Inform the parent automation that the event has triggered.
Definition automation.h:556
void add_global_state_listener(OTAGlobalStateListener *listener)
void cancel()
The media player volume was set and already sent to the speakers; it applies until the next announcem...
void item_started(bool is_announcement, bool other_audio_playing, float media_volume, F &&apply)
Called when an item starts on a playlist that holds announcements, and may hold media too.
void set_volume(float volume, F &&apply)
Stores a new announcement volume and applies it right away if it is in use.
void playlist_finished(float media_volume, F &&apply)
Called when the playlist that holds announcements has finished. Switches back to the media volume.
void end(float media_volume, F &&apply)
Switches back to the media player volume if the announcement volume is in use.
virtual void set_volume(float volume)
Definition speaker.h:74
virtual void set_mute_state(bool mute_state)
Definition speaker.h:85
optional< media_player::MediaPlayerSupportedFormat > announcement_format_
void save_volume_restore_state_()
Saves the current volume and mute state to the flash for restoration.
std::deque< PlaylistItem > announcement_playlist_
void set_volume_(float volume, bool publish=true)
Updates this->volume and saves volume/mute state to flash for restortation if publish is true.
void play_file(const audio::AudioFile *media_file, bool announcement, bool enqueue)
void set_announcement_volume(float announcement_volume)
Sets the volume for announcements that start while nothing else is playing.
void set_mute_state_(bool mute_state)
Sets the mute state.
std::deque< PlaylistItem > media_playlist_
std::unique_ptr< AudioPipeline > media_pipeline_
void stop_and_unpause_media_()
Stops the media pipeline and polls until stopped to unpause it, avoiding an audible glitch.
optional< media_player::MediaPlayerSupportedFormat > media_format_
void control(const media_player::MediaPlayerCall &call) override
void apply_speaker_volume_(float volume)
Remaps a volume to the configured limits and sends it to the speakers.
void set_playlist_delay_ms(AudioPipelineType pipeline_type, uint32_t delay_ms)
void on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) override
std::unique_ptr< AudioPipeline > announcement_pipeline_
auto speaker_volume_applier_()
Returns a callable that sends a volume to the speakers, for announcement_volume_ to apply its changes...
media_player::MediaPlayerTraits get_traits() override
bool single_pipeline_()
Returns true if the media player has only the announcement pipeline defined, false if both the announ...
FLAG_HAS_TRANSITION float
const char * media_player_state_to_string(MediaPlayerState state)
OTAGlobalCallback * get_global_ota_callback()
ESPHOME_LOG_TAG(TAG, "announcement_volume")
T remap(U value, U min, U max, T min_out, T max_out)
Remap value from the range (min, max) to (min_out, max_out).
Definition helpers.h:887
static void uint32_t