@hyperframes/studio 0.8.92 → 0.8.93

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/studio",
3
- "version": "0.8.92",
3
+ "version": "0.8.93",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -60,12 +60,12 @@
60
60
  "dockview-react": "8.3.1",
61
61
  "gsap": "^3.13.0",
62
62
  "mediabunny": "^1.45.3",
63
- "@hyperframes/core": "0.8.92",
64
- "@hyperframes/sdk": "0.8.92",
65
- "@hyperframes/parsers": "0.8.92",
66
- "@hyperframes/shader-transitions": "0.8.92",
67
- "@hyperframes/player": "0.8.92",
68
- "@hyperframes/studio-server": "0.8.92"
63
+ "@hyperframes/core": "0.8.93",
64
+ "@hyperframes/shader-transitions": "0.8.93",
65
+ "@hyperframes/studio-server": "0.8.93",
66
+ "@hyperframes/parsers": "0.8.93",
67
+ "@hyperframes/player": "0.8.93",
68
+ "@hyperframes/sdk": "0.8.93"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@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.92"
84
+ "@hyperframes/producer": "0.8.93"
85
85
  },
86
86
  "peerDependencies": {
87
87
  "react": "19",
@@ -353,7 +353,11 @@ describe("useTimelinePlayer seek keepPlaying option (#834)", () => {
353
353
  describe("useTimelinePlayer RAF loop wrap-around", () => {
354
354
  type SeekCall = { time: number; options?: { keepPlaying?: boolean } };
355
355
 
356
- function attachInstrumentedAdapter(api: ReturnType<typeof useTimelinePlayer>, duration = 30) {
356
+ function attachInstrumentedAdapter(
357
+ api: ReturnType<typeof useTimelinePlayer>,
358
+ duration = 30,
359
+ hooks: object = {},
360
+ ) {
357
361
  let currentTime = 0;
358
362
  let playing = false;
359
363
  const seekCalls: SeekCall[] = [];
@@ -377,6 +381,7 @@ describe("useTimelinePlayer RAF loop wrap-around", () => {
377
381
  };
378
382
  attachIframeWindow(api, {
379
383
  __player: adapter,
384
+ __hf: hooks,
380
385
  postMessage: () => {},
381
386
  scrollTo: () => {},
382
387
  addEventListener: () => {},
@@ -446,6 +451,29 @@ describe("useTimelinePlayer RAF loop wrap-around", () => {
446
451
  }
447
452
  });
448
453
 
454
+ it("tells the preview where the loop wraps to while it plays, and that it stopped looping", () => {
455
+ const raf = installRafCapture();
456
+ try {
457
+ const { api, root } = renderTimelinePlayerHarness();
458
+ const setLoopStart = vi.fn();
459
+ const { adapter } = attachInstrumentedAdapter(api, 30, { setLoopStart });
460
+ act(() => {
461
+ usePlayerStore.getState().setInPoint(2);
462
+ usePlayerStore.getState().setOutPoint(5);
463
+ api.play();
464
+ });
465
+ adapter.setTime(3);
466
+ act(() => void raf.flushOne());
467
+ expect(setLoopStart).toHaveBeenLastCalledWith(2);
468
+ act(() => usePlayerStore.getState().setLoopEnabled(false));
469
+ act(() => void raf.flushOne());
470
+ expect(setLoopStart).toHaveBeenLastCalledWith(null);
471
+ unmountWithAct(root);
472
+ } finally {
473
+ raf.restore();
474
+ }
475
+ });
476
+
449
477
  it("does not seek and pauses cleanly when forward playback reaches the end without loop", () => {
450
478
  const raf = installRafCapture();
451
479
  try {
@@ -152,12 +152,18 @@ export function useTimelinePlayer({
152
152
  [],
153
153
  );
154
154
 
155
+ const setLoopStart = useCallback((seconds: number | null) => {
156
+ try {
157
+ (iframeRef.current?.contentWindow as IframeWindow | null)?.__hf?.setLoopStart?.(seconds);
158
+ } catch {}
159
+ }, []);
155
160
  const { startRAFLoop, stopRAFLoop, stopReverseLoop } = useTimelinePlayerLoop({
156
161
  rafRef,
157
162
  reverseRafRef,
158
163
  getAdapter,
159
164
  setCurrentTime,
160
165
  setIsPlaying,
166
+ setLoopStart,
161
167
  });
162
168
 
163
169
  const applyPlaybackRate = useCallback((rate: number) => {
@@ -22,6 +22,7 @@ interface UseTimelinePlayerLoopParams {
22
22
  getAdapter: () => PlaybackAdapter | null;
23
23
  setCurrentTime: (v: number) => void;
24
24
  setIsPlaying: (v: boolean) => void;
25
+ setLoopStart: (seconds: number | null) => void;
25
26
  }
26
27
 
27
28
  interface UseTimelinePlayerLoopResult {
@@ -36,6 +37,7 @@ export function useTimelinePlayerLoop({
36
37
  getAdapter,
37
38
  setCurrentTime,
38
39
  setIsPlaying,
40
+ setLoopStart,
39
41
  }: UseTimelinePlayerLoopParams): UseTimelinePlayerLoopResult {
40
42
  const stopReverseLoop = useCallback(() => {
41
43
  cancelAnimationFrame(reverseRafRef.current);
@@ -55,8 +57,10 @@ export function useTimelinePlayerLoop({
55
57
  const rawLoopStart = inPoint !== null ? inPoint : 0;
56
58
  const loopEnd = rawLoopStart < rawLoopEnd ? rawLoopEnd : dur;
57
59
  const loopStart = rawLoopStart < rawLoopEnd ? rawLoopStart : 0;
60
+ const looping = usePlayerStore.getState().loopEnabled && dur > 0;
61
+ setLoopStart(looping ? loopStart : null);
58
62
  if (time >= loopEnd) {
59
- if (usePlayerStore.getState().loopEnabled && dur > 0) {
63
+ if (looping) {
60
64
  // keepPlaying skips the adapter's implicit pause; play() below is then a no-op.
61
65
  adapter.seek(loopStart, { keepPlaying: true });
62
66
  liveTime.notify(loopStart);
@@ -75,7 +79,7 @@ export function useTimelinePlayerLoop({
75
79
  rafRef.current = requestAnimationFrame(tick);
76
80
  };
77
81
  rafRef.current = requestAnimationFrame(tick);
78
- }, [rafRef, getAdapter, setCurrentTime, setIsPlaying]);
82
+ }, [rafRef, getAdapter, setCurrentTime, setIsPlaying, setLoopStart]);
79
83
 
80
84
  const stopRAFLoop = useCallback(() => {
81
85
  cancelAnimationFrame(rafRef.current);
@@ -69,5 +69,6 @@ export type IframeWindow = Window & {
69
69
  __hf?: {
70
70
  leasePausedMedia?: (el: HTMLMediaElement) => void;
71
71
  releasePausedMedia?: (el: HTMLMediaElement) => void;
72
+ setLoopStart?: (seconds: number | null) => void;
72
73
  };
73
74
  };