From 3af9a7df2d687ec018f7d0705d25b5416c8dfa78 Mon Sep 17 00:00:00 2001 From: ukimsanov Date: Mon, 27 Jul 2026 22:35:09 -0700 Subject: [PATCH] refactor(studio): reuse player probe errors --- .../studio/src/components/nle/NLEContext.tsx | 21 ++--- .../studio/src/components/nle/NLEPreview.tsx | 6 +- .../src/player/components/Player.test.ts | 21 +++-- .../studio/src/player/components/Player.tsx | 7 +- .../hooks/useTimelinePlayer.seek.test.ts | 33 ------- .../player/hooks/useTimelineSyncCallbacks.ts | 91 +++++++++---------- scripts/studio-runtime-smoke.mjs | 7 +- 7 files changed, 67 insertions(+), 119 deletions(-) diff --git a/packages/studio/src/components/nle/NLEContext.tsx b/packages/studio/src/components/nle/NLEContext.tsx index 55b96a092..5bdece645 100644 --- a/packages/studio/src/components/nle/NLEContext.tsx +++ b/packages/studio/src/components/nle/NLEContext.tsx @@ -32,7 +32,7 @@ export interface NLEContextValue { togglePlay: () => void; seek: (time: number, options?: { keepPlaying?: boolean }) => boolean; refreshPlayer: () => void; - onIframeLoad: (reportError?: (message: string) => void) => void; + onIframeLoad: () => void; // composition stack (from useCompositionStack) compositionStack: CompositionLevel[]; updateCompositionStack: React.Dispatch>; @@ -122,17 +122,14 @@ export function NLEProvider({ refreshPlayer(); }, [refreshKey, refreshPlayer]); - const onIframeLoad = useCallback( - (reportError?: (message: string) => void) => { - baseOnIframeLoad(reportError); - // Pre-load + register MotionPathPlugin once so adding a motion path in the - // studio doesn't take the async plugin-load flash path on the first soft - // reload (the comp may not ship the plugin until it actually uses one). - ensureMotionPathPluginLoaded(iframeRef.current); - onIframeRef?.(iframeRef.current); - }, - [baseOnIframeLoad, iframeRef, onIframeRef], - ); + const onIframeLoad = useCallback(() => { + baseOnIframeLoad(); + // Pre-load + register MotionPathPlugin once so adding a motion path in the + // studio doesn't take the async plugin-load flash path on the first soft + // reload (the comp may not ship the plugin until it actually uses one). + ensureMotionPathPluginLoaded(iframeRef.current); + onIframeRef?.(iframeRef.current); + }, [baseOnIframeLoad, iframeRef, onIframeRef]); const { compositionStack, diff --git a/packages/studio/src/components/nle/NLEPreview.tsx b/packages/studio/src/components/nle/NLEPreview.tsx index a11d062e9..8e634f767 100644 --- a/packages/studio/src/components/nle/NLEPreview.tsx +++ b/packages/studio/src/components/nle/NLEPreview.tsx @@ -15,7 +15,7 @@ import { readStudioUiPreferences, writeStudioUiPreferences } from "../../utils/s interface NLEPreviewProps { projectId: string; iframeRef: RefObject; - onIframeLoad: (reportError?: (message: string) => void) => void; + onIframeLoad: () => void; onCompositionLoadingChange?: (loading: boolean) => void; portrait?: boolean; directUrl?: string; @@ -491,9 +491,9 @@ export const NLEPreview = memo(function NLEPreview({ ref={setPreviewIframeRef} projectId={directUrl ? undefined : projectId} directUrl={directUrl} - onLoad={(reportError) => { + onLoad={() => { updateCompositionSizeFromPreview(); - onIframeLoad(reportError); + onIframeLoad(); applyInitialZoom(); }} onCompositionLoadingChange={onCompositionLoadingChange} diff --git a/packages/studio/src/player/components/Player.test.ts b/packages/studio/src/player/components/Player.test.ts index 4516a166a..516b80795 100644 --- a/packages/studio/src/player/components/Player.test.ts +++ b/packages/studio/src/player/components/Player.test.ts @@ -7,6 +7,15 @@ import { shouldShowCompositionLoadingOverlay, } from "./Player"; +function createAudioIframe() { + const iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + const audio = iframe.contentDocument?.createElement("audio"); + expect(audio).toBeDefined(); + iframe.contentDocument?.body.appendChild(audio!); + return { audio: audio!, iframe }; +} + describe("preview errors", () => { it("reads the player probe error for the visible retry state", () => { expect( @@ -35,10 +44,7 @@ describe("composition loading overlay", () => { }); it("keeps the asset overlay up while media is still buffering", () => { - const iframe = document.createElement("iframe"); - document.body.appendChild(iframe); - const audio = iframe.contentDocument?.createElement("audio"); - expect(audio).toBeDefined(); + const { audio, iframe } = createAudioIframe(); Object.defineProperty(audio, "readyState", { value: 0, configurable: true, @@ -47,7 +53,6 @@ describe("composition loading overlay", () => { value: 2, configurable: true, }); - iframe.contentDocument?.body.appendChild(audio!); expect(hasUnloadedAssets(iframe, false)).toBe(true); @@ -55,10 +60,7 @@ describe("composition loading overlay", () => { }); it("does not keep the asset overlay stuck on failed media sources", () => { - const iframe = document.createElement("iframe"); - document.body.appendChild(iframe); - const audio = iframe.contentDocument?.createElement("audio"); - expect(audio).toBeDefined(); + const { audio, iframe } = createAudioIframe(); Object.defineProperty(audio, "error", { value: { code: 4, message: "format error" }, configurable: true, @@ -71,7 +73,6 @@ describe("composition loading overlay", () => { value: 3, configurable: true, }); - iframe.contentDocument?.body.appendChild(audio!); expect(hasUnloadedAssets(iframe, false)).toBe(false); diff --git a/packages/studio/src/player/components/Player.tsx b/packages/studio/src/player/components/Player.tsx index 584d35001..15602f07b 100644 --- a/packages/studio/src/player/components/Player.tsx +++ b/packages/studio/src/player/components/Player.tsx @@ -10,7 +10,7 @@ import { HyperframesLoader } from "../../components/ui"; interface PlayerProps { projectId?: string; directUrl?: string; - onLoad: (reportError: (message: string) => void) => void; + onLoad: () => void; onCompositionLoadingChange?: (loading: boolean) => void; portrait?: boolean; style?: React.CSSProperties; @@ -208,10 +208,7 @@ export const Player = forwardRef( const onEnd = () => container.classList.remove("preview-revealing"); container.addEventListener("animationend", onEnd, { once: true }); } - onLoad((message) => { - setPreviewError(message); - setCompositionLoading(false); - }); + onLoad(); // Show a loading overlay until every `