diff --git a/esphome/components/mixer/speaker/mixer_speaker.cpp b/esphome/components/mixer/speaker/mixer_speaker.cpp index 8e1278206f4..9d11abb3277 100644 --- a/esphome/components/mixer/speaker/mixer_speaker.cpp +++ b/esphome/components/mixer/speaker/mixer_speaker.cpp @@ -11,14 +11,14 @@ #include #include -namespace esphome { -namespace mixer_speaker { +namespace esphome::mixer_speaker { static const UBaseType_t MIXER_TASK_PRIORITY = 10; static const uint32_t STOPPING_TIMEOUT_MS = 5000; static const uint32_t TRANSFER_BUFFER_DURATION_MS = 50; static const uint32_t TASK_DELAY_MS = 25; +static const uint32_t MIXER_AUTO_STOP_DEBOUNCE_MS = 200; static const size_t TASK_STACK_SIZE = 4096; @@ -471,6 +471,7 @@ void MixerSpeaker::loop() { this->task_.deallocate(); ESP_LOGD(TAG, "Stopped"); xEventGroupClearBits(this->event_group_, MIXER_TASK_ALL_BITS); + this->all_stopped_since_ms_ = 0; } if (this->task_.is_created()) { @@ -483,8 +484,18 @@ void MixerSpeaker::loop() { } if (all_stopped) { - // Send stop command signal to the mixer task since no source speakers are active - xEventGroupSetBits(this->event_group_, MIXER_TASK_COMMAND_STOP); + if (this->all_stopped_since_ms_ == 0) { + this->all_stopped_since_ms_ = millis(); + } else if ((millis() - this->all_stopped_since_ms_) >= MIXER_AUTO_STOP_DEBOUNCE_MS) { + // Send stop command only after a short debounce to avoid stop/start thrash during rapid seeks. + xEventGroupSetBits(this->event_group_, MIXER_TASK_COMMAND_STOP); + } + } else { + this->all_stopped_since_ms_ = 0; + // New activity detected; clear any stale auto-stop request before it can stop the running task. + if (event_group_bits & MIXER_TASK_COMMAND_STOP) { + xEventGroupClearBits(this->event_group_, MIXER_TASK_COMMAND_STOP); + } } } else { // Task is fully stopped and cleaned up, check if we can disable loop @@ -515,6 +526,9 @@ esp_err_t MixerSpeaker::start(audio::AudioStreamInfo &stream_info) { this->enable_loop_soon_any_context(); // ensure loop processes command + // Starting a new stream supersedes any previously queued stop request. + xEventGroupClearBits(this->event_group_, MIXER_TASK_COMMAND_STOP); + uint32_t event_bits = xEventGroupGetBits(this->event_group_); if (!(event_bits & MIXER_TASK_COMMAND_START)) { // Set MIXER_TASK_COMMAND_START bit if not already set, and then immediately wake for low latency @@ -755,7 +769,6 @@ void MixerSpeaker::audio_mixer_task(void *params) { vTaskSuspend(nullptr); // Suspend this task indefinitely until the loop method deletes it } -} // namespace mixer_speaker -} // namespace esphome +} // namespace esphome::mixer_speaker #endif diff --git a/esphome/components/mixer/speaker/mixer_speaker.h b/esphome/components/mixer/speaker/mixer_speaker.h index 0e0b33c39bc..29876ea262f 100644 --- a/esphome/components/mixer/speaker/mixer_speaker.h +++ b/esphome/components/mixer/speaker/mixer_speaker.h @@ -14,8 +14,7 @@ #include -namespace esphome { -namespace mixer_speaker { +namespace esphome::mixer_speaker { /* Classes for mixing several source speaker audio streams and writing it to another speaker component. * - Volume controls are passed through to the output speaker @@ -200,9 +199,9 @@ class MixerSpeaker : public Component { optional audio_stream_info_; std::atomic frames_in_pipeline_{0}; // Frames written to output but not yet played + uint32_t all_stopped_since_ms_{0}; // Debounce transient all-stopped windows before stopping task }; -} // namespace mixer_speaker -} // namespace esphome +} // namespace esphome::mixer_speaker #endif