From b74ac8028440e3d7a822fe77415d01a68126c7ac Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Sat, 11 Jul 2026 00:51:44 +0900 Subject: [PATCH] Notify the observer when an enable operation rolls back after will-enable OnEngineWillEnable is invoked before the fallible parts of an enable operation: permission and category checks, node configuration, and engine start. When any of those failed, the rollback replayed only the constructive engine actions and never told the observer, so an observer that configured and activated the audio session for the enable was left holding an activation for an engine state that never materialized. It leaked until the next retry or stop. Push a rollback action right after a successful will-enable that fires OnEngineDidDisable with the previous engine state. Existing observers already implement did-disable as the release edge, so they get correct compensation without adopting a new callback. The result is ignored since the rollback itself cannot be aborted. Applies to both the device and manual rendering paths. --- modules/audio_device/audio_engine_device.mm | 28 +++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/modules/audio_device/audio_engine_device.mm b/modules/audio_device/audio_engine_device.mm index 76a3973746..1cb07fb123 100644 --- a/modules/audio_device/audio_engine_device.mm +++ b/modules/audio_device/audio_engine_device.mm @@ -1844,6 +1844,20 @@ AVAudioVoiceProcessingOtherAudioDuckingLevel ToAVDuckingLevel( LOGE() << "Call to OnEngineWillEnable returned error: " << result; return rollback(result); } + rollback_actions.push_back([this, state]() { + RTC_DCHECK_RUN_ON(thread_); + // Compensate the observer if a later step of this enable operation fails. + // It may have configured and activated the audio session for the enable + // that will now never happen, and without this call it is never told the + // engine rolled back. Reuses OnEngineDidDisable with the previous state so + // existing observers release what they acquired without adopting a new + // callback. The result is ignored, the rollback itself cannot be aborted. + if (observer_ != nullptr) { + LOGW() << "Enable rolled back after OnEngineWillEnable, notifying observer (Manual)"; + observer_->OnEngineDidDisable(engine_manual_input_, state.prev.IsOutputEnabled(), + state.prev.IsInputEnabled()); + } + }); } if (state.next.IsOutputEnabled() && !state.prev.IsOutputEnabled()) { @@ -2272,6 +2286,20 @@ AVAudioVoiceProcessingOtherAudioDuckingLevel ToAVDuckingLevel( LOGE() << "Call to OnEngineWillEnable returned error: " << result; return rollback(result); } + rollback_actions.push_back([this, state]() { + RTC_DCHECK_RUN_ON(thread_); + // Compensate the observer if a later step of this enable operation fails. + // It may have configured and activated the audio session for the enable + // that will now never happen, and without this call it is never told the + // engine rolled back. Reuses OnEngineDidDisable with the previous state so + // existing observers release what they acquired without adopting a new + // callback. The result is ignored, the rollback itself cannot be aborted. + if (observer_ != nullptr) { + LOGW() << "Enable rolled back after OnEngineWillEnable, notifying observer"; + observer_->OnEngineDidDisable(engine_device_, state.prev.IsOutputEnabled(), + state.prev.IsInputEnabled()); + } + }); } // --------------------------------------------------------------------------------------------