@hyperframes/studio 0.7.65 → 0.7.66
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-D2zuA3cx.js → hyperframes-player-BiCGkoKi.js} +1 -1
- package/dist/assets/{index-hLCqiyjx.js → index-CnL24iBm.js} +1 -1
- package/dist/assets/{index-DwwsYjuk.js → index-Cp6reNm_.js} +1 -1
- package/dist/assets/{index-BqX4YLnH.js → index-wCPvBGsm.js} +117 -117
- package/dist/index.html +1 -1
- package/dist/index.js +2515 -2530
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/player/components/PlayerControls.test.ts +0 -17
- package/src/player/components/PlayerControls.tsx +8 -23
- package/src/player/hooks/usePlaybackKeyboard.test.ts +2 -2
- package/src/player/hooks/usePlaybackKeyboard.ts +1 -4
- package/src/player/hooks/useTimelinePlayer.seek.test.ts +2 -2
- package/src/player/hooks/useTimelinePlayer.ts +5 -13
- package/src/player/lib/timelineDOM.ts +0 -1
- package/src/player/lib/timelineIframeHelpers.ts +0 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.66",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -46,11 +46,11 @@
|
|
|
46
46
|
"gsap": "^3.13.0",
|
|
47
47
|
"marked": "^14.1.4",
|
|
48
48
|
"mediabunny": "^1.45.3",
|
|
49
|
-
"@hyperframes/
|
|
50
|
-
"@hyperframes/
|
|
51
|
-
"@hyperframes/
|
|
52
|
-
"@hyperframes/
|
|
53
|
-
"@hyperframes/
|
|
49
|
+
"@hyperframes/core": "0.7.66",
|
|
50
|
+
"@hyperframes/sdk": "0.7.66",
|
|
51
|
+
"@hyperframes/studio-server": "0.7.66",
|
|
52
|
+
"@hyperframes/parsers": "0.7.66",
|
|
53
|
+
"@hyperframes/player": "0.7.66"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/react": "19",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"vite": "^6.4.2",
|
|
66
66
|
"vitest": "^3.2.4",
|
|
67
67
|
"zustand": "^5.0.0",
|
|
68
|
-
"@hyperframes/producer": "0.7.
|
|
68
|
+
"@hyperframes/producer": "0.7.66"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
71
|
"react": "19",
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import { resolveSeekPercent } from "./PlayerControls";
|
|
3
|
-
import { shouldMutePreviewAudio } from "../lib/timelineIframeHelpers";
|
|
4
3
|
|
|
5
4
|
describe("resolveSeekPercent", () => {
|
|
6
5
|
it("returns 0 when the track width is invalid", () => {
|
|
@@ -19,19 +18,3 @@ describe("resolveSeekPercent", () => {
|
|
|
19
18
|
expect(resolveSeekPercent(150, 100, 200)).toBe(0.25);
|
|
20
19
|
});
|
|
21
20
|
});
|
|
22
|
-
|
|
23
|
-
describe("shouldMutePreviewAudio", () => {
|
|
24
|
-
it("mutes when the user toggled audio off", () => {
|
|
25
|
-
expect(shouldMutePreviewAudio(true, 1)).toBe(true);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("auto-mutes above 1x playback", () => {
|
|
29
|
-
expect(shouldMutePreviewAudio(false, 1.5)).toBe(true);
|
|
30
|
-
expect(shouldMutePreviewAudio(false, 2)).toBe(true);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("keeps audio on at 1x or slower when the user has not muted it", () => {
|
|
34
|
-
expect(shouldMutePreviewAudio(false, 1)).toBe(false);
|
|
35
|
-
expect(shouldMutePreviewAudio(false, 0.5)).toBe(false);
|
|
36
|
-
});
|
|
37
|
-
});
|
|
@@ -2,7 +2,6 @@ import { useRef, useCallback, useEffect, memo } from "react";
|
|
|
2
2
|
import gsap from "gsap";
|
|
3
3
|
import { MorphSVGPlugin } from "gsap/MorphSVGPlugin";
|
|
4
4
|
import { formatFrameTime, formatTime, stepFrameTime } from "../lib/time";
|
|
5
|
-
import { shouldMutePreviewAudio } from "../lib/timelineIframeHelpers";
|
|
6
5
|
import { usePlayerStore } from "../store/playerStore";
|
|
7
6
|
import { trackStudioEvent } from "../../utils/studioTelemetry";
|
|
8
7
|
import { Tooltip } from "../../components/ui";
|
|
@@ -60,40 +59,30 @@ function PlayPauseMorphIcon({ playing }: { playing: boolean }) {
|
|
|
60
59
|
|
|
61
60
|
const MuteButton = memo(function MuteButton({
|
|
62
61
|
audioMuted,
|
|
63
|
-
audioAutoMuted,
|
|
64
|
-
effectiveAudioMuted,
|
|
65
62
|
controlsDisabled,
|
|
66
63
|
setAudioMuted,
|
|
67
64
|
}: {
|
|
68
65
|
audioMuted: boolean;
|
|
69
|
-
audioAutoMuted: boolean;
|
|
70
|
-
effectiveAudioMuted: boolean;
|
|
71
66
|
controlsDisabled: boolean;
|
|
72
67
|
setAudioMuted: (v: boolean) => void;
|
|
73
68
|
}) {
|
|
74
|
-
const label =
|
|
75
|
-
? "Audio muted above 1x speed"
|
|
76
|
-
: audioMuted
|
|
77
|
-
? "Unmute audio"
|
|
78
|
-
: "Mute audio";
|
|
69
|
+
const label = audioMuted ? "Unmute audio" : "Mute audio";
|
|
79
70
|
return (
|
|
80
71
|
<Tooltip label={label}>
|
|
81
72
|
<button
|
|
82
73
|
type="button"
|
|
83
74
|
onClick={() => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
setAudioMuted(!audioMuted);
|
|
87
|
-
}
|
|
75
|
+
trackStudioEvent("playback", { action: "mute_toggle", muted: !audioMuted });
|
|
76
|
+
setAudioMuted(!audioMuted);
|
|
88
77
|
}}
|
|
89
|
-
disabled={controlsDisabled
|
|
78
|
+
disabled={controlsDisabled}
|
|
90
79
|
aria-label={label}
|
|
91
|
-
aria-pressed={
|
|
80
|
+
aria-pressed={audioMuted}
|
|
92
81
|
className={`h-7 w-7 flex-shrink-0 flex items-center justify-center rounded-md border transition-colors disabled:pointer-events-none ${
|
|
93
|
-
|
|
82
|
+
audioMuted
|
|
94
83
|
? "text-studio-accent bg-studio-accent/10 border-studio-accent/30"
|
|
95
84
|
: "border-neutral-700 text-neutral-400 hover:border-neutral-500 hover:bg-neutral-800"
|
|
96
|
-
}
|
|
85
|
+
}`}
|
|
97
86
|
>
|
|
98
87
|
<svg
|
|
99
88
|
width="13"
|
|
@@ -107,7 +96,7 @@ const MuteButton = memo(function MuteButton({
|
|
|
107
96
|
aria-hidden="true"
|
|
108
97
|
>
|
|
109
98
|
<path d="M11 5 6 9H3v6h3l5 4V5Z" />
|
|
110
|
-
{
|
|
99
|
+
{audioMuted ? (
|
|
111
100
|
<>
|
|
112
101
|
<path d="m19 9-6 6" />
|
|
113
102
|
<path d="m13 9 6 6" />
|
|
@@ -383,8 +372,6 @@ export const PlayerControls = memo(function PlayerControls({
|
|
|
383
372
|
const durationRef = useRef(duration);
|
|
384
373
|
durationRef.current = duration;
|
|
385
374
|
const controlsDisabled = disabled || !timelineReady;
|
|
386
|
-
const audioAutoMuted = playbackRate > 1;
|
|
387
|
-
const effectiveAudioMuted = shouldMutePreviewAudio(audioMuted, playbackRate);
|
|
388
375
|
|
|
389
376
|
useEffect(() => {
|
|
390
377
|
if (!timeDisplayRef.current) return;
|
|
@@ -486,8 +473,6 @@ export const PlayerControls = memo(function PlayerControls({
|
|
|
486
473
|
|
|
487
474
|
<MuteButton
|
|
488
475
|
audioMuted={audioMuted}
|
|
489
|
-
audioAutoMuted={audioAutoMuted}
|
|
490
|
-
effectiveAudioMuted={effectiveAudioMuted}
|
|
491
476
|
controlsDisabled={controlsDisabled}
|
|
492
477
|
setAudioMuted={setAudioMuted}
|
|
493
478
|
/>
|
|
@@ -189,7 +189,7 @@ describe("usePlaybackKeyboard — mute & loop shortcuts (#905)", () => {
|
|
|
189
189
|
expect(usePlayerStore.getState().audioMuted).toBe(false);
|
|
190
190
|
});
|
|
191
191
|
|
|
192
|
-
it("M
|
|
192
|
+
it("M toggles audioMuted above 1x playback too", () => {
|
|
193
193
|
const { dispatch } = setupHook();
|
|
194
194
|
usePlayerStore.setState({ playbackRate: 2, audioMuted: false });
|
|
195
195
|
|
|
@@ -197,7 +197,7 @@ describe("usePlaybackKeyboard — mute & loop shortcuts (#905)", () => {
|
|
|
197
197
|
dispatch(keydown({ code: "KeyM", key: "m" }));
|
|
198
198
|
});
|
|
199
199
|
|
|
200
|
-
expect(usePlayerStore.getState().audioMuted).toBe(
|
|
200
|
+
expect(usePlayerStore.getState().audioMuted).toBe(true);
|
|
201
201
|
});
|
|
202
202
|
|
|
203
203
|
it("Shift+L toggles loopEnabled without starting forward shuttle", () => {
|
|
@@ -116,10 +116,7 @@ export function usePlaybackKeyboard({
|
|
|
116
116
|
if (key === "m") {
|
|
117
117
|
e.preventDefault();
|
|
118
118
|
const state = usePlayerStore.getState();
|
|
119
|
-
|
|
120
|
-
if (state.playbackRate <= 1) {
|
|
121
|
-
state.setAudioMuted(!state.audioMuted);
|
|
122
|
-
}
|
|
119
|
+
state.setAudioMuted(!state.audioMuted);
|
|
123
120
|
return;
|
|
124
121
|
}
|
|
125
122
|
if (key === "l" && e.shiftKey) {
|
|
@@ -211,7 +211,7 @@ describe("useTimelinePlayer seek hydration", () => {
|
|
|
211
211
|
});
|
|
212
212
|
|
|
213
213
|
describe("useTimelinePlayer audio controls (#835)", () => {
|
|
214
|
-
it("applies playback-rate changes immediately and
|
|
214
|
+
it("applies playback-rate changes immediately and keeps audio unmuted above 1x", () => {
|
|
215
215
|
const { api, root } = renderTimelinePlayerHarness();
|
|
216
216
|
const postMessage = vi.fn();
|
|
217
217
|
const timeScale = vi.fn();
|
|
@@ -244,7 +244,7 @@ describe("useTimelinePlayer audio controls (#835)", () => {
|
|
|
244
244
|
source: "hf-parent",
|
|
245
245
|
type: "control",
|
|
246
246
|
action: "set-muted",
|
|
247
|
-
muted:
|
|
247
|
+
muted: false,
|
|
248
248
|
}),
|
|
249
249
|
"*",
|
|
250
250
|
);
|
|
@@ -37,11 +37,7 @@ import {
|
|
|
37
37
|
parseTimelineFromDOM,
|
|
38
38
|
} from "../lib/timelineDOM";
|
|
39
39
|
import { normalizeToZones } from "../components/timelineZones";
|
|
40
|
-
import {
|
|
41
|
-
setPreviewMediaMuted,
|
|
42
|
-
setPreviewPlaybackRate,
|
|
43
|
-
shouldMutePreviewAudio,
|
|
44
|
-
} from "../lib/timelineIframeHelpers";
|
|
40
|
+
import { setPreviewMediaMuted, setPreviewPlaybackRate } from "../lib/timelineIframeHelpers";
|
|
45
41
|
import { scrubMusicAtSeek, stopScrubPreviewAudio } from "../lib/playbackScrub";
|
|
46
42
|
import { applyCachedSourceDurations, probeMissingSourceDurations } from "../lib/mediaProbe";
|
|
47
43
|
import { shouldResumeForwardPlaybackAfterSeek, shouldStopAfterSeek } from "../lib/playbackSeek";
|
|
@@ -241,13 +237,9 @@ export function useTimelinePlayer() {
|
|
|
241
237
|
}
|
|
242
238
|
} catch {}
|
|
243
239
|
}, []);
|
|
244
|
-
const applyPreviewAudioState = useCallback((
|
|
245
|
-
const { audioMuted
|
|
246
|
-
|
|
247
|
-
setPreviewMediaMuted(
|
|
248
|
-
iframeRef.current,
|
|
249
|
-
shouldMutePreviewAudio(audioMuted, effectivePlaybackRate),
|
|
250
|
-
);
|
|
240
|
+
const applyPreviewAudioState = useCallback(() => {
|
|
241
|
+
const { audioMuted } = usePlayerStore.getState();
|
|
242
|
+
setPreviewMediaMuted(iframeRef.current, audioMuted);
|
|
251
243
|
}, []);
|
|
252
244
|
const play = useCallback(() => {
|
|
253
245
|
stopRAFLoop();
|
|
@@ -285,7 +277,7 @@ export function useTimelinePlayer() {
|
|
|
285
277
|
if (initialTime !== adapter.getTime()) adapter.seek(initialTime);
|
|
286
278
|
const speed = Math.max(0.1, Math.min(4, rate));
|
|
287
279
|
applyPlaybackRate(speed);
|
|
288
|
-
applyPreviewAudioState(
|
|
280
|
+
applyPreviewAudioState();
|
|
289
281
|
let startTime = initialTime;
|
|
290
282
|
let startedAt = performance.now();
|
|
291
283
|
|
|
@@ -111,10 +111,6 @@ function postPreviewControl(
|
|
|
111
111
|
postRuntimeControlMessage(iframe.contentWindow, action, payload);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
export function shouldMutePreviewAudio(audioMuted: boolean, playbackRate: number): boolean {
|
|
115
|
-
return audioMuted || playbackRate > 1;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
114
|
export function setPreviewMediaMuted(iframe: HTMLIFrameElement | null, muted: boolean): void {
|
|
119
115
|
if (!iframe) return;
|
|
120
116
|
try {
|