From 30e7fc06cc6758b7c082fd6846ff63440a51e791 Mon Sep 17 00:00:00 2001 From: webadderall <131426131+webadderall@users.noreply.github.com> Date: Sun, 8 Nov 2026 13:31:04 +1100 Subject: [PATCH] Fix Recordly 2.0 part 3 follow-up review findings Preserve existing PR history and integrate validated current-cycle fixes. --- .../videoPlayback/usePreviewMediaState.ts | 6 +++++- src/hooks/recordingFileWriter.ts | 11 +++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/components/video-editor/videoPlayback/usePreviewMediaState.ts b/src/components/video-editor/videoPlayback/usePreviewMediaState.ts index 97695ab9..97597eb3 100644 --- a/src/components/video-editor/videoPlayback/usePreviewMediaState.ts +++ b/src/components/video-editor/videoPlayback/usePreviewMediaState.ts @@ -16,8 +16,13 @@ export function usePreviewMediaState( const videoRef = useRef(null); const previewVideoSourceRef = useRef(new PreviewVideoSource()); + const previewRenderLoopRef = useRef | null>(null); const attachVideo = useCallback((video: HTMLVideoElement | null) => { + if (video !== videoRef.current) { + // Ref detachment precedes passive cleanup. Stop sampling before the source is destroyed. + previewRenderLoopRef.current?.dispose(); + } // VideoSource.destroy() clears the media URL, so only destroy it when // React detaches the element, never during a layout effect cleanup. previewVideoSourceRef.current.setVideo(video); @@ -64,7 +69,6 @@ export function usePreviewMediaState( const lastRenderedContentTimeRef = useRef(null); - const previewRenderLoopRef = useRef | null>(null); const requestPreviewRender = useCallback(() => { previewRenderLoopRef.current?.requestRender(); }, []); diff --git a/src/hooks/recordingFileWriter.ts b/src/hooks/recordingFileWriter.ts index 1a7769cc..c4b14f88 100644 --- a/src/hooks/recordingFileWriter.ts +++ b/src/hooks/recordingFileWriter.ts @@ -12,6 +12,7 @@ export class RecordingFileWriter { private queue = Promise.resolve(); private streamId: string | undefined; private pendingBytes = 0; + private oversizedChunkBytes = 0; private failure: string | undefined; private cancelled = false; private path: string | undefined; @@ -28,8 +29,12 @@ export class RecordingFileWriter { } write(chunk: Blob) { if (this.cancelled || this.failure || !chunk.size) return; - // A single delayed recorder Blob drains in slices; bound additional queued data. - if (this.pendingBytes > 0 && this.pendingBytes + chunk.size > 32 * 1024 * 1024) { + const isOversizedChunk = this.pendingBytes === 0 && chunk.size > 32 * 1024 * 1024; + // One delayed Blob may drain in slices alongside up to 32 MiB of later data. + if ( + !isOversizedChunk && + this.pendingBytes - this.oversizedChunkBytes + chunk.size > 32 * 1024 * 1024 + ) { this.fail( new Error( "Recording storage cannot keep up. The partial recording will be preserved.", @@ -37,6 +42,7 @@ export class RecordingFileWriter { ); return; } + if (isOversizedChunk) this.oversizedChunkBytes = chunk.size; this.pendingBytes += chunk.size; this.queue = this.queue .then(async () => { @@ -68,6 +74,7 @@ export class RecordingFileWriter { .catch((error) => this.fail(error)) .finally(() => { this.pendingBytes -= chunk.size; + if (isOversizedChunk) this.oversizedChunkBytes = 0; }); } cancel() {