Skip to content

Fix iOS AVAudioSession before engine init#22

Open
txbrown wants to merge 6 commits into
mainfrom
fix/ios-audio-session-setup
Open

Fix iOS AVAudioSession before engine init#22
txbrown wants to merge 6 commits into
mainfrom
fix/ios-audio-session-setup

Conversation

@txbrown
Copy link
Copy Markdown
Collaborator

@txbrown txbrown commented May 5, 2026

iOS defaults to AVAudioSessionCategorySoloAmbient with a system-chosen buffer size (~4096 frames on device). Elementary's runtime is initialised with a fixed block size of 512, causing a mismatch that produces garbage audio (audible as a low-frequency square wave) immediately on app launch.

Fix: set AVAudioSessionCategoryPlayback with MixWithOthers and AllowBluetoothA2DP, request a 512-frame preferred buffer duration, activate the session before creating AVAudioEngine, then derive the actual block size from session.IOBufferDuration so the runtime always matches what the engine delivers per callback.

I didn't notice this issue on Midicircuit because it still has its own AVAudioEngine setup and some custom native code to bridge between it and react-native-elementary-audio.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the iOS native audio startup path so AVAudioSession is configured before AVAudioEngine is created, with the goal of preventing the startup-time buffer-size mismatch that currently produces corrupted audio on launch.

Changes:

  • Configure AVAudioSession during Elementary initialization, including playback category, options, preferred I/O buffer duration, and activation.
  • Extend init logging to include the granted IOBufferDuration.
  • Derive the Elementary runtime block size from the active session’s actual buffer duration instead of hard-coding 512.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ios/Elementary.mm Outdated
Comment thread ios/Elementary.mm Outdated
Comment thread ios/Elementary.mm Outdated
@txbrown txbrown force-pushed the fix/ios-audio-session-setup branch 2 times, most recently from 48913e8 to 417c7d3 Compare May 14, 2026 21:29
…er mismatch

iOS defaults to AVAudioSessionCategorySoloAmbient with a system-chosen
buffer size (~4096 frames on device). Elementary's runtime is initialized
with a fixed block size of 512, causing a mismatch that produces garbage
audio (audible as a low-frequency square wave) immediately on app launch.

Fix: configure AVAudioSession with Playback category before creating
AVAudioEngine, then derive the actual block size from
session.IOBufferDuration so the runtime always matches what the engine
delivers per callback.

- Initialize all NSError* to nil and reset between calls, since
  AVAudioSession APIs don't clear NSError** on success and stale
  pointers caused false error checks.

- Extract audio session configuration into configureAudioSession which
  checks for an already-active session before overwriting settings,
  preventing breakage for apps needing Ambient/PlayAndRecord/mute-switch
  behavior.

- Recreate runtime with current sample rate and block size after engine
  config changes (e.g. Bluetooth/AirPlay route switch) to avoid
  reintroducing buffer-size mismatch.

- Extract computeBufferSizeFromSession helper to deduplicate block size
  calculation.
@txbrown txbrown force-pushed the fix/ios-audio-session-setup branch 3 times, most recently from 417c7d3 to b8f8c12 Compare May 14, 2026 21:31
txbrown added 3 commits May 14, 2026 22:52
Add configurable iOS audio session ownership so apps can choose playback settings
or opt out when another audio stack owns AVAudioSession. Lazily initialize the
native engine so examples can configure the session before first render.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

Comment thread src/index.tsx Outdated
Comment thread ios/Elementary.mm Outdated
Comment thread ios/Elementary.mm Outdated
Comment thread ios/Elementary.mm Outdated
Comment thread ios/Elementary.mm
txbrown added 2 commits May 15, 2026 21:26
- Fix stale NSError: use separate local errors and nil-assign before
  each call so AVAudioSession APIs cannot leave garbage in the pointer.
- Early return in handleAudioInterruption when setActive fails so engine
  restart is not attempted on an inactive session.
- Compute preferred IO buffer duration from actual session sample rate
  (after category is set) instead of hardcoding 48000 Hz.
- Replace magic 0x4 with availability-checked constant for Bluetooth HFP.
- Serialize audio session state changes on the main thread to prevent
  data races on audioSessionActive from notification handlers and JS
  thread methods.
- JS: default configureAudioSession() argument to empty object so
  calling it with no args works.
AVAudioSessionCategoryOptionAllowBluetoothHFP is only available from
Xcode 26/iOS SDK 26.2. The CI runs Xcode 16.4 where the constant
doesn't exist. Fall back to the raw value with a comment explaining why.
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.

2 participants