@hyperframes/studio 0.6.45 → 0.6.47
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/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
7
7
|
<title>HyperFrames Studio</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-DpbZouXZ.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-SKRp8mGz.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.47",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"@codemirror/view": "6.40.0",
|
|
32
32
|
"@phosphor-icons/react": "^2.1.10",
|
|
33
33
|
"mediabunny": "^1.45.3",
|
|
34
|
-
"@hyperframes/core": "0.6.
|
|
35
|
-
"@hyperframes/player": "0.6.
|
|
34
|
+
"@hyperframes/core": "0.6.47",
|
|
35
|
+
"@hyperframes/player": "0.6.47"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/react": "19",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"vite": "^6.4.2",
|
|
47
47
|
"vitest": "^3.2.4",
|
|
48
48
|
"zustand": "^5.0.0",
|
|
49
|
-
"@hyperframes/producer": "0.6.
|
|
49
|
+
"@hyperframes/producer": "0.6.47"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"react": "19",
|
|
@@ -92,9 +92,16 @@ export function toOverlayRect(
|
|
|
92
92
|
const root =
|
|
93
93
|
doc?.querySelector<HTMLElement>("[data-composition-id]") ?? doc?.documentElement ?? null;
|
|
94
94
|
const rootRect = root?.getBoundingClientRect();
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
// Use the composition's declared dimensions (data-width/data-height) for scale
|
|
96
|
+
// calculation instead of rootRect.width/height. When GSAP applies transforms
|
|
97
|
+
// (scale, translate) to the root element, rootRect dimensions change but the
|
|
98
|
+
// composition's canonical size stays the same. Using rootRect causes overlay
|
|
99
|
+
// misalignment during animated playback.
|
|
100
|
+
const declaredWidth = readPositiveDimension(root?.getAttribute("data-width") ?? null);
|
|
101
|
+
const declaredHeight = readPositiveDimension(root?.getAttribute("data-height") ?? null);
|
|
102
|
+
const rootWidth = declaredWidth ?? rootRect?.width;
|
|
103
|
+
const rootHeight = declaredHeight ?? rootRect?.height;
|
|
104
|
+
if (!rootWidth || !rootHeight || !rootRect) return null;
|
|
98
105
|
|
|
99
106
|
const elementRect = element.getBoundingClientRect();
|
|
100
107
|
const rootScaleX = iframeRect.width / rootWidth;
|
|
@@ -111,8 +118,8 @@ export function toOverlayRect(
|
|
|
111
118
|
});
|
|
112
119
|
|
|
113
120
|
return {
|
|
114
|
-
left: iframeRect.left - overlayRect.left +
|
|
115
|
-
top: iframeRect.top - overlayRect.top +
|
|
121
|
+
left: iframeRect.left - overlayRect.left + elementRect.left * rootScaleX,
|
|
122
|
+
top: iframeRect.top - overlayRect.top + elementRect.top * rootScaleY,
|
|
116
123
|
width: elementRect.width * rootScaleX,
|
|
117
124
|
height: elementRect.height * rootScaleY,
|
|
118
125
|
editScaleX: editScale.scaleX,
|
|
@@ -196,6 +196,29 @@ describe("createStaticSeekPlaybackAdapter", () => {
|
|
|
196
196
|
expect(adapter.getDuration()).toBe(82);
|
|
197
197
|
});
|
|
198
198
|
|
|
199
|
+
it("clamps time at the duration boundary during RAF tick", () => {
|
|
200
|
+
const clock = createManualAnimationClock();
|
|
201
|
+
const renderedTimes: number[] = [];
|
|
202
|
+
const adapter = createStaticSeekPlaybackAdapter(
|
|
203
|
+
{
|
|
204
|
+
getTime: () => 0,
|
|
205
|
+
renderSeek: (time: number) => {
|
|
206
|
+
renderedTimes.push(time);
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
2,
|
|
210
|
+
clock,
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
adapter.seek(0);
|
|
214
|
+
adapter.play();
|
|
215
|
+
clock.step(3_000);
|
|
216
|
+
|
|
217
|
+
expect(adapter.getTime()).toBe(2);
|
|
218
|
+
expect(adapter.isPlaying()).toBe(false);
|
|
219
|
+
expect(renderedTimes).toEqual([0, 2]);
|
|
220
|
+
});
|
|
221
|
+
|
|
199
222
|
it("pauses old adapter before replacing with new duration", () => {
|
|
200
223
|
const clock = createManualAnimationClock();
|
|
201
224
|
const adapter = createStaticSeekPlaybackAdapter(
|
|
@@ -226,11 +226,12 @@ export function useTimelinePlayer() {
|
|
|
226
226
|
const tick = () => {
|
|
227
227
|
const adapter = getAdapter();
|
|
228
228
|
if (adapter) {
|
|
229
|
-
const
|
|
229
|
+
const rawTime = adapter.getTime();
|
|
230
230
|
const dur = adapter.getDuration();
|
|
231
|
+
const time = dur > 0 ? Math.min(rawTime, dur) : rawTime;
|
|
231
232
|
liveTime.notify(time); // direct DOM updates, no React re-render
|
|
232
233
|
const { inPoint, outPoint } = usePlayerStore.getState();
|
|
233
|
-
const rawLoopEnd = outPoint !== null ? outPoint : dur;
|
|
234
|
+
const rawLoopEnd = outPoint !== null ? Math.min(outPoint, dur) : dur;
|
|
234
235
|
const rawLoopStart = inPoint !== null ? inPoint : 0;
|
|
235
236
|
const loopEnd = rawLoopStart < rawLoopEnd ? rawLoopEnd : dur;
|
|
236
237
|
const loopStart = rawLoopStart < rawLoopEnd ? rawLoopStart : 0;
|
|
@@ -258,7 +259,6 @@ export function useTimelinePlayer() {
|
|
|
258
259
|
const stopRAFLoop = useCallback(() => {
|
|
259
260
|
cancelAnimationFrame(rafRef.current);
|
|
260
261
|
}, []);
|
|
261
|
-
|
|
262
262
|
const applyPlaybackRate = useCallback((rate: number) => {
|
|
263
263
|
const iframe = iframeRef.current;
|
|
264
264
|
if (!iframe) return;
|
|
@@ -280,7 +280,6 @@ export function useTimelinePlayer() {
|
|
|
280
280
|
console.warn("[useTimelinePlayer] Could not set playback rate (cross-origin)", err);
|
|
281
281
|
}
|
|
282
282
|
}, []);
|
|
283
|
-
|
|
284
283
|
const applyPreviewAudioState = useCallback((playbackRateOverride?: number) => {
|
|
285
284
|
const { audioMuted, playbackRate } = usePlayerStore.getState();
|
|
286
285
|
const effectivePlaybackRate = playbackRateOverride ?? playbackRate;
|
|
@@ -289,7 +288,6 @@ export function useTimelinePlayer() {
|
|
|
289
288
|
shouldMutePreviewAudio(audioMuted, effectivePlaybackRate),
|
|
290
289
|
);
|
|
291
290
|
}, []);
|
|
292
|
-
|
|
293
291
|
const play = useCallback(() => {
|
|
294
292
|
stopRAFLoop();
|
|
295
293
|
stopReverseLoop();
|
|
@@ -313,7 +311,6 @@ export function useTimelinePlayer() {
|
|
|
313
311
|
stopRAFLoop,
|
|
314
312
|
stopReverseLoop,
|
|
315
313
|
]);
|
|
316
|
-
|
|
317
314
|
const playBackward = useCallback(
|
|
318
315
|
(rate: number) => {
|
|
319
316
|
stopRAFLoop();
|
|
@@ -334,7 +331,7 @@ export function useTimelinePlayer() {
|
|
|
334
331
|
const elapsed = ((now - startedAt) / 1000) * speed;
|
|
335
332
|
let nextTime = startTime - elapsed;
|
|
336
333
|
const { inPoint, outPoint } = usePlayerStore.getState();
|
|
337
|
-
const rawLoopEnd = outPoint !== null ? outPoint : duration;
|
|
334
|
+
const rawLoopEnd = outPoint !== null ? Math.min(outPoint, duration) : duration;
|
|
338
335
|
const rawLoopStart = inPoint !== null ? inPoint : 0;
|
|
339
336
|
const loopEnd = rawLoopStart < rawLoopEnd ? rawLoopEnd : duration;
|
|
340
337
|
const loopStart = rawLoopStart < rawLoopEnd ? rawLoopStart : 0;
|
|
@@ -373,7 +370,6 @@ export function useTimelinePlayer() {
|
|
|
373
370
|
stopReverseLoop,
|
|
374
371
|
],
|
|
375
372
|
);
|
|
376
|
-
|
|
377
373
|
const pause = useCallback(() => {
|
|
378
374
|
stopReverseLoop();
|
|
379
375
|
const adapter = getAdapter();
|
|
@@ -385,7 +381,6 @@ export function useTimelinePlayer() {
|
|
|
385
381
|
shuttleSpeedIndexRef.current = 0;
|
|
386
382
|
stopRAFLoop();
|
|
387
383
|
}, [getAdapter, setCurrentTime, setIsPlaying, stopRAFLoop, stopReverseLoop]);
|
|
388
|
-
|
|
389
384
|
const seek = useCallback(
|
|
390
385
|
(time: number, options?: { keepPlaying?: boolean }) => {
|
|
391
386
|
// Reverse shuttle is always stopped: the RAF reverse tick can't survive
|
|
@@ -431,7 +426,6 @@ export function useTimelinePlayer() {
|
|
|
431
426
|
}
|
|
432
427
|
});
|
|
433
428
|
}, [seek]);
|
|
434
|
-
|
|
435
429
|
const { playbackKeyDownRef, playbackKeyUpRef, attachIframeShortcutListeners, togglePlay } =
|
|
436
430
|
usePlaybackKeyboard({
|
|
437
431
|
iframeRef,
|
|
@@ -460,7 +454,6 @@ export function useTimelinePlayer() {
|
|
|
460
454
|
attachIframeShortcutListeners,
|
|
461
455
|
applyPreviewAudioState,
|
|
462
456
|
});
|
|
463
|
-
|
|
464
457
|
const saveSeekPosition = useCallback(() => {
|
|
465
458
|
const adapter = getAdapter();
|
|
466
459
|
pendingSeekRef.current = adapter
|
|
@@ -471,19 +464,15 @@ export function useTimelinePlayer() {
|
|
|
471
464
|
stopReverseLoop();
|
|
472
465
|
setIsPlaying(false);
|
|
473
466
|
}, [getAdapter, stopRAFLoop, setIsPlaying, stopReverseLoop]);
|
|
474
|
-
|
|
475
467
|
const refreshPlayer = useCallback(() => {
|
|
476
468
|
const iframe = iframeRef.current;
|
|
477
469
|
if (!iframe) return;
|
|
478
|
-
|
|
479
470
|
saveSeekPosition();
|
|
480
|
-
|
|
481
471
|
const src = iframe.src;
|
|
482
472
|
const url = new URL(src, window.location.origin);
|
|
483
473
|
url.searchParams.set("_t", String(Date.now()));
|
|
484
474
|
iframe.src = url.toString();
|
|
485
475
|
}, [saveSeekPosition]);
|
|
486
|
-
|
|
487
476
|
const getAdapterRef = useRef(getAdapter);
|
|
488
477
|
getAdapterRef.current = getAdapter;
|
|
489
478
|
|