From e32bc9fea641c3dc9c7b06ee627ebd407fab8e60 Mon Sep 17 00:00:00 2001 From: webadderall <131426131+webadderall@users.noreply.github.com> Date: Wed, 8 Apr 2026 22:00:28 +1000 Subject: [PATCH] feat(zoom): merge nearby auto zoom suggestions within 1.5s Suggested zoom regions with gaps <= 1500ms are merged into single contiguous spans before being added to the timeline. --- .../video-editor/timeline/TimelineEditor.tsx | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/components/video-editor/timeline/TimelineEditor.tsx b/src/components/video-editor/timeline/TimelineEditor.tsx index 59c7935c..3d09a0dd 100644 --- a/src/components/video-editor/timeline/TimelineEditor.tsx +++ b/src/components/video-editor/timeline/TimelineEditor.tsx @@ -35,6 +35,7 @@ const AUDIO_ROW_ID = "row-audio"; const FALLBACK_RANGE_MS = 1000; const TARGET_MARKER_COUNT = 12; const SUGGESTION_SPACING_MS = 1800; +const MERGE_NEARBY_GAP_MS = 1500; interface TimelineEditorProps { videoDuration: number; @@ -1090,8 +1091,7 @@ export default function TimelineEditor({ const sortedCandidates = [...dwellCandidates].sort((a, b) => b.strength - a.strength); const acceptedCenters: number[] = []; - - let addedCount = 0; + const accepted: { start: number; end: number; focus: ZoomFocus }[] = []; sortedCandidates.forEach((candidate) => { const tooCloseToAccepted = acceptedCenters.some( @@ -1115,18 +1115,33 @@ export default function TimelineEditor({ reservedSpans.push({ start: candidateStart, end: candidateEnd }); acceptedCenters.push(candidate.centerTimeMs); - onZoomSuggested({ start: candidateStart, end: candidateEnd }, candidate.focus); - addedCount += 1; + accepted.push({ start: candidateStart, end: candidateEnd, focus: candidate.focus }); }); - if (addedCount === 0) { + // Merge nearby accepted regions (gap ≤ MERGE_NEARBY_GAP_MS) into single spans + const sorted = [...accepted].sort((a, b) => a.start - b.start); + const merged: typeof sorted = []; + for (const region of sorted) { + const prev = merged[merged.length - 1]; + if (prev && region.start - prev.end <= MERGE_NEARBY_GAP_MS) { + prev.end = Math.max(prev.end, region.end); + } else { + merged.push({ ...region }); + } + } + + if (merged.length === 0) { toast.info("No auto-zoom slots available", { description: "Detected dwell points overlap existing zoom regions.", }); return; } - toast.success(`Added ${addedCount} interaction-based zoom suggestion${addedCount === 1 ? "" : "s"}`); + for (const region of merged) { + onZoomSuggested({ start: region.start, end: region.end }, region.focus); + } + + toast.success(`Added ${merged.length} interaction-based zoom suggestion${merged.length === 1 ? "" : "s"}`); }, [ videoDuration, totalMs,