fix(studio): enable timeline resize for all elements, improve perf and UX

Enable trim-start and trim-end for all authored timeline elements (divs,
sections, compositions) — not just video/audio/img. The deterministic-window
gate was overly restrictive since all non-implicit elements have authored
data-start/data-duration that define their timeline window.

Replace iframe reload after resize/move with direct DOM attribute patching
via patchIframeDomTiming(). This eliminates playhead-jump-to-zero, visual
blinking, and race conditions from file-watcher echoes. File persistence
runs in a serialized background queue (persistTimelineEdit + enqueueEdit)
so rapid edits don't overwrite each other.

Add mediabunny-based media probe service (mediaProbe.ts) for fast metadata
extraction from file headers. Timeline elements missing sourceDuration are
enriched asynchronously without waiting for DOM loadedmetadata events.

Tune the runtime media preloader: lower lazy threshold from 6 to 3 clips,
add 3s lookbehind window for reverse scrub, adaptive promoted-clip cap.

Deduplicate getTimelineEditCapabilities — computed once in TimelineCanvas
and passed as a prop to TimelineClip instead of recomputing per clip.

Remove dead PlaybackAdapter re-export from useTimelinePlayer — all consumers
import directly from playbackTypes.
This commit is contained in:
Miguel Ángel
2026-05-19 20:40:39 -04:00
parent 4237165517
commit 89f9ca196d
13 changed files with 460 additions and 346 deletions
@@ -3,7 +3,7 @@ import type { TimelineTrackStyle } from "./timelineTheme";
import { memo, type ReactNode } from "react";
import type { TimelineElement } from "../store/playerStore";
import { defaultTimelineTheme, getClipHandleOpacity, type TimelineTheme } from "./timelineTheme";
import { getTimelineEditCapabilities } from "./timelineEditing";
import type { TimelineEditCapabilities } from "./timelineEditing";
interface TimelineClipProps {
el: TimelineElement;
@@ -13,6 +13,7 @@ interface TimelineClipProps {
isHovered: boolean;
isDragging?: boolean;
hasCustomContent: boolean;
capabilities: TimelineEditCapabilities;
theme?: TimelineTheme;
trackStyle: TimelineTrackStyle;
isComposition: boolean;
@@ -33,6 +34,7 @@ export const TimelineClip = memo(function TimelineClip({
isHovered,
isDragging = false,
hasCustomContent,
capabilities,
theme = defaultTimelineTheme,
trackStyle,
isComposition,
@@ -47,6 +49,7 @@ export const TimelineClip = memo(function TimelineClip({
const leftPx = el.start * pps;
const widthPx = Math.max(el.duration * pps, 4);
const handleOpacity = getClipHandleOpacity({ isHovered, isSelected, isDragging });
const borderColor = isSelected
? theme.clipBorderActive
: isHovered
@@ -59,7 +62,6 @@ export const TimelineClip = memo(function TimelineClip({
: isHovered
? theme.clipShadowHover
: theme.clipShadow;
const capabilities = getTimelineEditCapabilities(el);
const displayLabel = el.label || el.id || el.tag;
const showHandles = handleOpacity > 0.01;