iOS: `audioDeviceModule` becomes permanently `nil`/unavailable after backgrounding, even though `AVAudioSession` is never interrupted
Maintainer thường phản hồi trong vòng 1 ngày
Đánh giá
Issue này chưa được đánh giá.
Mô tả
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.
- Ngôn ngữ chính
- Dart
- Star
- 421
- Fork
- 260
- Merge trung bình
- 2 ngày 5 giờ
- Pull request đã merge (30 ngày)
- 17
Chuẩn bị môi trường
Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của livekit/client-sdk-flutter
-
`jwtPayload.video` returns null grants for tokens from LiveKit server SDKsCó thể đã có người làm @RaphaelFakhri đã nhận 3 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
livekit/client-sdk-flutter#1236 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 48/100
livekit/client-sdk-flutter#1227 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Inbound data packets never delivered while media flows normally (2.13.0, LiveKit Cloud 1.13.7)Đang mở
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 45/100
livekit/client-sdk-flutter#1221 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 30/100
livekit/client-sdk-flutter#1219 · 1 reaction ·
Maintainer thường phản hồi trong vòng 1 ngày
-
[bug]Early reconnect during the initial room connectionCó thể làm lại được @hiroshihorie đã nhận 40 ngày trước và không có pull request nào đang mở. Đang mở
livekit/client-sdk-flutter#1189 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của livekit/client-sdk-flutter
Issue tương tự
-
coverage 1.16.0: format_coverage fails on the `{}` that `dart test --coverage` writes for fully skipped suitesCó thể đã có người làm Có pull request liên kết đang mở hoặc đã được merge. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
Maintainer thường phản hồi trong vòng 1 ngày
-
fyi-ecosystem platform-ios team-ios
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 70/100
flutter/flutter#194348 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
good first issue
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 66/100
Maintainer thường phản hồi trong vòng 1 ngày
-
documentation good first issue
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 88/100
djazairdev/wilayas#22 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 80/100
rajput-hemant/calculator#199 · 3 bình luận · 1 reaction ·