Skip to content

Invalid engine states on recording or playback start#1005

Merged
maciejmakowski2003 merged 22 commits intomainfrom
fix/1004-invalid-engine-state-on-recording-or-playback-start
Apr 14, 2026
Merged

Invalid engine states on recording or playback start#1005
maciejmakowski2003 merged 22 commits intomainfrom
fix/1004-invalid-engine-state-on-recording-or-playback-start

Conversation

@michalsek
Copy link
Copy Markdown
Member

@michalsek michalsek commented Mar 31, 2026

Closes #1004
Closes RNAA-407
Closes RNAA-382

⚠️ Breaking changes ⚠️

  • None.

Introduced changes

  • Fixes issue when audio session was deactivated without suspending the context leading to broken state.
  • Both iOS player and recorder will try to activate or re-activate the session when starting.
  • Each time AVAudioEngine enters "iddle" state it will be teared down, to reconstruct later
  • Fixes missing isPaused in AudioRecorder JSI interface.
  • Adds sloppy (code quality) playback->recording->playback automated test screen

Checklist

  • Linked relevant issue
  • Updated relevant documentation
  • Added/Conducted relevant tests
  • Performed self-review of the code
  • Updated Web Audio API coverage
  • Added support for web
  • Updated old arch android spec file

@michalsek michalsek requested a review from Copilot March 31, 2026 17:58
@michalsek michalsek self-assigned this Mar 31, 2026
@michalsek michalsek added fix bug Something isn't working labels Mar 31, 2026
Copy link
Copy Markdown
Contributor

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 improves iOS audio session / engine state management to prevent invalid states when starting recording or playback, and adds an iOS-focused stress-test screen in the example app to exercise these transitions.

Changes:

  • Introduces AudioSessionManager::ensureActive(force, error) and new input-route helpers to make session activation/configuration more robust.
  • Refactors AudioEngine lifecycle (create/destroy/rebuild) and adds handling for explicit session deactivation + better “engine running” checks.
  • Expands the example app with an “Audio Pipeline Stress” suite and exposes AudioRecorderHostObject.isPaused() to JS.

Reviewed changes

Copilot reviewed 17 out of 19 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
packages/react-native-audio-api/ios/audioapi/ios/system/AudioSessionManager.mm Adds ensureActive, activation helper, cached-state reset, and input route/format helpers.
packages/react-native-audio-api/ios/audioapi/ios/system/AudioSessionManager.h Exposes ensureActive, input-route/format helpers publicly.
packages/react-native-audio-api/ios/audioapi/ios/system/AudioEngine.mm Adds engine create/destroy, graph rebuild tracking, session-deactivated handler, and running-state helper.
packages/react-native-audio-api/ios/audioapi/ios/system/AudioEngine.h Declares graphNeedsRebuild, onSessionDeactivated, isEngineRunning.
packages/react-native-audio-api/ios/audioapi/ios/core/NativeAudioRecorder.m Uses getPreferredInputFormat fallback when engine format is invalid.
packages/react-native-audio-api/ios/audioapi/ios/core/NativeAudioPlayer.m Forces session activation for playback and makes source-node attach/detach idempotent.
packages/react-native-audio-api/ios/audioapi/ios/core/IOSAudioRecorder.mm Forces session activation for recording and errors early if input route/format isn’t usable.
packages/react-native-audio-api/ios/audioapi/ios/core/IOSAudioPlayer.mm Tightens “is playing” check to include underlying engine running state.
packages/react-native-audio-api/ios/audioapi/ios/AudioAPIModule.mm On session deactivation, clears cached session active state and updates engine state.
packages/react-native-audio-api/common/cpp/audioapi/HostObjects/inputs/AudioRecorderHostObject.cpp Exports isPaused() to JS.
packages/react-native-audio-api/common/cpp/audioapi/core/sources/AudioBufferBaseSourceNode.cpp Formatting-only change around NOLINT placement.
packages/react-native-audio-api/common/cpp/audioapi/core/effects/WorkletProcessingNode.cpp Adds missing <algorithm> include.
apps/fabric-example/ios/Podfile.lock Updates CocoaPods lockfile checksums.
apps/fabric-example/ios/FabricExample/Info.plist Whitespace/indentation cleanup.
apps/fabric-example/ios/FabricExample.xcodeproj/project.pbxproj Project config changes (signing team, bundle id, build phase metadata).
apps/common-app/src/examples/index.ts Registers the new AudioPipelineStress example screen.
apps/common-app/src/examples/AudioPipelineStress/index.ts Re-exports the new screen component.
apps/common-app/src/examples/AudioPipelineStress/AudioPipelineStress.tsx Adds an iOS-only automated stress suite UI for record/playback + session toggling scenarios.
apps/common-app/src/examples/AudioFile/AudioPlayer.ts Removes unused import and improves formatting for readability.

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

@michalsek michalsek marked this pull request as ready for review April 13, 2026 10:30
@michalsek michalsek requested a review from Copilot April 13, 2026 12:34
Copy link
Copy Markdown
Contributor

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 58 out of 63 changed files in this pull request and generated 5 comments.

Comments suppressed due to low confidence (1)

apps/common-app/src/demos/Record/Record.tsx:130

  • Recorder.stop() returns paths: string[], which can legally be empty even on success (see other call sites that guard for this). Accessing info.paths[0] without a length check can pass undefined into decodeAudioData, causing a runtime error. Please guard for an empty paths array and surface a user-facing error.
  const onStopRecording = useCallback(async () => {
    const info = Recorder.stop();
    RecordingNotificationManager.hide();
    setState(RecordingState.Loading);

    if (info.status !== 'success') {
      Alert.alert('Error', `Failed to stop recording: ${info.message}`);
      setRecordedBuffer(null);
      setState(RecordingState.Idle);
      return;
    }

    const audioBuffer = await audioContext.decodeAudioData(info.paths[0]);
    setRecordedBuffer(audioBuffer);
    setState(RecordingState.ReadyToPlay);
    currentPositionSV.value = 0;
  }, []);

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

Copy link
Copy Markdown
Collaborator

@maciejmakowski2003 maciejmakowski2003 left a comment

Choose a reason for hiding this comment

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

g00d job

@maciejmakowski2003 maciejmakowski2003 merged commit 414255d into main Apr 14, 2026
5 checks passed
@maciejmakowski2003 maciejmakowski2003 deleted the fix/1004-invalid-engine-state-on-recording-or-playback-start branch April 14, 2026 13:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

recorder.start() fails with "sampleRate and channelCount must be greater than 0" despite active audio session

4 participants