@hyperframes/studio 0.7.65 → 0.7.67-alpha.0

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.7.65",
3
+ "version": "0.7.67-alpha.0",
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/parsers": "0.7.65",
50
- "@hyperframes/player": "0.7.65",
51
- "@hyperframes/core": "0.7.65",
52
- "@hyperframes/studio-server": "0.7.65",
53
- "@hyperframes/sdk": "0.7.65"
49
+ "@hyperframes/core": "0.7.67-alpha.0",
50
+ "@hyperframes/parsers": "0.7.67-alpha.0",
51
+ "@hyperframes/studio-server": "0.7.67-alpha.0",
52
+ "@hyperframes/player": "0.7.67-alpha.0",
53
+ "@hyperframes/sdk": "0.7.67-alpha.0"
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.65"
68
+ "@hyperframes/producer": "0.7.67-alpha.0"
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 = audioAutoMuted
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
- if (!audioAutoMuted) {
85
- trackStudioEvent("playback", { action: "mute_toggle", muted: !audioMuted });
86
- setAudioMuted(!audioMuted);
87
- }
75
+ trackStudioEvent("playback", { action: "mute_toggle", muted: !audioMuted });
76
+ setAudioMuted(!audioMuted);
88
77
  }}
89
- disabled={controlsDisabled || audioAutoMuted}
78
+ disabled={controlsDisabled}
90
79
  aria-label={label}
91
- aria-pressed={effectiveAudioMuted}
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
- effectiveAudioMuted
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
- } ${audioAutoMuted ? "opacity-70" : ""}`}
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
- {effectiveAudioMuted ? (
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 does NOT toggle audioMuted above 1x playback (matches button gating)", () => {
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(false);
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
- // Audio is force-muted above 1x playback — match the mute button's gating.
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 auto-mutes audio above 1x", () => {
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: true,
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((playbackRateOverride?: number) => {
245
- const { audioMuted, playbackRate } = usePlayerStore.getState();
246
- const effectivePlaybackRate = playbackRateOverride ?? playbackRate;
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(speed);
280
+ applyPreviewAudioState();
289
281
  let startTime = initialTime;
290
282
  let startedAt = performance.now();
291
283
 
@@ -55,7 +55,6 @@ export {
55
55
  autoHealMissingCompositionIds,
56
56
  setPreviewMediaMuted,
57
57
  setPreviewPlaybackRate,
58
- shouldMutePreviewAudio,
59
58
  resolveIframe,
60
59
  buildMissingCompositionElements,
61
60
  } from "./timelineIframeHelpers";
@@ -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 {