Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

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

Abierto
#1,206 2 comentarios 0 reacciones 1 asignado Ver en GitHub

Los mantenedores suelen responder en 1 día

@hiroshihorie ya está trabajando en esto.

Desde el 15/9/2026.

  • #1212 de @hiroshihorie — abierto

Evaluación

Este issue todavía no se ha evaluado.

Descripción

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.

Lenguaje dominante
Dart
Estrellas
421
Forks
260
Merge medio
2 d 5 h
PR fusionados (30 d)
17

Preparar el entorno

Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de livekit/client-sdk-flutter

Todos los issues de livekit/client-sdk-flutter

Issues similares

Más issues de Dart

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.