Skip to content

iOS: recover call audio after external audio session changes - #3

Merged
Magmusacy merged 6 commits into
fishjam-m124from
fishjam-m124-callkit-audio-restart
Oct 8, 2026
Merged

Magmusacy merged 6 commits into
fishjam-m124from
fishjam-m124-callkit-audio-restart

Conversation

@Magmusacy

@Magmusacy Magmusacy commented Oct 6, 2026 •

Copy link
Copy Markdown

Fixes iOS call audio going silent for the rest of a call when other code deactivates the audio session (e.g. expo-audio after playing a sound).

Problem

  1. Other code deactivates the shared AVAudioSession.
  2. iOS stops WebRTC's VoiceProcessingIO unit.
  3. AudioDeviceIOS still has it marked as started, so UpdateAudioUnit never restarts it.

Fix

  1. VoiceProcessingAudioUnit listens to kAudioOutputUnitProperty_IsRunning and reports changes through OnRunningStateChanged().
  2. AudioDeviceIOS restarts a unit that is marked as started but isn't running, through UpdateAudioUnit (same path as an interruption end).
  3. If Stop() fails, it logs and leaves the unit alone.

The restart respects canPlayOrRecord. CallKit hold is handled by the app in fishjam-cloud/fishjam-react-native-webrtc#102.

Testing

iPhone 16 (iOS 26.6.2), fishjam-chat + browser peer:

  • Session deactivated (foreground, background, after a sound): audio back after ~0.4 s.
  • With #102, CallKit hold → unhold: silent while held, back after ~1 s, peer hears the mic.
  • Regular call flows: no regressions.

When CallKit deactivates and later reactivates the audio session (e.g. a
call put on hold and resumed), the audio unit stops but AudioDeviceIOS keeps
treating it as started, so it is never restarted and the call stays silent.
Report an interruption begin on external deactivation, and before the
interruption end on external activation, so the unit is stopped cleanly and
restarted.

Claude-Session: https://claude.ai/code/session_01NAQ1TqkaV8fAgzXw48z9Gp
@Magmusacy Magmusacy changed the title iOS: restart the audio unit after external audio session changes iOS: recover call audio after external audio session changes Oct 7, 2026
@Magmusacy
Magmusacy requested a balanced review from Copilot October 7, 2026 10:09

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Synthetic public interruption events and an unhandled stop failure can produce inconsistent state or prevent recovery.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
What changed in this PR

Adds recovery for iOS audio units stopped by CallKit or external audio-session changes.

Changes:

  • Converts external activation/deactivation into restart signals.
  • Monitors VoiceProcessingIO running-state changes.
  • Adds interruption-event tests.
File Description
RTCAudioSessionTest.mm Tests external session event reporting.
voice_processing_audio_unit.mm Adds running-state monitoring.
voice_processing_audio_unit.h Extends observer and unit interfaces.
audio_device_ios.mm Restarts externally stopped units.
audio_device_ios.h Declares recovery callbacks.
RTCAudioSession.mm Emits events for external session changes.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread sdk/objc/components/audio/RTCAudioSession.mm Outdated
Comment thread sdk/objc/native/src/audio/audio_device_ios.mm Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

A failed running-state query is incorrectly treated as confirmation that the audio unit stopped.

0 open findings

2 resolved since last review
Previously missed (1)

In code that hasn't changed since last review

Medium severity Avoid recovery when AudioUnitGetProperty status is unknown

sdk/​objc/​native/​src/​audio/​voice_processing_audio_unit.mm:392

AudioUnitGetProperty failures are indistinguishable from a confirmed stopped state here. The caller consequently invokes Stop() and recovery whenever the query fails, which can interrupt a unit that is still running and obscures the actual Core Audio error. Return the OSStatus separately (for example, via a status return plus an out parameter), log it, and skip recovery unless the query succeeded with is_running == 0.

🧠 Review effort: Balanced

@Magmusacy
Magmusacy requested a review from czerwiukk October 7, 2026 18:15
@Magmusacy
Magmusacy merged commit 9316c64 into fishjam-m124 Oct 8, 2026
@Magmusacy
Magmusacy deleted the fishjam-m124-callkit-audio-restart branch October 8, 2026 14:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants