Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

iOS: `audioDeviceModule` becomes permanently `nil`/unavailable after backgrounding, even though `AVAudioSession` is never interrupted

未关闭
#1,206 2 条评论 0 个 reaction 已指派 1 人 在 GitHub 查看

维护者通常 1 天内回复

@hiroshihorie 已经在做这个了。

开始于 2026年9月15日。

  • #1212 来自 @hiroshihorie —— 未关闭

评估

这个 Issue 还没有评估数据。

描述

On iOS, after a Room stays connected through an app background → foreground cycle, the WebRTC audio device module (ADM) becomes unavailable and never recovers within the process lifetime — even though a native-level trace shows AVAudioSession itself is completely undisturbed by the transition (no interruption, no route change, no media services reset). This affects both:

  • AudioManager.instance.setEngineAvailability(...), which throws PlatformException(setEngineAvailability, audio device module is unavailable, null, null).
  • localParticipant.setMicrophoneEnabled(true), which throws AudioProcessingException(platformUnavailable): audio device module is unavailable and does not recover on retry (tested up to 4 retries / ~2s).

Environment

  • livekit_client: 2.12.0
  • Flutter: 3.38.10
  • Platform: iOS (physical device), deployment target 15.0
  • WebRTC-SDK pod: 144.7559.09 (as pinned by livekit_client/flutter_webrtc)
  • Info.plist already declares UIBackgroundModes: [audio]

Repro steps

  1. Join a Room as a participant who is not publishing audio/video (listen-only), so the app never calls setMicrophoneEnabled/setCameraEnabled before backgrounding.
  2. Background the app (Home button / swipe up) and leave it backgrounded for roughly 10–30+ seconds. Do not disconnect the Room — it stays connected the whole time (AudioSessionManagementMode.automatic, no manual session calls before this point).
  3. Bring the app back to the foreground.
  4. From the app's resume/AppLifecycleListener.onRestart handler, call AudioManager.instance.setEngineAvailability(AudioEngineAvailability.none), or call localParticipant.setMicrophoneEnabled(true) to open the mic for the first time.

Actual behavior

Both calls fail immediately with the native side reporting the audio device module is unavailable:

PlatformException(setEngineAvailability, audio device module is unavailable, null, null)
AudioProcessingException(platformUnavailable): audio device module is unavailable
  • Retrying setMicrophoneEnabled (500ms backoff, 4 attempts) never succeeds.
  • Calling setEngineAvailability(AudioEngineAvailability.none) first (in an attempt to "kick" the engine back awake, mirroring the CallKit gate pattern) also fails outright, because — per LiveKitPlugin.swift's handleSetEngineAvailability — that call itself requires a live audioDeviceModule to operate on:
guard let adm = FlutterWebRTCPlugin.sharedSingleton()?.peerConnectionFactory?.audioDeviceModule else {
    result(FlutterError(code: "setEngineAvailability", message: "audio device module is unavailable", details: nil))
    return
}

So it can't be used to recover a already-nil ADM — only to gate one that's already alive.

  • We also tried AudioManager.instance.setAudioSessionOptions(const AudioSessionOptions.communication()) (switching to AudioSessionManagementMode.manual and immediately re-applying/activating the Apple audio session configuration) right before resuming, on the theory that proactively reconfiguring AVAudioSession before WebRTC touches it again might help. It completed without throwing, but had no effect — the ADM was still unavailable afterward.
  • This is a permanent condition for the rest of the process lifetime, not a transient timing issue: we've seen it persist across multiple minutes and multiple foreground interactions in the same session, with no self-recovery observed. (We have not yet fully confirmed whether it ever self-heals given a much longer wait — but several minutes was not enough.)

Expected behavior

Either:

  • The ADM should survive an app background/foreground cycle when the Room stays connected (no explicit disconnect()), especially since UIBackgroundModes: audio is declared and AVAudioSession itself shows no signs of interruption; or
  • There should be a supported, documented way to force-recreate/recover the ADM from the Dart API surface after this happens, distinct from setEngineAvailability (which requires a live ADM as a precondition and can't be used to fix a dead one).

Diagnostic evidence: AVAudioSession is not the problem

To rule out an OS-level audio session interruption as the cause, we added native observers (in our own AppDelegate, not modifying any plugin) for:

  • UIApplication.willResignActiveNotification / didEnterBackgroundNotification / willEnterForegroundNotification / didBecomeActiveNotification
  • AVAudioSession.interruptionNotification
  • AVAudioSession.routeChangeNotification
  • AVAudioSession.mediaServicesWereResetNotification

At each app-lifecycle event we log AVAudioSession.sharedInstance()'s category, mode, isOtherAudioPlaying, secondaryAudioShouldBeSilencedHint, recordPermission, and currentRoute. Across a full background → foreground cycle that reproduced the ADM failure, the output was:

[AudioDebug] willResignActive     | category=AVAudioSessionCategoryPlayback mode=AVAudioSessionModeSpokenAudio isOtherAudioPlaying=false secondaryAudioShouldBeSilenced=false recordPermission=granted route=<... outputs=(Speaker) inputs=() >
[AudioDebug] didEnterBackground   | category=AVAudioSessionCategoryPlayback mode=AVAudioSessionModeSpokenAudio isOtherAudioPlaying=false secondaryAudioShouldBeSilenced=false recordPermission=granted route=<... outputs=(Speaker) inputs=() >
[AudioDebug] willEnterForeground  | category=AVAudioSessionCategoryPlayback mode=AVAudioSessionModeSpokenAudio isOtherAudioPlaying=false secondaryAudioShouldBeSilenced=false recordPermission=granted route=<... outputs=(Speaker) inputs=() >
[AudioDebug] didBecomeActive      | category=AVAudioSessionCategoryPlayback mode=AVAudioSessionModeSpokenAudio isOtherAudioPlaying=false secondaryAudioShouldBeSilenced=false recordPermission=granted route=<... outputs=(Speaker) inputs=() >

category/mode/isOtherAudioPlaying/route are byte-for-byte identical across all four events. No interruption, routeChange, or mediaServicesWereReset notification fired at any point. This strongly suggests the ADM teardown is not driven by any observable AVAudioSession-level event, but happens internally inside the WebRTC-SDK binary's own engine/session management, which we don't have source access to (it ships as a precompiled WebRTC-SDK pod, version 144.7559.09).

We also confirmed LKAudioEngineObserver (LiveKitPlugin.swift) — the Swift-side glue that owns iOS audio session activation timing — is purely reactive to RTCAudioDeviceModuleDelegate callbacks coming from the WebRTC-SDK binary; it does not itself observe UIApplication lifecycle notifications. So whatever decides to tear the engine/ADM down on backgrounding is a decision made inside the closed-source binary, not in anything on the Dart or Swift-glue side we can inspect or patch.

What we've already ruled out

  • Upgrading to 2.12.0: we were originally on an older livekit_client and upgraded specifically hoping this would fix it (required a coordinated freezed/json_serializable/json_annotation bump in our app to unblock the version). It did not change the behavior.
  • PR #1182 / #1165: fixes iOS engine error -9001 for pre-connect audio / pre-join mic preview / mic-only scenarios, where the session policy hadn't been pushed to native yet because it's normally only pushed from Room.connect(). Our repro is post-connect, mid-session, well after the room is connected and the policy has been pushed — different code path.
  • PR #1179: same -9001 root cause as #1182, folded into it.
  • PR #1183 (ensureMicrophoneAccess): addresses microphone permission requests silently stalling when triggered while the app isn't active. Not our case — our test device already has microphone permission .authorized (confirmed via AVAudioSession.recordPermission in the log above), so this permission-check path is a no-op for us.
  • We confirmed via direct inspection of the installed livekit_client-2.12.0 package source that all three of the above are already present in what we're running.

Question for maintainers

  • Is this a known limitation of how the WebRTC-SDK fork (144.7559.09) manages its audio engine/ADM across iOS app backgrounding, even when UIBackgroundModes: audio is declared and the app never disconnects the Room?
  • Is there a supported way (public API or documented pattern) to force the ADM to be recreated/recovered after this happens, short of a full app restart?
  • Would adding instrumentation/logging around ADM creation/teardown inside the WebRTC-SDK binary (or exposing an event for it) be something the team would consider, given AVAudioSession-level signals don't correlate with the failure at all?

Happy to provide a minimal reproduction project, a full device log, or test a patched build if that would help narrow this down further.

主要语言
Dart
星标
421
派生
260
平均合并
2 天 17 小时
30 天内合并 PR
14

环境准备

这个项目没有提供开发容器、Dockerfile 或贡献指南,环境需要你自己搭建:先看它的 README,通用步骤见我们的新手贡献指南。

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

livekit/client-sdk-flutter 的其他 Issue

查看 livekit/client-sdk-flutter 的全部 Issue

相似的 Issue

更多 Dart Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。