Skip to content

[iOS] startStream() takes 3+ seconds on first call, blocks UI thread, and conflicts with existing audio playback #604

Description

@liuzhihaoa

Description
Environment
Plugin: record 6.2.1 / record_ios 1.2.1
iOS: 26.5 (physical device)
Flutter: 3.x
Problem
Calling startStream() for the first time on iOS takes 3+ seconds. During this time, the Flutter UI freezes completely. Subsequent calls are faster but still around 700ms. Additionally, if an existing AVAudioEngine or NativeAudioIOS playback is active (e.g. a live video stream), startStream() breaks the existing audio — causing AU render err: -1 spam in the console.

Our use case is a live video player with an intercom (talk) feature: the user watches a live video stream with audio, then presses a button to start sending audio to the device. startStream() is called to capture microphone input.

Root Cause Analysis
I've identified three specific issues in the iOS implementation:

  1. AVAudioEngine created fresh on every startStream() call
    File: RecorderStreamDelegate.swift:27

swift
应用
let audioEngine = AVAudioEngine()
A new AVAudioEngine is allocated every time start() is called. After stop() (line 86-88), it's destroyed:

swift
应用
m_audioEngine?.inputNode.removeTap(onBus: m_bus)
m_audioEngine?.stop()
m_audioEngine = nil // engine destroyed, next start() must re-create
The first AVAudioEngine.start() on iOS involves heavy initialization: allocating AudioUnits, configuring the audio processing pipeline, setting up input routing. This is the primary cause of the 3-second delay.

Suggested fix: Create the AVAudioEngine once and keep it alive, using pause()/resume() instead of stop()/destroy.

  1. setActive(true) conflicts with existing audio playback
    File: RecorderSessionExtension.swift:25-31

if manage {
try audioSession.setCategory(.playAndRecord, options: ...)
try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
}
Even when the AVAudioSession is already in .playAndRecord mode (set by the app), the plugin unconditionally calls setCategory + setActive(true). This deactivates and reactivates the audio session, interrupting any existing playback. This causes AU render err: -1 and breaks the video audio during talk initiation.

Suggested fix: Check if the session is already in the desired category/state before re-activating. Or expose a config option to skip internal session management when the app handles it externally.

  1. audioEngine.start() blocks the main thread
    File: RecorderStreamDelegate.swift:70-71

audioEngine.prepare()
try audioEngine.start()
Both prepare() and start() run synchronously on the main thread (called from the Flutter MethodChannel handler). On first use, AVAudioEngine.start() takes 2-3 seconds because iOS initializes the audio input hardware, configures voice processing I/O, and sets up routing — all synchronously. This blocks the Flutter platform thread and freezes the UI.

Suggested fix: Move prepare() to happen eagerly (e.g. on engine creation), and consider offloading start() to a background queue if possible, or at minimum document that the first call will be slow.

Additional observation
setVoiceProcessingEnabled(true) in line 30 also adds to the initialization cost. Voice processing I/O requires additional setup on the AudioUnit.

Reproduction Steps
Start an AVAudioEngine or other audio playback (simulating video audio)
Call AudioRecorder.startStream() with:
encoder: pcm16bits
sampleRate: 8000
numChannels: 1
echoCancel: true
autoGain: true
Observe 3+ second freeze on first call
Console shows AU render err: -1 spam
Expected Behavior
First startStream() should complete in < 500ms
Should not block Flutter UI rendering
Should not break existing audio playback

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions