From 121bf20e99da6c514d7ae0a0fe166c4a12ae7209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Mon, 26 Oct 2026 17:27:26 +0000 Subject: [PATCH] perf(studio): a scrub's state messages re-read the preview's compositions at most every 500 ms (#6429) --- .../player/hooks/previewMessageRouter.test.ts | 34 +++++++++++++++++++ .../src/player/hooks/previewMessageRouter.ts | 17 ++++++++-- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/packages/studio/src/player/hooks/previewMessageRouter.test.ts b/packages/studio/src/player/hooks/previewMessageRouter.test.ts index 417525def0..5ccbc96b96 100644 --- a/packages/studio/src/player/hooks/previewMessageRouter.test.ts +++ b/packages/studio/src/player/hooks/previewMessageRouter.test.ts @@ -23,6 +23,40 @@ describe("preview state messages", () => { }); }); +describe("preview state messages while a scrub posts them", () => { + it("re-reads the preview for missing compositions at most every 500 ms", () => { + vi.useFakeTimers(); + try { + vi.setSystemTime(10_000); + const enrich = vi.fn(); + const handle = createPreviewMessageHandler({ + iframeRef: { current: null }, + processTimelineMessageRef: { current: vi.fn() }, + enrichMissingCompositionsRef: { current: enrich }, + lastTimelineMessageRef: { current: 0 }, + getAdapter: () => null, + syncTimelineElements: vi.fn(), + }); + const state = () => + handle( + new MessageEvent("message", { + data: { source: "hf-preview", type: "state", isPlaying: false }, + }), + ); + state(); + vi.advanceTimersByTime(100); + state(); + state(); + expect(enrich).toHaveBeenCalledTimes(1); + vi.advanceTimersByTime(500); + state(); + expect(enrich).toHaveBeenCalledTimes(2); + } finally { + vi.useRealTimers(); + } + }); +}); + describe("preview quality messages", () => { function routeMessage(data: unknown, frame?: { postMessage: (message: unknown) => void }) { const iframe = frame ? ({ contentWindow: frame } as unknown as HTMLIFrameElement) : null; diff --git a/packages/studio/src/player/hooks/previewMessageRouter.ts b/packages/studio/src/player/hooks/previewMessageRouter.ts index f0b70d3bdc..d9215404c3 100644 --- a/packages/studio/src/player/hooks/previewMessageRouter.ts +++ b/packages/studio/src/player/hooks/previewMessageRouter.ts @@ -69,15 +69,24 @@ function acceptedPreviewMessage( return acceptStudioRuntimeMessage(data) ? data : null; } +/** Whether enrichment is due: no timeline message and no enrichment of ours for 500 ms. Marks it run. */ +function enrichDue(lastTimelineMessage: number, enriched: { at: number }): boolean { + const now = Date.now(); + if (now - lastTimelineMessage <= 500 || now - enriched.at <= 500) return false; + enriched.at = now; + return true; +} + /** * A `state` tick doubles as a recovery hook: if the store still has no * elements, read the manifest straight off the iframe, and if no `timeline` - * message has arrived recently, re-run enrichment. + * message has arrived recently, re-run enrichment (at most every 500 ms: it queries the whole document). */ function handleStateMessage( deps: PreviewMessageRouterDeps, iframe: HTMLIFrameElement | null, playingHoldsClipListPosts: boolean, + enriched: { at: number }, ): void { try { if (usePlayerStore.getState().elements.length === 0) { @@ -86,7 +95,7 @@ function handleStateMessage( deps.processTimelineMessageRef.current(manifest); } } - if (!playingHoldsClipListPosts && Date.now() - deps.lastTimelineMessageRef.current > 500) { + if (!playingHoldsClipListPosts && enrichDue(deps.lastTimelineMessageRef.current, enriched)) { deps.enrichMissingCompositionsRef.current(); } } catch {} @@ -135,11 +144,13 @@ function applyStoreReport(data: PreviewMessage): boolean { export function createPreviewMessageHandler( deps: PreviewMessageRouterDeps, ): (e: MessageEvent) => void { + const enriched = { at: 0 }; return (e: MessageEvent) => { const iframe = deps.iframeRef.current; const data = acceptedPreviewMessage(e, iframe); if (!data) return; - if (data.type === "state") return handleStateMessage(deps, iframe, data.isPlaying === true); + if (data.type === "state") + return handleStateMessage(deps, iframe, data.isPlaying === true, enriched); if (applyStoreReport(data)) return; if (data.type === "ready") { const level = usePlayerStore.getState().previewQuality;