[iOS] No audio in either direction after a call resumes from CallKit hold: the AVAudioEngine ADM is never told about CallKit audio session (de)activation

Aperta
#1,535 2 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 2 giorni

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
52/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
dart, swift

Direzione di ricerca

Start by reading StreamVideoCallkitManager.provider(_:didActivate:) and provider(_:didDeactivate:), then trace how the Flutter SDK reaches the RTCAudioDeviceModule in stream_webrtc_flutter. Check how the React Native implementation handles engine availability for comparison. Done means CallKit hold and resume restore audio playback and microphone recording, including when the call has already connected.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Environment

  • stream_video_flutter / stream_video / stream_video_push_notification: git ref 164832a0f29d1c0f6dd1edb9f2458bc568ad56ad (SDK version 1.7.2)
  • stream_webrtc_flutter: 4.0.1 (StreamWebRTC 145.17.0)
  • iOS 26.6, iPhone 15 Pro
  • Audio-only calls, reported to CallKit with PushNotificationManager.startOutgoingCall after join, then setCallConnected

Steps to reproduce

  1. Join an audio call on iOS with CallKit integration.
  2. Receive a regular phone call and answer it, so CallKit puts the Stream call on hold (ActionCallToggleHold(isOnHold: true)).
  3. End the phone call, so the Stream call resumes from hold (from the system UI, or by the app).

Expected

Audio plays and the microphone records again, as before the hold.

Actual

The call's UI and state look normal, but nothing is heard either way. Calling Call.suspendAudio() on hold and Call.resumeAudio() after CallKit re-activates the audio session (ActionCallToggleAudioSession(isActive: true)) does not help: the tracks are re-enabled, and the engine still produces no audio.

Root cause

StreamWebRTC 145 uses an AVAudioEngine-based RTCAudioDeviceModule. With CallKit, the engine has to be told when CallKit takes the audio session away and gives it back, through -[RTCAudioDeviceModule setEngineAvailability:]. Nothing in the Flutter SDK calls it:

  • stream_webrtc_flutter doesn't call setEngineAvailability and doesn't expose it.
  • StreamVideoCallkitManager.provider(_:didActivate:), for a call that hasConnected, only posts a synthetic AVAudioSession.interruptionNotification (type .ended, option .shouldResume) through sendDefaultAudioInterruptionNofificationToStartAudioResource(). That restarted the older audio-unit ADM, but the AVAudioEngine ADM doesn't restart from it.
  • provider(_:didDeactivate:) returns early while the call is on hold (it only prints Call is on hold), so nothing downstream learns the session went away.
  • ActionCallToggleHold and ActionCallToggleAudioSession are declared, but no SDK package acts on them.

The React Native SDK already handles this. GetStream/stream-video-js#2328 waits for CallKit's audio activation before starting the engine, and toggles engine availability on activation and deactivation (with GetStream/react-native-webrtc#53).

When it likely regressed

Probably in stream_video_flutter 1.4.0 / stream_webrtc_flutter 3.0.0 (2026-05-14). From the changelogs:

stream_webrtc_flutter StreamWebRTC (iOS) stream_video_flutter
1.0.5 (2025-05-20) to 2.2.7 (2026-04-27) 125.6422.070 up to 1.3.3
3.0.0 (2026-05-14) 145.9.0 1.4.0 (per-call factory and ADM)
3.2.0, 4.0.x 145.17.0 1.5.0 to 1.7.2

There's no StreamWebRTC version between 125 and 145 in the Flutter plugin, so the ADM's behaviour can only have changed in that one jump. The synthetic "interruption ended" notification in didActivate was written for the older audio-unit ADM. Stream's AVAudioEngine-based ADM is already in @stream-io/react-native-webrtc 137.2.0, and the React Native SDK added engine availability for it.

We haven't verified that hold/resume works on 1.3.x: our app adopted Stream Video on 1.5+ / 3.2.0. If it helps, we can run the same hold test on 1.3.3 to confirm.

Workaround we use

We expose setEngineAvailability through a small app-side method channel. The plugin resolves the call's factory through FlutterWebRTCPlugin.sharedSingleton().resolveFactory(forId:) and runs on factory.admQueue:

factory.admQueue.async {
    let code = adm.setEngineAvailability(RTCAudioEngineAvailability(
        isInputAvailable: available,
        isOutputAvailable: available
    ))
    DispatchQueue.main.async { result(code) }
}

Then on the Dart side:

// On ActionCallToggleHold(isOnHold: true):
await call.suspendAudio();
await CallAudioEngine.setAvailable(call, available: false);

// Once CallKit re-activated the audio session (ActionCallToggleAudioSession(isActive: true)):
await CallAudioEngine.setAvailable(call, available: true);
await call.resumeAudio();

With this, the engine comes back running, playing and recording, and audio works both ways after the hold. Logs from our native state dump:

before suspending: engine running: true, playing: true, recording: true, available input/output: true/true
suspended:         engine running: false, playing: false, available input/output: false/false
before resuming:   engine running: false, playing: false, available input/output: false/false
resumed:           engine running: true, playing: true, recording: true, available input/output: true/true

Suggested fix

The same thing the React Native SDK does: mark the engine unavailable in didDeactivate (including while the call is on hold), and available again in didActivate (including for a call that hasConnected). Ideally the SDK also exposes setEngineAvailability (for example on Call or the native factory) for apps that manage CallKit themselves.

Lingua principale
Dart
Stelle
79
Fork
55
Merge medio
2g 11h
PR unite (30g)
143

Preparare l'ambiente

  • Nessun Dockerfile né file Docker Compose
  • Ha un modello di pull request
  • Nessuna guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di GetStream/stream-video-flutter

Tutte le issue di GetStream/stream-video-flutter

Issue simili

Altre issue su Dart

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.