mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
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:
@@ -7,7 +7,7 @@ import { useTimelineSyncCallbacks } from "./useTimelineSyncCallbacks";
|
||||
// Re-export public API consumed by tests and external modules.
|
||||
// All of these were previously defined in this file; they now live in focused
|
||||
// sub-modules but are re-exported here so existing import sites don't change.
|
||||
export type { PlaybackAdapter, ClipManifestClip } from "../lib/playbackTypes";
|
||||
export type { ClipManifestClip } from "../lib/playbackTypes";
|
||||
export { createStaticSeekPlaybackAdapter } from "../lib/playbackAdapter";
|
||||
export {
|
||||
getTimelineElementSelector,
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
setPreviewPlaybackRate,
|
||||
shouldMutePreviewAudio,
|
||||
} from "../lib/timelineIframeHelpers";
|
||||
import { probeMediaUrl, getCachedProbe } from "../lib/mediaProbe";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Hook
|
||||
@@ -106,6 +107,31 @@ export function useTimelinePlayer() {
|
||||
if (!state.timelineReady) {
|
||||
setTimelineReady(true);
|
||||
}
|
||||
|
||||
// Asynchronously enrich media elements missing sourceDuration via mediabunny.
|
||||
// The probe reads file headers only — no full decode — so this is cheap.
|
||||
const needsProbe = mergedElements.filter(
|
||||
(el) =>
|
||||
el.src &&
|
||||
el.sourceDuration == null &&
|
||||
["video", "audio"].includes(el.tag.toLowerCase()) &&
|
||||
!getCachedProbe(el.src),
|
||||
);
|
||||
if (needsProbe.length > 0) {
|
||||
void Promise.allSettled(
|
||||
needsProbe.map(async (el) => {
|
||||
const result = await probeMediaUrl(el.src!);
|
||||
if (!result) return;
|
||||
const current = usePlayerStore.getState().elements;
|
||||
const key = el.key ?? el.id;
|
||||
const idx = current.findIndex((e) => (e.key ?? e.id) === key);
|
||||
if (idx === -1 || current[idx].sourceDuration != null) return;
|
||||
const patched = current.slice();
|
||||
patched[idx] = { ...current[idx], sourceDuration: result.duration };
|
||||
setElements(patched);
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
[setElements, setTimelineReady, setDuration],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user