From ed8eea14b0c8beefd964485a74027ef3ca9e6ed0 Mon Sep 17 00:00:00 2001 From: smirk-dev Date: Sun, 7 Jun 2026 09:37:45 +0530 Subject: [PATCH] fix: bound waveform extraction memory usage for large video files Waveform extraction called file.arrayBuffer() on the full uploaded video regardless of size. For uploads up to the 2GB cap this allocated multiple gigabytes (raw buffer + decoded PCM), spiking the heap and crashing browser tabs before editing began. - Add MAX_WAVEFORM_FILE_SIZE_BYTES (300MB) config in src/lib/constants.ts. - Refactor useAudioWaveform to a discriminated status union (idle | loading | ready | disabled | error). Files over the threshold are never read into memory; within the threshold audio is read through a bounded Blob.slice so decodeAudioData never receives more than the threshold's bytes. Small files are unchanged. - Strengthen cancellation: in-flight reads/decodes bail out on unmount or file change before creating an AudioContext or setting state. - Wire WaveformCanvas into TrimControl and render a placeholder + helper text when extraction is disabled for large files. - Add tests for threshold behaviour, the no-full-read guarantee, custom thresholds, decode errors, and unmount cancellation. Closes #1013 Co-Authored-By: Claude Opus 4.8 (1M context) --- src/components/TrimControl.tsx | 30 +++- src/components/__tests__/TrimControl.test.tsx | 78 ++++++++++ src/hooks/__tests__/useAudioWaveform.test.tsx | 133 ++++++++++++++++++ src/hooks/useAudioWaveform.ts | 73 ++++++++-- src/lib/constants.ts | 13 ++ 5 files changed, 312 insertions(+), 15 deletions(-) create mode 100644 src/components/__tests__/TrimControl.test.tsx create mode 100644 src/hooks/__tests__/useAudioWaveform.test.tsx diff --git a/src/components/TrimControl.tsx b/src/components/TrimControl.tsx index d48bdd69..a66d0fc5 100644 --- a/src/components/TrimControl.tsx +++ b/src/components/TrimControl.tsx @@ -25,7 +25,11 @@ export default function TrimControl({ recipe, onChange, duration, file }: Props) recipe.trimStart.toString() ); - const { waveform, isLoading: waveformLoading } = useAudioWaveform(file); + const { + waveform, + status: waveformStatus, + isLoading: waveformLoading, + } = useAudioWaveform(file); const hasAudio = waveform.length > 0; useEffect(() => { @@ -235,6 +239,30 @@ export default function TrimControl({ recipe, onChange, duration, file }: Props) /> )} + + {file && ( +
+ {waveformStatus === "disabled" ? ( +
+

+ Waveform preview is disabled for very large files to keep the + editor responsive. +

+
+ ) : ( + + )} +
+ )} +