mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 14:50:02 +00:00
perf(studio): grow composition duration live on extend, no preview remount
Extending a clip past the video end used to force the server-fallback path that fully remounts the preview iframe (the SDK fast path can't express the root composition's data-duration, and the runtime bakes+drops data-duration at load so it can't be patched live). On a large comp that remount is a visible hitch. Add a runtime control-bridge action set-root-duration -> clock.setDuration, so the studio can grow the transport length in place. On an extend the studio now posts it (and patches the clip's own timing live) instead of reloading; it only reloads when a GSAP source rewrite actually happened (the gsap-mutation endpoints now report a mutated flag). Non-animated extends — the common case — commit as fast as a normal edit. Verified: bridge dispatch + studio no-reload/post-message paths unit- tested; core/studio/studio-server typecheck + suites green; the built runtime artifact carries the handler; E2E confirms the extend no longer remounts the preview and still persists.
This commit is contained in:
@@ -1852,6 +1852,7 @@ export function initSandboxRuntimeModular(): void {
|
||||
// transport tick. A plain count misses same-count swaps (one sub-comp unloads
|
||||
// as another loads), so the signature keys on id+tag in document order.
|
||||
let clipTreeSignature = "";
|
||||
let liveRootDurationOverrideSeconds = 0;
|
||||
const computeClipTreeSignature = (): string => {
|
||||
let sig = "";
|
||||
for (const el of document.querySelectorAll("[data-start]")) {
|
||||
@@ -1904,6 +1905,30 @@ export function initSandboxRuntimeModular(): void {
|
||||
scheduleRootStageLayoutDiagnostics();
|
||||
};
|
||||
|
||||
const finitePositiveDuration = (value: number): number =>
|
||||
Number.isFinite(value) && value > 0 ? value : 0;
|
||||
|
||||
const growRootDurationLive = (durationSeconds: number) => {
|
||||
const nextDuration = finitePositiveDuration(Number(durationSeconds));
|
||||
if (nextDuration <= 0) return;
|
||||
const rootEl = resolveRootCompositionElement();
|
||||
const rootAttrDuration = finitePositiveDuration(
|
||||
Number.parseFloat(rootEl?.getAttribute("data-duration") ?? ""),
|
||||
);
|
||||
const currentDuration = Math.max(
|
||||
liveRootDurationOverrideSeconds,
|
||||
finitePositiveDuration(clock.getDuration()),
|
||||
rootAttrDuration,
|
||||
);
|
||||
if (nextDuration <= currentDuration) return;
|
||||
|
||||
liveRootDurationOverrideSeconds = nextDuration;
|
||||
rootEl?.setAttribute("data-duration", String(nextDuration));
|
||||
clock.setDuration(nextDuration);
|
||||
postTimeline();
|
||||
postState(true);
|
||||
};
|
||||
|
||||
const runAdapters = (method: "discover" | "pause" | "play", timeSeconds = 0) => {
|
||||
for (const adapter of state.deterministicAdapters) {
|
||||
try {
|
||||
@@ -2193,6 +2218,7 @@ export function initSandboxRuntimeModular(): void {
|
||||
if (state.transportClock) state.transportClock.setRate(state.playbackRate);
|
||||
applyWebAudioRate();
|
||||
},
|
||||
onSetRootDuration: growRootDurationLive,
|
||||
onSetColorGrading: (target, grading) => {
|
||||
colorGrading.setGrading(target, grading);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user