Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

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

Đang mở
#1,206 2 bình luận 0 reaction 1 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

@hiroshihorie đang làm issue này rồi.

Từ ngày 15/9/2026.

  • #1212 của @hiroshihorie — đang mở

Đá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 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.

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

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. 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.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của livekit/client-sdk-flutter

Tất cả issue của livekit/client-sdk-flutter

Issue tương tự

Thêm issue về Dart

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.