12static constexpr uint32_t MEDIA_CONTROLS_QUEUE_LENGTH = 20;
14static const char *
const TAG =
"speaker_source_media_player";
48 this->
pipelines_[pipeline].sources.emplace_back(std::make_unique<SourceBinding>(
this, media_source, pipeline));
54 "Speaker Source Media Player:\n"
55 " Volume Increment: %.2f\n"
69 if (this->
pref_.
load(&volume_restore_state)) {
78 for (
size_t i = 0; i < this->
pipelines_.size(); i++) {
93 if (active_source ==
nullptr) {
102 source_frames = std::min(frames, current);
103 }
while (source_frames > 0 &&
104 !ps.
pending_frames.compare_exchange_weak(current, current - source_frames, std::memory_order_relaxed));
106 if (source_frames > 0) {
130 call.set_media_url(uri);
196 vTaskDelay(pdMS_TO_TICKS(timeout_ms));
199 size_t bytes_written = ps.
speaker->
play(data,
length, pdMS_TO_TICKS(timeout_ms));
200 if (bytes_written > 0) {
204 return bytes_written;
208 vTaskDelay(pdMS_TO_TICKS(timeout_ms));
216 switch (
source->get_state()) {
222 ESP_LOGE(TAG,
"Media source error");
255 if (announcement_source !=
nullptr) {
259 switch (announcement_state) {
265 ESP_LOGE(TAG,
"Announcement source error");
283 if (this->
state != old_state) {
292 for (
auto &binding : ps.
sources) {
293 if (binding->source->can_handle(uri)) {
296 return binding->source;
298 if (first_match ==
nullptr) {
299 first_match = binding->source;
309 if (target_source ==
nullptr) {
310 ESP_LOGW(TAG,
"No source for URI");
311 ESP_LOGV(TAG,
"URI: %s", uri.c_str());
320 if (active_source !=
nullptr) {
325 ESP_LOGV(TAG,
"Pipeline %u: stopping active source", pipeline);
339 ESP_LOGV(TAG,
"Pipeline %u: target source busy, stopping", pipeline);
359 if (!target_source->
play_uri(uri)) {
360 ESP_LOGE(TAG,
"Pipeline %u: Failed to play URI: %s", pipeline, uri.c_str());
375 cmd.pipeline = pipeline;
377 ESP_LOGE(TAG,
"Queue full, command dropped");
384 this->
pipelines_[pipeline].playlist_delay_ms = delay_ms;
392 this->
set_timeout(PIPELINE_TIMEOUT_IDS[pipeline], delay_ms,
408 bool command_executed =
false;
409 uint8_t pipeline = control_command.
pipeline;
415 switch (control_command.type) {
422 ps.
playlist.push_back(*control_command.data.uri);
426 command_executed =
true;
432 ps.
playlist.push_back(*control_command.data.uri);
440 bool nothing_playing =
446 command_executed =
true;
463 command_executed =
true;
473 command_executed =
true;
480 command_executed =
true;
486 if (command_executed) {
492 delete control_command.data.uri;
506 if (active_source !=
nullptr) {
507 target_source = active_source;
512 switch (player_command) {
516 if (target_source !=
nullptr) {
519 }
else if (!has_internal_playlist && active_source ==
nullptr && !ps.
playlist.empty()) {
521 if (last_has_internal_playlist) {
530 if (target_source !=
nullptr) {
538 if (!has_internal_playlist && active_source ==
nullptr && !ps.
playlist.empty()) {
540 if (last_has_internal_playlist) {
548 }
else if (target_source !=
nullptr) {
555 if (target_source !=
nullptr) {
562 if (!has_internal_playlist) {
568 if (target_source !=
nullptr) {
575 if (!has_internal_playlist) {
584 }
else if (target_source !=
nullptr) {
591 if (!has_internal_playlist) {
600 }
else if (target_source !=
nullptr) {
607 if (!has_internal_playlist) {
609 }
else if (target_source !=
nullptr) {
615 if (!has_internal_playlist) {
617 }
else if (target_source !=
nullptr) {
623 if (!has_internal_playlist) {
625 }
else if (target_source !=
nullptr) {
631 if (!has_internal_playlist) {
643 }
else if (target_source !=
nullptr) {
650 if (!has_internal_playlist) {
652 }
else if (target_source !=
nullptr) {
658 if (!has_internal_playlist) {
660 }
else if (target_source !=
nullptr) {
683 auto announcement =
call.get_announcement();
684 if (announcement.has_value() && announcement.value()) {
698 auto media_url =
call.get_media_url();
699 if (media_url.has_value()) {
700 auto command =
call.get_command();
711 control_command.data.uri =
new std::string(media_url.value());
713 delete control_command.data.uri;
714 ESP_LOGE(TAG,
"Queue full, URI dropped");
726 auto cmd =
call.get_command();
727 if (cmd.has_value()) {
728 switch (cmd.value()) {
744 control_command.data.command = cmd.value();
746 ESP_LOGE(TAG,
"Queue full, command dropped");
759 traits.set_supports_pause(
true);
762 if (ps.format.has_value()) {
763 traits.get_supported_formats().push_back(ps.format.value());
783 if (ps.is_configured()) {
784 ps.speaker->set_mute_state(mute_state);
795 for (
auto &ps : this->pipelines_) {
796 for (
auto &binding : ps.sources) {
797 binding->source->notify_mute_changed(mute_state);
813 if (ps.is_configured()) {
814 ps.speaker->set_volume(bounded_volume);
823 for (
auto &ps : this->pipelines_) {
824 for (
auto &binding : ps.sources) {
825 binding->source->notify_volume_changed(
volume);
867 for (
size_t i = 0; i < ps.
playlist.size(); i++) {
ESPDEPRECATED("Use const char* overload instead. Removed in 2026.7.0", "2026.1.0") void defer(const std voi defer)(const char *name, std::function< void()> &&f)
Defer a callback to the next loop() call.
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") void set_timeout(const std voi set_timeout)(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") bool cancel_timeout(const std boo cancel_timeout)(const char *name)
Cancel a timeout function.
ESPPreferenceObject make_entity_preference(uint32_t version=0)
Create a preference object for storing this entity's state/settings.
void trigger(const Ts &...x)
Inform the parent automation that the event has triggered.
virtual size_t play(const uint8_t *data, size_t length)=0
Plays the provided audio data.
void set_audio_stream_info(const audio::AudioStreamInfo &audio_stream_info)
audio::AudioStreamInfo & get_audio_stream_info()
uint32_t random_uint32()
Return a random 32-bit unsigned integer.
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).
speaker::Speaker * speaker
std::vector< size_t > shuffle_indices
uint32_t playlist_delay_ms
media_source::MediaSource * last_source
media_source::MediaSource * pending_source
std::atomic< uint32_t > pending_frames
std::vector< std::unique_ptr< SourceBinding > > sources
std::vector< std::string > playlist
std::atomic< media_source::MediaSource * > active_source
media_source::MediaSource * stopping_source
void request_play_uri(const std::string &uri) override
void request_mute(bool is_muted) override
SpeakerSourceMediaPlayer * player
void request_volume(float volume) override
void report_state(media_source::MediaSourceState state) override
size_t write_audio(const uint8_t *data, size_t length, uint32_t timeout_ms, const audio::AudioStreamInfo &stream_info) override
media_source::MediaSource * source