Skip to content

fix(recording): stream the native webcam to disk instead of losing it - #259

Merged
EtienneLescot merged 1 commit into
release/v1.9.0from
fix/native-webcam-stream-to-disk
Aug 4, 2026
Merged

fix(recording): stream the native webcam to disk instead of losing it#259
EtienneLescot merged 1 commit into
release/v1.9.0from
fix/native-webcam-stream-to-disk

Conversation

@EtienneLescot

@EtienneLescot EtienneLescot commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Fixes #253.

The bug

On the macOS and Linux native capture paths the webcam handle is built with no file name, which selects in-memory buffering — nothing reaches disk during capture. Finalize then has to flatten the whole clip into a single ArrayBuffer to hand it across IPC. Past ~2 GB (roughly 20 minutes at BITRATE_BASE) that allocation throws.

The throw was swallowed twice:

  1. fixWebmDuration catches its own failure and returns the unpatched blob — WebmFile.fromBlob does a full-blob FileReader.readAsArrayBuffer, so it dies first and silently.
  2. The finalize catch logged to console and returned undefined, which made the if (webcamAsset && result.path) guard skip the attach entirely.

The session was written screen-only and the editor opened as if nothing had happened, camera simply absent. No toast, no failure state.

One correction to the issue's diagnosis: this is not memory exhaustion. Chromium handled the buffering correctly and spilled the ~1380 one-second blobs to blob_storage — that's what the reporter's 2149 MB disk-write warning actually shows. What has a hard ceiling is the mandatory flatten. The old design needed a contiguous multi-GB allocation four times over: the FileReader read, .arrayBuffer(), the structured clone through ipcRenderer.invoke, and Buffer.from() in the main process.

Windows is unaffected — its helper owns webcam capture directly.

The fix

Pass the webcam file name on both native paths so chunks stream to disk as they arrive, the way the legacy path and the Windows helper already do. All the plumbing existed (RecordingStreamRegistry, finalizeRecordingFile, repairRecordingContainer) — the native paths just weren't wired to it.

  • Finalize branches on isStreaming(): a streamed clip hands over its name alone; the main process closes the stream and patches the WebM duration on disk. Nothing multi-gigabyte is flattened or crosses IPC. Buffered short takes keep the existing behaviour.
  • Every failure now reaches the user as a toast, with a reason. Both the catch and the size === 0 early return used to exit without saying anything.
  • Partial files are cleaned up. Now that bytes land on disk during capture, a webcam stream not folded into a saved session gets closed and its file removed, or a discarded/failed take orphans a half-written .webm.
  • The macOS and Linux finalizers were line-for-line copies, so the shared logic moved into finalizeWebcamAsset() instead of being duplicated a second time.

Tests

Six new cases in src/hooks/webcamAsset.test.ts, following the webcamOffset.test.ts convention. Verified they have teeth: removing the isStreaming() branch fails two of them.

Full suite: 1625 passing. The 6 failures in electron/recording/webm-seek-index.test.ts are pre-existing and unrelated — that suite asserts remux behaviour behind a process.platform !== "linux" guard, so it only passes on Linux (confirmed failing identically on a clean tree).

What I could not verify

The streaming path is exercised by unit tests, not by a real >2 GB recording — I have no way to run a 20-minute webcam capture here. Worth one manual long take on macOS before release.

On the macOS and Linux native capture paths the webcam handle was built
with no file name, which selects in-memory buffering. Nothing reached
disk during capture, and finalize had to flatten the whole clip into one
ArrayBuffer to hand it across IPC. Past ~2GB — a take of roughly 20
minutes at BITRATE_BASE — that allocation throws.

The throw was swallowed twice over: fixWebmDuration catches its own
FileReader failure and returns the unpatched blob, then the finalize
catch logged to console and returned undefined, which made the attach
guard skip attachNative*WebcamRecording entirely. The session was
written screen-only and the editor opened as if nothing had happened,
with the camera simply absent. A 23-minute take lost its webcam this
way; shorter takes in the same app session saved fine.

Pass the webcam file name on both native paths so chunks stream to disk
as they arrive, the way the legacy path and the Windows helper already
do. Finalize now branches on isStreaming(): a streamed clip hands over
its name alone and the main process closes the stream and patches the
WebM duration on disk, so nothing multi-gigabyte is ever flattened or
sent across IPC. Buffered short takes keep the existing behaviour.

Every failure now comes back with a reason and reaches the user as a
toast. Silently discarding a completed take is the worst available
outcome, and it was the one that shipped.

Because the bytes now land on disk during capture, a webcam stream that
isn't folded into a saved session is closed and its partial file removed
— otherwise a discarded or failed take orphans a half-written .webm.

The macOS and Linux finalizers were line-for-line copies, so the shared
logic moves into finalizeWebcamAsset() rather than being duplicated
again. Its tests pin both halves of the fix: that a streamed clip is
never read into memory, and that a failure is never silent.

Fixes #253
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: dfa95a46-75fe-4710-a1dc-42a3c8c37dc3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@EtienneLescot
EtienneLescot merged commit a3cda04 into release/v1.9.0 Aug 4, 2026
12 checks passed
@EtienneLescot
EtienneLescot deleted the fix/native-webcam-stream-to-disk branch August 4, 2026 22:02
@lorenjphillips

Copy link
Copy Markdown

thank you!

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