Skip to content

fix: 🐛 Prevent nullptr == Tap() crash on iOS during recorder stream start - #496

Open
lavigarg-simform wants to merge 2 commits into
mainfrom
fix/I487_ios_crash_audio_bytes
Open

lavigarg-simform wants to merge 2 commits into
mainfrom
fix/I487_ios_crash_audio_bytes

Conversation

@lavigarg-simform

@lavigarg-simform lavigarg-simform commented Jun 19, 2026 •

Copy link
Copy Markdown

Description

On iOS, starting the recorder bytes stream could crash with a fatal
nullptr == Tap() exception (issue #487).

RecorderBytesStreamEngine.attach() installed a tap on input bus 0 without
clearing any existing tap first. Two paths left a stale tap on the bus:

  1. A second attach() call before detach() (re-start without teardown).
  2. A prior audioEngine.start() that threw — the tap was installed but never
    removed, leaking it onto the bus.

AVAudioEngine throws nullptr == Tap() when a tap is installed on a bus that
already has one, crashing the app.

This PR makes attach() reset prior state before installing:

  • Stops the engine if running, removes any existing tap on bus 0, and resets
    totalFrames.
  • On a failed audioEngine.start(), removes the just-installed tap so it
    doesn't leak onto the bus and crash the next attach().

No public API change.

Checklist

  • The title of my PR starts with a Conventional Commit prefix (fix:, feat:, docs: etc).
  • I have followed the Contributor Guide when preparing my PR.
  • I have updated/added tests for ALL new/updated/fixed functionality.
  • I have updated/added relevant documentation in docs and added dartdoc comments with ///.
  • I have updated/added relevant examples in examples or docs.

Breaking Change?

  • Yes, this PR is a breaking change.
  • No, this PR is not a breaking change.

Related Issues

Closes #487

@lavigarg-simform
lavigarg-simform marked this pull request as ready for review June 19, 2026 15:01

@vasu-nageshri-simform vasu-nageshri-simform left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The fix addresses the root cause of #487. I reproduced the crash with a standalone AVAudioEngine script on macOS:

  • Old pattern (installTap twice on bus 0 with no cleanup): aborts with *** Terminating app due to uncaught exception 'com.apple.coreaudio.avfaudio', reason: 'required condition is false: nullptr == Tap()'
  • PR pattern (stop() if running + removeTap(onBus: 0) before installTap): second attach works, no crash
  • removeTap(onBus: 0) on a bus with no tap is a no-op, so calling it unconditionally is safe
  • The example app builds for the iOS simulator on this branch (flutter build ios --simulator --debug)

Realistic ways to hit a double attach() in the current code, all covered by this fix:

  1. AudioRecorder (and its single RecorderBytesStreamEngine) is one instance per plugin, so two RecorderControllers recording, or a fast double tap on record() (the Dart state check runs before the await), calls attach() twice on the same engine.
  2. Hot restart: the native plugin instance survives while Dart state resets to stopped, so the next record() lands on an engine that still has the old tap.
  3. A failed audioEngine.start() left the tap installed. Fixed by the removeTap in catch.

Inline comments below. Only the paused reset is something I'd like to see in this PR. The rest are nits or follow-ups.

General

  • Heads-up: the pending SPM work (feat/swift-package-manager-support) renames this file to ios/audio_waveforms/Sources/audio_waveforms/RecorderBytesStreamEngine.swift. Whichever lands second needs a rebase. Git rename detection should handle most of it, but please double-check the change ends up in the new path.
  • macOS isn't affected: it creates a new AVAudioEngine on each record and doesn't use RecorderBytesStreamEngine.

Comment thread CHANGELOG.md Resolved
- Reset the paused flag in attach() and detach() so a recording started
  after record -> pause -> stop is not silent
- Make attach() throw and let startRecording send a single result, and
  stop the file recorder when the engine fails to start
- Fix indentation in the catch block
- Link the PR in the CHANGELOG entry
@vasu-nageshri-simform
vasu-nageshri-simform force-pushed the fix/I487_ios_crash_audio_bytes branch from 745409c to ed22b64 Compare October 29, 2026 04:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants