Skip to content

fix: stream recording chunks to disk to support recordings longer than 10 minutes#617

Open
Amanuel2x wants to merge 1 commit into
siddharthvaddem:mainfrom
Amanuel2x:fix/streaming-chunks-to-disk
Open

fix: stream recording chunks to disk to support recordings longer than 10 minutes#617
Amanuel2x wants to merge 1 commit into
siddharthvaddem:mainfrom
Amanuel2x:fix/streaming-chunks-to-disk

Conversation

@Amanuel2x
Copy link
Copy Markdown

@Amanuel2x Amanuel2x commented May 19, 2026

Fixes #616

Problem

Recordings longer than ~10 minutes silently fail to save. The entire recording was buffered in the renderer as a Blob[] array, then on stop fixWebmDuration() + .arrayBuffer() + Buffer.from() created 3–4 additional in-memory copies before the data was written to disk. For an 18-minute 1080p recording this exceeds 400–600 MB in the renderer process, causing a silent crash with no file saved and no error shown.

Solution

Open an fs.WriteStream on the main process the moment recording starts, and pipe each 1-second ondataavailable chunk directly to disk via two new IPC calls (open-recording-stream, append-recording-chunk). On stop the stream is closed and the session manifest is written — the renderer never holds more than a single 1-second chunk in memory.

A full in-memory fallback is preserved for cases where the IPC stream fails to open.

Changes

  • src/hooks/useScreenRecorder.tscreateRecorderHandle streams chunks to disk via IPC instead of accumulating them in Blob[]; stop path skips arrayBuffer() when streaming succeeded
  • electron/ipc/handlers.ts — adds open-recording-stream and append-recording-chunk handlers; storeRecordedSessionFiles closes the write stream instead of calling writeFile when streaming was used
  • electron/preload.ts — exposes the two new IPC calls
  • electron/electron-env.d.ts — type declarations for the two new IPC calls

Testing

  • tsc --noEmit — no type errors
  • vitest --run — 151/151 tests passing

Summary by CodeRabbit

Release Notes

  • New Features
    • Implemented streaming-based recording architecture that writes video data directly to disk instead of buffering in memory, improving performance and reducing memory consumption during recording sessions.

Review Change Stack

Recordings longer than ~10 minutes would silently fail to save. The
entire recording was held in memory as a Blob[] array, then on stop
fixWebmDuration() and arrayBuffer() created additional copies before
the data was sent over IPC to be written. For an 18+ minute 1080p
recording this meant 400-600MB being duplicated 3-4x in the renderer
process, exceeding Electron's memory limits and causing a silent crash
with no file written to disk.

Fix: open an fs.WriteStream on the main process as soon as recording
starts, and send each 1-second ondataavailable chunk directly to disk
via IPC instead of accumulating chunks in RAM. On stop, the stream is
closed and the session manifest is written — no in-memory blob needed.
A full in-memory fallback is preserved for environments where the IPC
stream is unavailable.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

📝 Walkthrough

Walkthrough

This PR implements disk-backed recording streaming to prevent out-of-memory crashes for long recordings. Instead of buffering the entire WebM blob in renderer memory, chunks are now streamed directly to disk via IPC as they arrive, keeping memory usage constant regardless of recording duration.

Changes

Recording Stream Persistence

Layer / File(s) Summary
IPC Contract & Bridge
electron/electron-env.d.ts, electron/preload.ts
New renderer-callable IPC methods openRecordingStream(recordingId, fileName) and appendRecordingChunk(recordingId, chunk) are typed and exposed via preload bridge, returning { success, error? } promises.
Main Process Stream Handlers
electron/ipc/handlers.ts
activeWriteStreams map tracks per-recordingId write streams; new handlers open-recording-stream (creates fs write stream) and append-recording-chunk (writes ArrayBuffer chunks) support streaming video data directly to disk.
Session Storage with Stream Finalization
electron/ipc/handlers.ts
storeRecordedSessionFiles now closes active write streams keyed by createdAt (screen) and createdAt + 1 (webcam), falling back to buffer writes if streams weren't opened.
Renderer Streaming Implementation & Finalization
src/hooks/useScreenRecorder.ts
createRecorderHandle now accepts recordingId and fileName, opens an IPC stream on init, buffers chunks until ready, and appends subsequent chunks; returns a handle with streaming flag. finalizeRecording skips blob processing for streamed recordings and passes empty ArrayBuffer(0) placeholders to session storage.
Recording Call Site Updates
src/hooks/useScreenRecorder.ts
macOS webcam, browser screen, and browser webcam recording starts now pass recordingId and output fileName to createRecorderHandle, enabling streaming mode across all recording paths.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly Related PRs

  • siddharthvaddem/openscreen#352: Modifies recording session persistence in electron/ipc/handlers.ts around output path resolution; main PR adds streaming chunk handlers to the same area.
  • siddharthvaddem/openscreen#457: Updates src/hooks/useScreenRecorder.ts recording lifecycle with recordingId-based session/discard logic that overlaps with streaming implementation.

Suggested Reviewers

  • siddharthvaddem
  • FabLrc

Poem

🎬 chunks flow freely to disk,
renderer memory no longer at risk,
write streams open, append, and close—
long recordings now win, not lose. ✨

🚥 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 Title clearly identifies the fix (streaming chunks) and the specific problem solved (supporting longer recordings), directly reflecting the main change in the changeset.
Description check ✅ Passed Description covers all required sections: problem statement, solution approach, detailed file-by-file changes, and testing results. Includes linked issue reference (#616) and type/test verification.
Linked Issues check ✅ Passed All coding objectives from #616 are met: renderer streams chunks via IPC instead of buffering [useScreenRecorder.ts], main process writes directly to disk [ipc/handlers.ts], fallback preserves in-memory behavior, and renderer holds only single 1-second chunks.
Out of Scope Changes check ✅ Passed All four files modified (electron-env.d.ts, ipc/handlers.ts, preload.ts, useScreenRecorder.ts) directly support the streaming-to-disk objective with no extraneous changes observed.

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

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/hooks/useScreenRecorder.ts (1)

117-119: 💤 Low value

nit: chunk append errors are silently ignored.

if appendRecordingChunk fails mid-recording (disk full, stream error), the promise rejection is swallowed by void. the recording continues but data is lost with no feedback.

lowkey risky for long recordings — at minimum consider logging failures so they're debuggable. could also set a flag to trigger fallback behavior.

Also applies to: 142-143

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useScreenRecorder.ts` around lines 117 - 119, The code currently
uses "void window.electronAPI.appendRecordingChunk(recordingId, chunk)" inside
the pendingChunks loop (and similarly at lines ~142-143), which swallows promise
rejections and hides failures; update the logic in useScreenRecorder.ts to await
or handle appendRecordingChunk's promise and surface errors: wrap the per-chunk
calls (or the Promise.all of them) in try/catch or attach .catch handlers that
log the error (e.g., console.error or an existing logger) and set a recording
error flag/state (e.g., recordingFailed or setRecordingError) so the UI or
fallback behavior can react when appendRecordingChunk(recordingId, chunk) fails;
ensure you update both the pendingChunks loop and the later occurrence that uses
appendRecordingChunk.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@electron/ipc/handlers.ts`:
- Around line 2081-2089: The handler that opens a write stream (using
resolveRecordingOutputPath, createWriteStream and activeWriteStreams with
recordingId) can fail with ENOENT if the recordings directory doesn't exist;
before creating the write stream call fs.mkdir (or fs.promises.mkdir) on the
parent directory of resolveRecordingOutputPath(fileName) with { recursive: true
} (await if using promises) inside the try block, then create the stream and set
activeWriteStreams; keep the existing try/catch and return shape but ensure the
directory creation is awaited and errors propagate into the existing error
return.
- Around line 2130-2141: The finalize path leaks file streams when a recording
is discarded: update finalizeScreenRecording (and/or cancelRecording flow) to
explicitly close and cleanup any activeWriteStreams entry for the recordingId
(use the same logic as in the existing write/close block that calls screenWs.end
and activeWriteStreams.delete) before bailing out, and also remove any partial
screenVideoPath (or ensure stream end is awaited) so open handles and orphaned
.webm files are cleaned up; reference activeWriteStreams,
finalizeScreenRecording, cancelRecording, discardRecordingId.current,
storeRecordedSession and discardCursorTelemetry when adding this cleanup or
creating a dedicated IPC abortRecording handler.

In `@src/hooks/useScreenRecorder.ts`:
- Around line 104-166: The returned streaming flag is captured before
streamOpenPromise resolves, causing a race and data loss; fix by determining
streaming only when recording stops and exposing that final value (or including
it with the resolved blob). Concretely, update recorder.onstop to set a
finalStreaming boolean (based on streamFailed) before resolving
recordedBlobPromise, and change the hook's return value to either provide a
getter/function for streaming or have recordedBlobPromise resolve to an object
like { blob, streaming }; then update finalizeRecording to consume the final
streaming state from that getter/object instead of reading the early-captured
streaming variable. Ensure you touch streamOpenPromise,
streamFailed/streamReady, recorder.onstop, recordedBlobPromise, and the place
that checks activeScreenRecorder.streaming in finalizeRecording.

---

Nitpick comments:
In `@src/hooks/useScreenRecorder.ts`:
- Around line 117-119: The code currently uses "void
window.electronAPI.appendRecordingChunk(recordingId, chunk)" inside the
pendingChunks loop (and similarly at lines ~142-143), which swallows promise
rejections and hides failures; update the logic in useScreenRecorder.ts to await
or handle appendRecordingChunk's promise and surface errors: wrap the per-chunk
calls (or the Promise.all of them) in try/catch or attach .catch handlers that
log the error (e.g., console.error or an existing logger) and set a recording
error flag/state (e.g., recordingFailed or setRecordingError) so the UI or
fallback behavior can react when appendRecordingChunk(recordingId, chunk) fails;
ensure you update both the pendingChunks loop and the later occurrence that uses
appendRecordingChunk.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1f44ef88-365b-4fb5-b52e-82313de502a5

📥 Commits

Reviewing files that changed from the base of the PR and between 00fbd95 and 2f60df5.

📒 Files selected for processing (4)
  • electron/electron-env.d.ts
  • electron/ipc/handlers.ts
  • electron/preload.ts
  • src/hooks/useScreenRecorder.ts

Comment thread electron/ipc/handlers.ts
Comment on lines +2081 to +2089
try {
const filePath = resolveRecordingOutputPath(fileName);
const ws = createWriteStream(filePath, { flags: "w" });
activeWriteStreams.set(recordingId, ws);
return { success: true };
} catch (error) {
return { success: false, error: String(error) };
}
},
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing directory creation before opening write stream.

createWriteStream will throw ENOENT if RECORDINGS_DIR doesn't exist. Native recording flows call fs.mkdir(RECORDINGS_DIR, { recursive: true }) before starting, but the browser-based recording flow doesn't — it relies on this handler being called first.

🐛 Proposed fix
 	ipcMain.handle(
 		"open-recording-stream",
 		async (
 			_,
 			recordingId: number,
 			fileName: string,
 		): Promise<{ success: boolean; error?: string }> => {
 			try {
+				await fs.mkdir(RECORDINGS_DIR, { recursive: true });
 				const filePath = resolveRecordingOutputPath(fileName);
 				const ws = createWriteStream(filePath, { flags: "w" });
 				activeWriteStreams.set(recordingId, ws);
 				return { success: true };
 			} catch (error) {
 				return { success: false, error: String(error) };
 			}
 		},
 	);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
const filePath = resolveRecordingOutputPath(fileName);
const ws = createWriteStream(filePath, { flags: "w" });
activeWriteStreams.set(recordingId, ws);
return { success: true };
} catch (error) {
return { success: false, error: String(error) };
}
},
try {
await fs.mkdir(RECORDINGS_DIR, { recursive: true });
const filePath = resolveRecordingOutputPath(fileName);
const ws = createWriteStream(filePath, { flags: "w" });
activeWriteStreams.set(recordingId, ws);
return { success: true };
} catch (error) {
return { success: false, error: String(error) };
}
},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@electron/ipc/handlers.ts` around lines 2081 - 2089, The handler that opens a
write stream (using resolveRecordingOutputPath, createWriteStream and
activeWriteStreams with recordingId) can fail with ENOENT if the recordings
directory doesn't exist; before creating the write stream call fs.mkdir (or
fs.promises.mkdir) on the parent directory of
resolveRecordingOutputPath(fileName) with { recursive: true } (await if using
promises) inside the try block, then create the stream and set
activeWriteStreams; keep the existing try/catch and return shape but ensure the
directory creation is awaited and errors propagate into the existing error
return.

Comment thread electron/ipc/handlers.ts
Comment on lines +2130 to +2141

// Close the streaming write stream if one was used; otherwise fall back to
// writing the full buffer (short recordings that never opened a stream).
const screenWs = activeWriteStreams.get(createdAt);
if (screenWs) {
await new Promise<void>((resolve, reject) =>
screenWs.end((err?: Error | null) => (err ? reject(err) : resolve())),
);
activeWriteStreams.delete(createdAt);
} else if (payload.screen.videoData && payload.screen.videoData.byteLength > 0) {
await fs.writeFile(screenVideoPath, Buffer.from(payload.screen.videoData));
}
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.

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if discardCursorTelemetry is the only cleanup called on discard
rg -n "discardCursorTelemetry" --type ts -C 3

Repository: siddharthvaddem/openscreen

Length of output: 1541


🏁 Script executed:

#!/bin/bash
# Find the discard-cursor-telemetry handler in electron handlers
rg -n "discard-cursor-telemetry" --type ts -A 15 electron/

Repository: siddharthvaddem/openscreen

Length of output: 1152


🏁 Script executed:

#!/bin/bash
# Also check what happens with activeWriteStreams when recording is discarded
rg -n "activeWriteStreams" --type ts -B 3 -A 3

Repository: siddharthvaddem/openscreen

Length of output: 3504


🏁 Script executed:

#!/bin/bash
# Check if there's a cancel/cleanup handler for recordings
rg -n "cancelRecording\|abort.*[Rr]ecord" --type ts -A 5

Repository: siddharthvaddem/openscreen

Length of output: 52


🏁 Script executed:

#!/bin/bash
# Search for the discard-cursor-telemetry handler definition
rg -n '"discard-cursor-telemetry"' electron/ipc/handlers.ts -A 20

Repository: siddharthvaddem/openscreen

Length of output: 52


🏁 Script executed:

#!/bin/bash
# Also check the structure around the storeRecordedSessionFiles handler to understand the flow better
rg -n '"store-recorded-session"' electron/ipc/handlers.ts -B 5 -A 3

Repository: siddharthvaddem/openscreen

Length of output: 313


🏁 Script executed:

#!/bin/bash
# Search for discard-cursor-telemetry in all files
rg -n '"discard-cursor-telemetry"' . --type ts

Repository: siddharthvaddem/openscreen

Length of output: 166


🏁 Script executed:

#!/bin/bash
# Check the useScreenRecorder hook to see full flow on discard
rg -n "discardRecordingId.current" src/hooks/useScreenRecorder.ts -B 5 -A 10

Repository: siddharthvaddem/openscreen

Length of output: 5815


Streams definitely leak when recording is cancelled/discarded.

When cancelRecording is called, it sets discardRecordingId.current and triggers finalization. In finalizeScreenRecording, if the recording is being discarded (line 416-418 in useScreenRecorder.ts), it bails out early without calling storeRecordedSession. That handler is the only place activeWriteStreams gets cleaned up—so the write streams stay open indefinitely, and partial .webm files are orphaned on disk.

The discardCursorTelemetry call looks like an incomplete attempt at cleanup; the handler doesn't even exist in the codebase.

You'll need either:

  1. A dedicated IPC to abort/cleanup a recording stream by recordingId, or
  2. Close streams directly when discard happens (maybe in a new handler or as part of the same finalize flow)

Without this, cancelled recordings will accumulate open file handles and disk clutter.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@electron/ipc/handlers.ts` around lines 2130 - 2141, The finalize path leaks
file streams when a recording is discarded: update finalizeScreenRecording
(and/or cancelRecording flow) to explicitly close and cleanup any
activeWriteStreams entry for the recordingId (use the same logic as in the
existing write/close block that calls screenWs.end and
activeWriteStreams.delete) before bailing out, and also remove any partial
screenVideoPath (or ensure stream end is awaited) so open handles and orphaned
.webm files are cleaned up; reference activeWriteStreams,
finalizeScreenRecording, cancelRecording, discardRecordingId.current,
storeRecordedSession and discardCursorTelemetry when adding this cleanup or
creating a dedicated IPC abortRecording handler.

Comment on lines +104 to +166

// Try to open a disk-backed stream. Falls back to in-memory if IPC unavailable.
const streamOpenPromise =
window.electronAPI?.openRecordingStream?.(recordingId, fileName) ??
Promise.resolve({ success: false });

const pendingChunks: ArrayBuffer[] = [];
let streamReady = false;
let streamFailed = false;

streamOpenPromise.then((result) => {
if (result.success) {
streamReady = true;
for (const chunk of pendingChunks) {
void window.electronAPI.appendRecordingChunk(recordingId, chunk);
}
pendingChunks.length = 0;
} else {
streamFailed = true;
}
});

const fallbackChunks: Blob[] = [];

const recordedBlobPromise = new Promise<Blob>((resolve, reject) => {
recorder.ondataavailable = (event: BlobEvent) => {
if (event.data && event.data.size > 0) {
chunks.push(event.data);
if (!event.data || event.data.size === 0) return;

if (streamFailed) {
fallbackChunks.push(event.data);
return;
}

void event.data.arrayBuffer().then((buf) => {
if (streamFailed) {
fallbackChunks.push(new Blob([buf], { type: mimeType }));
return;
}
if (streamReady) {
void window.electronAPI.appendRecordingChunk(recordingId, buf);
} else {
pendingChunks.push(buf);
}
});
};

recorder.onerror = () => {
reject(new Error("Recording failed"));
};

recorder.onstop = () => {
resolve(new Blob(chunks, { type: mimeType }));
if (streamFailed) {
// Streaming failed — return full in-memory blob as fallback.
resolve(new Blob(fallbackChunks, { type: mimeType }));
} else {
// Streaming succeeded — main process already has the data on disk.
resolve(new Blob([], { type: mimeType }));
}
};
});

recorder.start(RECORDER_TIMESLICE_MS);
return { recorder, recordedBlobPromise };
return { recorder, recordedBlobPromise, streaming: !streamFailed };
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.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Race condition: streaming flag doesn't reflect actual outcome.

The streaming flag is captured at line 166 as !streamFailed, but at that point the streamOpenPromise hasn't resolved yet — so streamFailed is always false and streaming is always true initially.

If the IPC stream fails to open (permissions, disk error, etc.), the fallback logic in ondataavailable correctly buffers to fallbackChunks, and onstop correctly returns the fallback blob. But then in finalizeRecording, the check at line 430:

if (!activeScreenRecorder.streaming && screenBlob.size > 0) {

...is false because streaming was captured as true. The in-memory data gets thrown away and an empty ArrayBuffer(0) is sent instead, resulting in data loss.

kinda cursed timing issue — maybe make streaming a getter that checks the final state, or resolve the promise before returning.

🐛 One possible fix: defer flag evaluation to onstop
-	const fallbackChunks: Blob[] = [];
+	let usedFallback = false;
+	const fallbackChunks: Blob[] = [];

 	const recordedBlobPromise = new Promise<Blob>((resolve, reject) => {
 		recorder.ondataavailable = (event: BlobEvent) => {
 			if (!event.data || event.data.size === 0) return;

 			if (streamFailed) {
 				fallbackChunks.push(event.data);
+				usedFallback = true;
 				return;
 			}

 			void event.data.arrayBuffer().then((buf) => {
 				if (streamFailed) {
 					fallbackChunks.push(new Blob([buf], { type: mimeType }));
+					usedFallback = true;
 					return;
 				}
 				// ... rest unchanged
 			});
 		};

 		recorder.onstop = () => {
-			if (streamFailed) {
+			if (usedFallback) {
 				resolve(new Blob(fallbackChunks, { type: mimeType }));
 			} else {
 				resolve(new Blob([], { type: mimeType }));
 			}
 		};
 	});

 	recorder.start(RECORDER_TIMESLICE_MS);
-	return { recorder, recordedBlobPromise, streaming: !streamFailed };
+	// streaming is determined by whether we fell back to in-memory
+	// We need to expose this info; simplest: check blob size in finalizeRecording
+	// OR return a promise/getter. For now, returning true and relying on blob.size check.
+	return { recorder, recordedBlobPromise, streaming: true };

Then update finalizeRecording to check blob size regardless of streaming flag:

-if (!activeScreenRecorder.streaming && screenBlob.size === 0) {
+if (screenBlob.size === 0 && !activeScreenRecorder.streaming) {
   return;
 }
+// If blob has data, always process it (fallback case)
+let screenVideoData: ArrayBuffer = new ArrayBuffer(0);
+if (screenBlob.size > 0) {
+  const fixedScreenBlob = await fixWebmDuration(screenBlob, duration);
+  screenVideoData = await fixedScreenBlob.arrayBuffer();
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useScreenRecorder.ts` around lines 104 - 166, The returned
streaming flag is captured before streamOpenPromise resolves, causing a race and
data loss; fix by determining streaming only when recording stops and exposing
that final value (or including it with the resolved blob). Concretely, update
recorder.onstop to set a finalStreaming boolean (based on streamFailed) before
resolving recordedBlobPromise, and change the hook's return value to either
provide a getter/function for streaming or have recordedBlobPromise resolve to
an object like { blob, streaming }; then update finalizeRecording to consume the
final streaming state from that getter/object instead of reading the
early-captured streaming variable. Ensure you touch streamOpenPromise,
streamFailed/streamReady, recorder.onstop, recordedBlobPromise, and the place
that checks activeScreenRecorder.streaming in finalizeRecording.

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.

Recordings longer than ~10 minutes silently fail to save

1 participant