@hyperframes/studio 0.8.169 → 0.8.171
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{hyperframes-player-B04Yx96S.js → hyperframes-player-DohJxMIU.js} +2 -2
- package/dist/assets/{index-D2e0J6kh.js → index-BOkm4Ooc.js} +101 -101
- package/dist/assets/{index-D9CDX_Rr.js → index-DfwtrPPt.js} +1 -1
- package/dist/{chunk-4UHZLLQL.js → chunk-BLXESG6S.js} +4 -1
- package/dist/index.html +1 -1
- package/dist/index.js +15 -4
- package/dist/player.d.ts +1 -0
- package/dist/player.js +1 -1
- package/package.json +8 -8
- package/src/components/nle/NLEPreview.slowBoot.test.ts +63 -0
- package/src/components/nle/NLEPreview.tsx +2 -2
- package/src/player/components/Player.tsx +4 -0
- package/src/player/hooks/useShadowPreviewReload.ts +7 -1
- package/src/player/hooks/useTimelinePlayer.shadowReload.test.ts +25 -0
- package/src/player/hooks/useTimelineSyncCallbacks.ts +11 -1
|
@@ -1146,7 +1146,8 @@ var Player = forwardRef(
|
|
|
1146
1146
|
onPainted,
|
|
1147
1147
|
portrait,
|
|
1148
1148
|
style,
|
|
1149
|
-
suppressLoadingOverlay
|
|
1149
|
+
suppressLoadingOverlay,
|
|
1150
|
+
noLoadTimeout
|
|
1150
1151
|
}, ref) => {
|
|
1151
1152
|
const containerRef = useRef(null);
|
|
1152
1153
|
const loadCountRef = useRef(0);
|
|
@@ -1204,6 +1205,7 @@ var Player = forwardRef(
|
|
|
1204
1205
|
setCompositionLoading(true);
|
|
1205
1206
|
loadStartedAtRef.current = performance.now();
|
|
1206
1207
|
loadIdRef.current += 1;
|
|
1208
|
+
player.removeAttribute("no-load-timeout");
|
|
1207
1209
|
player.setAttribute("src", retryUrl.pathname + retryUrl.search);
|
|
1208
1210
|
};
|
|
1209
1211
|
retryPreviewRef.current = retryPreview;
|
|
@@ -1286,6 +1288,7 @@ var Player = forwardRef(
|
|
|
1286
1288
|
player.setAttribute("shader-capture-scale", "1");
|
|
1287
1289
|
player.setAttribute("shader-loading", "player");
|
|
1288
1290
|
player.setAttribute("low-power-idle", "");
|
|
1291
|
+
player.toggleAttribute("no-load-timeout", noLoadTimeout === true);
|
|
1289
1292
|
player.setAttribute("width", String(portrait ? 1080 : 1920));
|
|
1290
1293
|
player.setAttribute("height", String(portrait ? 1920 : 1080));
|
|
1291
1294
|
player.style.width = "100%";
|
package/dist/index.html
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
document.documentElement.dataset.theme = "paper";
|
|
15
15
|
}
|
|
16
16
|
</script>
|
|
17
|
-
<script type="module" crossorigin src="/assets/index-
|
|
17
|
+
<script type="module" crossorigin src="/assets/index-BOkm4Ooc.js"></script>
|
|
18
18
|
<link rel="stylesheet" crossorigin href="/assets/index-DkQKsFxx.css">
|
|
19
19
|
</head>
|
|
20
20
|
<body>
|
package/dist/index.js
CHANGED
|
@@ -47,7 +47,7 @@ import {
|
|
|
47
47
|
usePreviewVariablesStore,
|
|
48
48
|
whenPreviewBooted,
|
|
49
49
|
writeStudioUiPreferences
|
|
50
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-BLXESG6S.js";
|
|
51
51
|
import {
|
|
52
52
|
Button,
|
|
53
53
|
ContextMenu,
|
|
@@ -30154,7 +30154,8 @@ function useTimelineSyncCallbacks({
|
|
|
30154
30154
|
applyPreviewAudioState,
|
|
30155
30155
|
onAdapterReady = revealAndCommit,
|
|
30156
30156
|
isCurrent,
|
|
30157
|
-
onLoadGiveUp
|
|
30157
|
+
onLoadGiveUp,
|
|
30158
|
+
hostOwnsDeadline
|
|
30158
30159
|
}) {
|
|
30159
30160
|
const processTimelineMessage = useCallback38(
|
|
30160
30161
|
(data) => {
|
|
@@ -30291,6 +30292,7 @@ function useTimelineSyncCallbacks({
|
|
|
30291
30292
|
};
|
|
30292
30293
|
window.addEventListener("message", onMessage);
|
|
30293
30294
|
stopWaitingRef.current = stopWaiting;
|
|
30295
|
+
if (hostOwnsDeadline) return;
|
|
30294
30296
|
probeIntervalRef.current = setTimeout(() => {
|
|
30295
30297
|
if (!settled) {
|
|
30296
30298
|
trySettle();
|
|
@@ -30300,7 +30302,14 @@ function useTimelineSyncCallbacks({
|
|
|
30300
30302
|
revealIframe(iframeRef.current);
|
|
30301
30303
|
}, 5e3);
|
|
30302
30304
|
},
|
|
30303
|
-
[
|
|
30305
|
+
[
|
|
30306
|
+
initializeAdapter,
|
|
30307
|
+
iframeRef,
|
|
30308
|
+
probeIntervalRef,
|
|
30309
|
+
applyPreviewAudioState,
|
|
30310
|
+
onLoadGiveUp,
|
|
30311
|
+
hostOwnsDeadline
|
|
30312
|
+
]
|
|
30304
30313
|
);
|
|
30305
30314
|
const cancelPendingLoad = useCallback38(() => {
|
|
30306
30315
|
if (probeIntervalRef.current) clearInterval(probeIntervalRef.current);
|
|
@@ -31697,7 +31706,8 @@ function useShadowPreviewReload({
|
|
|
31697
31706
|
applyPreviewAudioState: () => {
|
|
31698
31707
|
},
|
|
31699
31708
|
onAdapterReady: markAdapterReady,
|
|
31700
|
-
isCurrent: isCurrentShadow
|
|
31709
|
+
isCurrent: isCurrentShadow,
|
|
31710
|
+
hostOwnsDeadline: true
|
|
31701
31711
|
});
|
|
31702
31712
|
cancelPendingLoadRef.current = cancelPendingLoad;
|
|
31703
31713
|
const onShadowReadyChange = useCallback39(
|
|
@@ -33928,6 +33938,7 @@ var NLEPreview = memo23(function NLEPreview2({
|
|
|
33928
33938
|
onPreviewError: (message) => onShadowError(slot.gen, message),
|
|
33929
33939
|
portrait,
|
|
33930
33940
|
suppressLoadingOverlay: true,
|
|
33941
|
+
noLoadTimeout: true,
|
|
33931
33942
|
style: SHADOW_IFRAME_STYLE
|
|
33932
33943
|
},
|
|
33933
33944
|
`${activeKey}-${slot.gen}`
|
package/dist/player.d.ts
CHANGED
package/dist/player.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.171",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -59,12 +59,12 @@
|
|
|
59
59
|
"dockview-react": "8.3.1",
|
|
60
60
|
"gsap": "^3.13.0",
|
|
61
61
|
"mediabunny": "^1.45.3",
|
|
62
|
-
"@hyperframes/core": "0.8.
|
|
63
|
-
"@hyperframes/
|
|
64
|
-
"@hyperframes/
|
|
65
|
-
"@hyperframes/
|
|
66
|
-
"@hyperframes/studio-server": "0.8.
|
|
67
|
-
"@hyperframes/
|
|
62
|
+
"@hyperframes/core": "0.8.171",
|
|
63
|
+
"@hyperframes/player": "0.8.171",
|
|
64
|
+
"@hyperframes/sdk": "0.8.171",
|
|
65
|
+
"@hyperframes/shader-transitions": "0.8.171",
|
|
66
|
+
"@hyperframes/studio-server": "0.8.171",
|
|
67
|
+
"@hyperframes/parsers": "0.8.171"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@tailwindcss/vite": "^4.3.3",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"vite": "^6.4.2",
|
|
82
82
|
"vitest": "^4.1.11",
|
|
83
83
|
"zustand": "^5.0.0",
|
|
84
|
-
"@hyperframes/producer": "0.8.
|
|
84
|
+
"@hyperframes/producer": "0.8.171"
|
|
85
85
|
},
|
|
86
86
|
"peerDependencies": {
|
|
87
87
|
"react": "19",
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
// @vitest-environment-options {"happyDOM":{"settings":{"disableIframePageLoading":true}}}
|
|
3
|
+
|
|
4
|
+
import React, { act, createRef } from "react";
|
|
5
|
+
import { createRoot } from "react-dom/client";
|
|
6
|
+
import { afterEach, expect, it, vi } from "vitest";
|
|
7
|
+
import { NLEPreview } from "./NLEPreview";
|
|
8
|
+
|
|
9
|
+
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
vi.useRealTimers();
|
|
13
|
+
document.body.innerHTML = "";
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("keeps a shadow reload whose runtime boots after 8 s, and its player comes ready", async () => {
|
|
17
|
+
await import("@hyperframes/player");
|
|
18
|
+
vi.useFakeTimers();
|
|
19
|
+
const onShadowError = vi.fn();
|
|
20
|
+
const host = document.createElement("div");
|
|
21
|
+
document.body.append(host);
|
|
22
|
+
const root = createRoot(host);
|
|
23
|
+
await act(async () => {
|
|
24
|
+
root.render(
|
|
25
|
+
React.createElement(NLEPreview, {
|
|
26
|
+
projectId: "p",
|
|
27
|
+
iframeRef: createRef<HTMLIFrameElement>(),
|
|
28
|
+
onIframeLoad: () => {},
|
|
29
|
+
previewSlots: [
|
|
30
|
+
{ gen: 0, role: "live" },
|
|
31
|
+
{ gen: 1, role: "shadow", url: "/api/projects/p/preview?_t=1" },
|
|
32
|
+
],
|
|
33
|
+
onShadowIframeLoad: () => {},
|
|
34
|
+
onShadowReadyChange: () => {},
|
|
35
|
+
onShadowError,
|
|
36
|
+
setShadowIframeNode: () => {},
|
|
37
|
+
resetPreviewSlots: () => {},
|
|
38
|
+
}),
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const shadow = host.querySelectorAll<
|
|
43
|
+
HTMLElement & { iframeElement: HTMLIFrameElement; ready: boolean }
|
|
44
|
+
>("hyperframes-player")[1];
|
|
45
|
+
const iframe = shadow.iframeElement;
|
|
46
|
+
act(() => {
|
|
47
|
+
iframe.dispatchEvent(new Event("load"));
|
|
48
|
+
});
|
|
49
|
+
act(() => {
|
|
50
|
+
vi.advanceTimersByTime(10_000);
|
|
51
|
+
});
|
|
52
|
+
expect(onShadowError).not.toHaveBeenCalled();
|
|
53
|
+
|
|
54
|
+
Object.assign(iframe.contentWindow!, { __player: { getDuration: () => 9 } });
|
|
55
|
+
act(() => {
|
|
56
|
+
vi.advanceTimersByTime(1_000);
|
|
57
|
+
});
|
|
58
|
+
expect(onShadowError).not.toHaveBeenCalled();
|
|
59
|
+
expect(shadow.ready).toBe(true);
|
|
60
|
+
// The live slot keeps the player's own 8 s deadline.
|
|
61
|
+
expect(host.querySelector("hyperframes-player")?.hasAttribute("no-load-timeout")).toBe(false);
|
|
62
|
+
act(() => root.unmount());
|
|
63
|
+
});
|
|
@@ -209,8 +209,7 @@ export const NLEPreview = memo(function NLEPreview({
|
|
|
209
209
|
|
|
210
210
|
const updateCompositionSizeFromPreview = useCallback(() => {
|
|
211
211
|
const next = readPreviewCompositionSize(previewIframeRef.current);
|
|
212
|
-
// Pure updater
|
|
213
|
-
// (updaters may run more than once under Strict Mode / concurrent React).
|
|
212
|
+
// Pure updater; the effect below notifies the parent (Strict Mode may run updaters twice).
|
|
214
213
|
setCompositionSize((prev) =>
|
|
215
214
|
prev?.width === next?.width && prev?.height === next?.height ? prev : next,
|
|
216
215
|
);
|
|
@@ -563,6 +562,7 @@ export const NLEPreview = memo(function NLEPreview({
|
|
|
563
562
|
onPreviewError={(message) => onShadowError(slot.gen, message)}
|
|
564
563
|
portrait={portrait}
|
|
565
564
|
suppressLoadingOverlay
|
|
565
|
+
noLoadTimeout
|
|
566
566
|
style={SHADOW_IFRAME_STYLE}
|
|
567
567
|
/>
|
|
568
568
|
),
|
|
@@ -37,6 +37,7 @@ interface PlayerProps {
|
|
|
37
37
|
portrait?: boolean;
|
|
38
38
|
style?: React.CSSProperties;
|
|
39
39
|
suppressLoadingOverlay?: boolean;
|
|
40
|
+
noLoadTimeout?: boolean;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
interface HyperframesPlayerElement extends HTMLElement {
|
|
@@ -154,6 +155,7 @@ export const Player = forwardRef<HTMLIFrameElement, PlayerProps>(
|
|
|
154
155
|
portrait,
|
|
155
156
|
style,
|
|
156
157
|
suppressLoadingOverlay,
|
|
158
|
+
noLoadTimeout,
|
|
157
159
|
},
|
|
158
160
|
ref,
|
|
159
161
|
) => {
|
|
@@ -224,6 +226,7 @@ export const Player = forwardRef<HTMLIFrameElement, PlayerProps>(
|
|
|
224
226
|
setCompositionLoading(true);
|
|
225
227
|
loadStartedAtRef.current = performance.now();
|
|
226
228
|
loadIdRef.current += 1;
|
|
229
|
+
player.removeAttribute("no-load-timeout");
|
|
227
230
|
player.setAttribute("src", retryUrl.pathname + retryUrl.search);
|
|
228
231
|
};
|
|
229
232
|
retryPreviewRef.current = retryPreview;
|
|
@@ -331,6 +334,7 @@ export const Player = forwardRef<HTMLIFrameElement, PlayerProps>(
|
|
|
331
334
|
player.setAttribute("shader-capture-scale", "1");
|
|
332
335
|
player.setAttribute("shader-loading", "player");
|
|
333
336
|
player.setAttribute("low-power-idle", "");
|
|
337
|
+
player.toggleAttribute("no-load-timeout", noLoadTimeout === true);
|
|
334
338
|
player.setAttribute("width", String(portrait ? 1080 : 1920));
|
|
335
339
|
player.setAttribute("height", String(portrait ? 1920 : 1080));
|
|
336
340
|
player.style.width = "100%";
|
|
@@ -49,7 +49,12 @@ function isDocumentHidden(): boolean {
|
|
|
49
49
|
|
|
50
50
|
type UseShadowPreviewReloadParams = Omit<
|
|
51
51
|
UseTimelineSyncCallbacksParams,
|
|
52
|
-
|
|
52
|
+
| "probeIntervalRef"
|
|
53
|
+
| "onAdapterReady"
|
|
54
|
+
| "getAdapter"
|
|
55
|
+
| "isCurrent"
|
|
56
|
+
| "onLoadGiveUp"
|
|
57
|
+
| "hostOwnsDeadline"
|
|
53
58
|
> & {
|
|
54
59
|
getAdapter: (overrideIframe?: HTMLIFrameElement | null) => PlaybackAdapter | null;
|
|
55
60
|
/** Runs right after a shadow becomes the live iframe (iframeRef already points at it). */
|
|
@@ -249,6 +254,7 @@ export function useShadowPreviewReload({
|
|
|
249
254
|
applyPreviewAudioState: () => {},
|
|
250
255
|
onAdapterReady: markAdapterReady,
|
|
251
256
|
isCurrent: isCurrentShadow,
|
|
257
|
+
hostOwnsDeadline: true,
|
|
252
258
|
});
|
|
253
259
|
cancelPendingLoadRef.current = cancelPendingLoad;
|
|
254
260
|
|
|
@@ -461,6 +461,31 @@ describe("shadow reload readiness and failure", () => {
|
|
|
461
461
|
unmount(root);
|
|
462
462
|
});
|
|
463
463
|
|
|
464
|
+
it("promotes a shadow whose runtime boots after Studio's own 5 s readiness wait", () => {
|
|
465
|
+
vi.useFakeTimers();
|
|
466
|
+
const onPreviewReloadFailed = vi.fn();
|
|
467
|
+
const { getApi, gen, root } = beginReload({ onPreviewReloadFailed });
|
|
468
|
+
const { win } = makeAdapterWindow();
|
|
469
|
+
const booting: Record<string, unknown> = { ...win, __player: undefined };
|
|
470
|
+
const iframe = makeFakeIframe(booting);
|
|
471
|
+
act(() => {
|
|
472
|
+
getApi().setShadowIframeNode(iframe);
|
|
473
|
+
getApi().onShadowIframeLoad(gen);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
act(() => void vi.advanceTimersByTime(10_000));
|
|
477
|
+
booting.__player = win.__player;
|
|
478
|
+
act(() => {
|
|
479
|
+
window.dispatchEvent(
|
|
480
|
+
new MessageEvent("message", { data: { source: "hf-preview", type: "state" } }),
|
|
481
|
+
);
|
|
482
|
+
getApi().onShadowReadyChange(gen, true);
|
|
483
|
+
});
|
|
484
|
+
expect(getApi().iframeRef.current).toBe(iframe);
|
|
485
|
+
expect(onPreviewReloadFailed).not.toHaveBeenCalled();
|
|
486
|
+
unmount(root);
|
|
487
|
+
});
|
|
488
|
+
|
|
464
489
|
it("gives a reload that replaces a slow one its own full set of budgets", () => {
|
|
465
490
|
vi.useFakeTimers();
|
|
466
491
|
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
@@ -60,6 +60,7 @@ export interface UseTimelineSyncCallbacksParams {
|
|
|
60
60
|
isCurrent?: (context?: number) => boolean;
|
|
61
61
|
/** Fires when no adapter appeared within the wait window. */
|
|
62
62
|
onLoadGiveUp?: (context?: number) => void;
|
|
63
|
+
hostOwnsDeadline?: boolean;
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
/**
|
|
@@ -163,6 +164,7 @@ export function useTimelineSyncCallbacks({
|
|
|
163
164
|
onAdapterReady = revealAndCommit,
|
|
164
165
|
isCurrent,
|
|
165
166
|
onLoadGiveUp,
|
|
167
|
+
hostOwnsDeadline,
|
|
166
168
|
}: UseTimelineSyncCallbacksParams) {
|
|
167
169
|
// Convert a runtime timeline message (from iframe postMessage) into TimelineElements
|
|
168
170
|
const processTimelineMessage = useCallback(
|
|
@@ -335,6 +337,7 @@ export function useTimelineSyncCallbacks({
|
|
|
335
337
|
};
|
|
336
338
|
window.addEventListener("message", onMessage);
|
|
337
339
|
stopWaitingRef.current = stopWaiting;
|
|
340
|
+
if (hostOwnsDeadline) return;
|
|
338
341
|
|
|
339
342
|
// Safety net: if no message arrives within 5s, try one last time then give up.
|
|
340
343
|
probeIntervalRef.current = setTimeout(() => {
|
|
@@ -346,7 +349,14 @@ export function useTimelineSyncCallbacks({
|
|
|
346
349
|
revealIframe(iframeRef.current);
|
|
347
350
|
}, 5000) as unknown as ReturnType<typeof setInterval>;
|
|
348
351
|
},
|
|
349
|
-
[
|
|
352
|
+
[
|
|
353
|
+
initializeAdapter,
|
|
354
|
+
iframeRef,
|
|
355
|
+
probeIntervalRef,
|
|
356
|
+
applyPreviewAudioState,
|
|
357
|
+
onLoadGiveUp,
|
|
358
|
+
hostOwnsDeadline,
|
|
359
|
+
],
|
|
350
360
|
);
|
|
351
361
|
|
|
352
362
|
const cancelPendingLoad = useCallback(() => {
|