From 2ae63e9289125a0742a98d01e2166656e5e117b3 Mon Sep 17 00:00:00 2001 From: Vagventure Date: Wed, 20 May 2026 12:31:59 +0530 Subject: [PATCH 1/6] feat: add asynchronous audio waveform canvas rendering to trim timeline Signed-off-by: Vagventure --- src/components/TrimControl.tsx | 21 ++++++- src/components/VideoEditor.tsx | 11 ++-- src/components/WaveformCanvas.tsx | 95 +++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 7 deletions(-) create mode 100644 src/components/WaveformCanvas.tsx diff --git a/src/components/TrimControl.tsx b/src/components/TrimControl.tsx index a4a03d19..505ea499 100644 --- a/src/components/TrimControl.tsx +++ b/src/components/TrimControl.tsx @@ -4,20 +4,26 @@ import { EditRecipe } from "@/lib/types"; import { useState, useEffect } from "react"; import { AlertCircle } from "lucide-react"; import { formatDuration } from "@/lib/utils"; +import { useAudioWaveform } from "@/hooks/useAudioWaveform"; +import WaveformCanvas from "@/components/WaveformCanvas"; interface Props { recipe: EditRecipe; onChange: (patch: Partial) => void; duration: number; + file: File | null; } -export default function TrimControl({ recipe, onChange, duration }: Props) { +export default function TrimControl({ recipe, onChange, duration, file }: Props) { const [invalidStart, setStart] = useState(false); const [invalidEnd, setEnd] = useState(false); const [startErrorMsg, setStartErrorMsg] = useState(""); const [endErrorMsg, setEndErrorMsg] = useState(""); const [startInput, setStartInput] = useState(recipe.trimStart.toString()); + const { waveform, isLoading: waveformLoading } = useAudioWaveform(file); + const hasAudio = waveform.length > 0; + useEffect(() => { setStartInput(recipe.trimStart.toString()); }, [recipe.trimStart]); @@ -113,6 +119,17 @@ export default function TrimControl({ recipe, onChange, duration }: Props) { return (
+ {/* Waveform — shown while loading or when file is present */} + {(file && (waveformLoading || hasAudio)) && ( +
+ +
+ )} +