iOS: `audioDeviceModule` becomes permanently `nil`/unavailable after backgrounding, even though `AVAudioSession` is never interrupted
维护者通常 1 天内回复
评估
这个 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 throwsPlatformException(setEngineAvailability, audio device module is unavailable, null, null).localParticipant.setMicrophoneEnabled(true), which throwsAudioProcessingException(platformUnavailable): audio device module is unavailableand 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-SDKpod: 144.7559.09 (as pinned bylivekit_client/flutter_webrtc)Info.plistalready declaresUIBackgroundModes: [audio]
Repro steps
- Join a
Roomas a participant who is not publishing audio/video (listen-only), so the app never callssetMicrophoneEnabled/setCameraEnabledbefore backgrounding. - 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). - Bring the app back to the foreground.
- From the app's resume/
AppLifecycleListener.onRestarthandler, callAudioManager.instance.setEngineAvailability(AudioEngineAvailability.none), or calllocalParticipant.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 — perLiveKitPlugin.swift'shandleSetEngineAvailability— that call itself requires a liveaudioDeviceModuleto 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 toAudioSessionManagementMode.manualand immediately re-applying/activating the Apple audio session configuration) right before resuming, on the theory that proactively reconfiguringAVAudioSessionbefore 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
Roomstays connected (no explicitdisconnect()), especially sinceUIBackgroundModes: audiois declared andAVAudioSessionitself 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/didBecomeActiveNotificationAVAudioSession.interruptionNotificationAVAudioSession.routeChangeNotificationAVAudioSession.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_clientand upgraded specifically hoping this would fix it (required a coordinatedfreezed/json_serializable/json_annotationbump in our app to unblock the version). It did not change the behavior. - PR #1182 / #1165: fixes iOS engine error
-9001for 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 fromRoom.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
-9001root 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 viaAVAudioSession.recordPermissionin 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.0package 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-SDKfork (144.7559.09) manages its audio engine/ADM across iOS app backgrounding, even whenUIBackgroundModes: audiois declared and the app never disconnects theRoom? - 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-SDKbinary (or exposing an event for it) be something the team would consider, givenAVAudioSession-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,通用步骤见我们的新手贡献指南。
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
livekit/client-sdk-flutter 的其他 Issue
-
难度 4/5 3-5 天 新手友好度 48/100
livekit/client-sdk-flutter#1227 ·
维护者通常 1 天内回复
-
难度 4/5 3-5 天 新手友好度 45/100
livekit/client-sdk-flutter#1221 · 1 条评论 ·
维护者通常 1 天内回复
-
难度 4/5 3-5 天 新手友好度 30/100
livekit/client-sdk-flutter#1219 · 1 个 reaction ·
维护者通常 1 天内回复
-
[bug]Early reconnect during the initial room connection可能重新可做 @hiroshihorie 于 42 天前认领,目前没有进行中的 PR。 未关闭
livekit/client-sdk-flutter#1189 · 已指派 1 人 ·
维护者通常 1 天内回复
-
难度 4/5 3-5 天 新手友好度 35/100
livekit/client-sdk-flutter#1186 · 1 个 reaction ·
维护者通常 1 天内回复
查看 livekit/client-sdk-flutter 的全部 Issue
相似的 Issue
-
Documentation
难度 1/5 1 小时以内 新手友好度 82/100
维护者通常 1 天内回复
-
Kazakh (kk_KZ) and Georgian (ka_GE) system locales get English instead of the shipped translations未关闭bug
难度 2/5 1-3 小时 新手友好度 75/100
维护者通常 1 天内回复
-
area: docs eco: dart good first issue help wanted lane: free-agent roadmap size: XS type: docs
难度 2/5 1-3 小时 新手友好度 72/100
arminazimi/evergreen#430 ·
维护者通常 1 天内回复
-
area: rust-core bug priority: low
难度 2/5 1-3 小时 新手友好度 72/100
维护者通常 1 天内回复
-
bug direct-code
难度 2/5 1-3 小时 新手友好度 66/100
aaronified/fluenough#526 · 1 条评论 ·
维护者通常 1 天内回复