Skip to content

fix(recording): stop audio dropping out on long recordings#536

Open
Reedaaz wants to merge 2 commits into
webadderallorg:mainfrom
Reedaaz:pr/audio-mixing-reliability
Open

fix(recording): stop audio dropping out on long recordings#536
Reedaaz wants to merge 2 commits into
webadderallorg:mainfrom
Reedaaz:pr/audio-mixing-reliability

Conversation

@Reedaaz
Copy link
Copy Markdown

@Reedaaz Reedaaz commented May 19, 2026

The problem

Recording audio cuts out / goes missing on longer takes — silently. The recording keeps going with no sound, so you only find out after a 10-minute take is wasted. It's intermittent and varies by OS / Windows version / audio driver.

This PR fixes three concrete, independent mechanisms that each produce exactly that symptom. None of them are speculative — they're visible in the current useScreenRecorder code.

Root causes & fixes (all in src/hooks/useScreenRecorder.ts)

1. Mixing graph garbage-collected mid-recording (the "cuts out after ~30s" case)

When mic + system audio are mixed, the code built an AudioContext graph but only kept a reference to the AudioContext itself. The nodes — two MediaStreamAudioSourceNodes, the mic GainNode, the MediaStreamAudioDestinationNode — were local variables.

A MediaStreamAudioSourceNode with no JS reference is garbage-collected by V8 even while still connected, so after a few dozen seconds the mixed track goes silent while video keeps recording. Classic Web Audio footgun.

Fix: retain all four nodes in a ref for the recording lifetime; disconnect them explicitly in cleanupCapturedMedia.

2. Mixer AudioContext can start suspended (the "no audio at all" case)

On some Windows / audio-driver configs the freshly created mixing AudioContext does not auto-start, so the mixed output is silent from frame one. This is a strong candidate for the OS/version variance.

Fix: await context.resume() and assert it reaches running, throwing a clear error otherwise instead of producing a silent file.

3. Mid-recording audio loss was swallowed silently

If the OS killed the audio device/session mid-recording (driver glitch, Bluetooth drop, Windows audio engine reset), capture silently continued with dead audio. And recorder.onerror was literally () => setRecording(false) — a swallowed error that left a broken file with no feedback.

Fix: monitor ended on the system / mic / mixed tracks and handle recorder.onerror properly — stop the capture, finalize whatever was recorded, and surface a toast immediately so a long take fails loud instead of silent. Also salvages already-recorded mic-fallback chunks when the recorder is already inactive on stop, and cleans up track listeners on every path.

Scope & safety

  • One file, +271/-52, two commits.
  • Only the browser-capture / mixed-audio paths are touched; single-track and native paths are unaffected.
  • Cases 1 & 2 are deterministic fixes (remove a whole failure class). Case 3 can't prevent the OS killing a device, but converts a silently-wasted recording into an immediate, explicit failure — which is the realistic best outcome for that class.

Testing

  • tsc --noEmit clean.
  • 46 useScreenRecorder unit tests pass.

Targets v1.3.0-beta.3. Independent of my other PRs (#531/#532/#533).

Built with Claude Code + Codex as tools; design, review and validation are mine.

Summary by CodeRabbit

  • Bug Fixes
    • Improved error handling for audio capture with clearer error messages displayed to users.
    • Enhanced cleanup of audio resources and tracks to prevent memory leaks.
    • Recording now stops gracefully when audio interruptions occur.
    • Strengthened microphone fallback recording stability and reliability.
    • More robust state management ensures consistent resource cleanup across recording sessions.

Review Change Stack

Reedaaz and others added 2 commits May 19, 2026 14:56
The mixing AudioContext was kept alive but its nodes (the two
MediaStreamAudioSourceNodes, the mic GainNode and the
MediaStreamAudioDestinationNode) were only local variables. With no JS
reference, Chromium/Electron garbage-collects a
MediaStreamAudioSourceNode even while it is still connected, silently
killing the mixed audio after a few dozen seconds while video keeps
recording — making long recordings unreliable.

Retain all four nodes in a ref for the recording lifetime and disconnect
them in cleanupCapturedMedia. Only the mic+system mix path is affected;
single-track paths never built an AudioContext.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…udio loss

Builds on the mixing-graph retention fix with two more reliability gaps:

- The mixing AudioContext could start in "suspended" state on some
  Windows / audio-driver configs, producing a recording with no audio
  from the start. Resume it and assert it reaches "running", throwing a
  clear error otherwise.
- Audio could die mid-recording (OS killing the device/session, driver
  glitch, Bluetooth drop) while capture silently continued, wasting long
  takes. Monitor "ended" on system/mic/mixed tracks and the recorder's
  onerror: stop the capture, finalize what was recorded, and surface a
  toast immediately instead of writing minutes of silent/corrupt output.
  The old recorder.onerror was a silent setRecording(false).
- Salvage already-recorded mic-fallback chunks when the recorder is
  already inactive on stop, and clean up track listeners on all paths.

Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions github-actions Bot added the Slop label May 19, 2026
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ This pull request has been flagged by Anti-Slop.
Our automated checks detected patterns commonly associated with
low-quality or automated/AI submissions (failure count reached).
No automatic closure — a maintainer will review it.
If this is legitimate work, please add more context, link issues, or ping us.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d4c9ad06-f311-4391-a993-85b44285efde

📥 Commits

Reviewing files that changed from the base of the PR and between 4f729b7 and 45ecd8f.

📒 Files selected for processing (1)
  • src/hooks/useScreenRecorder.ts

📝 Walkthrough

Walkthrough

useScreenRecorder now implements comprehensive audio resource lifecycle management: audio mixing graph nodes are retained and explicitly disconnected during cleanup, audio track listeners are registered and deregistered systematically, browser audio interruptions trigger one-shot stopping with user toasts, and recorder errors are handled through a dedicated path that computes error messages and performs full state cleanup. Background finalization is refactored for structured error handling with proper HUD closure timing.

Changes

Audio Lifecycle and Error Handling

Layer / File(s) Summary
Error Message and Toast Infrastructure
src/hooks/useScreenRecorder.ts
Toast ID constants for audio interruption and recorder errors, plus getMediaRecorderErrorMessage() helper to derive user-facing messages from MediaRecorder events.
Audio Node References and Cleanup Tracking
src/hooks/useScreenRecorder.ts
mixingNodes ref retains audio graph node references across the recording lifetime; mediaTrackMonitorCleanups stores listener cleanup functions; monitorTrackEnded() and cleanupMediaTrackMonitors() helpers manage track-ended listeners, integrated into main cleanup path.
Browser Audio Track Monitoring Setup
src/hooks/useScreenRecorder.ts
browserAudioTracksToMonitor array collects system, microphone, and mixed audio tracks; browser audio mixing graph stores node references in mixingNodes and populates the monitoring list; monitorTrackEnded handlers wire up for each track to trigger stopBecauseBrowserAudioEnded.
Browser Recorder Error and Interruption Handling
src/hooks/useScreenRecorder.ts
stopBecauseBrowserAudioEnded() implements one-shot stop on audio track end with toast; handleBrowserRecorderError() deduplicates error handling, computes error messages, updates UI, stops recorder, cleans up media, stops webcam, and updates Electron state.
Microphone Fallback Recording Lifecycle
src/hooks/useScreenRecorder.ts
Fallback recorder stopping now performs track monitor cleanup and clears timing state; fallback audio track gets monitorTrackEnded handler; fallback recorder onerror surfaces toast with error messages and cleans up monitors on acquisition failure.
Recording State Reset and Initialization
src/hooks/useScreenRecorder.ts
startRecording resets interruption/error flags and clears media-track monitors to ensure fresh listener state for each new recording session.
Background Finalization Restructuring
src/hooks/useScreenRecorder.ts
Native finalization refactored into structured try/catch/finally: awaits webcam path, stores microphone sidecar, performs Windows muxing, updates session state, then closes HUD overlay in finalization step. Browser finalization adjusted with logging and recorder.onerror assignment to handleBrowserRecorderError.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • webadderallorg/Recordly#313: Modifies startRecording failure/cleanup flow—overlapping error-state reset and Electron recording state management at the same code paths.
  • webadderallorg/Recordly#259: Prior audio mixing and 48kHz AudioContext changes that this PR builds upon with lifecycle/error handling and node cleanup.
  • webadderallorg/Recordly#464: Modifies MediaRecorder stop/finalization flow—aligns with this PR's refinement of audio-track termination handling and media graph cleanup.

Suggested labels

Checked


🐰 Ah, the audio streams now heed the call,
With cleanup and errors both handled with care,
Listeners wake, then vanish in air,
Track interruptions no longer enthrall—
A lifecycle refined for recording's grand ball! 🎙️

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(recording): stop audio dropping out on long recordings' accurately summarizes the main objective and primary change in the PR, which is fixing audio dropout issues during longer recordings.
Description check ✅ Passed The PR description is comprehensive and exceeds template requirements: it includes problem statement, root causes with specific fixes, scope/safety details, and testing confirmation. However, the template's Type of Change, Screenshots/Video, and Testing Guide sections are not explicitly filled with checkboxes or structured formats.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

⚠️ This pull request has been flagged by Anti-Slop.
Our automated checks detected patterns commonly associated with
low-quality or automated/AI submissions (failure count reached).
No automatic closure — a maintainer will review it.
If this is legitimate work, please add more context, link issues, or ping us.

1 similar comment
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ This pull request has been flagged by Anti-Slop.
Our automated checks detected patterns commonly associated with
low-quality or automated/AI submissions (failure count reached).
No automatic closure — a maintainer will review it.
If this is legitimate work, please add more context, link issues, or ping us.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant