diff --git a/packages/studio/src/player/components/PlayerControls.test.ts b/packages/studio/src/player/components/PlayerControls.test.ts
index 84f23ac59..fad6e3cd6 100644
--- a/packages/studio/src/player/components/PlayerControls.test.ts
+++ b/packages/studio/src/player/components/PlayerControls.test.ts
@@ -1,5 +1,6 @@
import { describe, expect, it } from "vitest";
import { resolveSeekPercent } from "./PlayerControls";
+import { shouldMutePreviewAudio } from "../lib/timelineIframeHelpers";
describe("resolveSeekPercent", () => {
it("returns 0 when the track width is invalid", () => {
@@ -18,3 +19,19 @@ describe("resolveSeekPercent", () => {
expect(resolveSeekPercent(150, 100, 200)).toBe(0.25);
});
});
+
+describe("shouldMutePreviewAudio", () => {
+ it("mutes when the user toggled audio off", () => {
+ expect(shouldMutePreviewAudio(true, 1)).toBe(true);
+ });
+
+ it("auto-mutes above 1x playback", () => {
+ expect(shouldMutePreviewAudio(false, 1.5)).toBe(true);
+ expect(shouldMutePreviewAudio(false, 2)).toBe(true);
+ });
+
+ it("keeps audio on at 1x or slower when the user has not muted it", () => {
+ expect(shouldMutePreviewAudio(false, 1)).toBe(false);
+ expect(shouldMutePreviewAudio(false, 0.5)).toBe(false);
+ });
+});
diff --git a/packages/studio/src/player/components/PlayerControls.tsx b/packages/studio/src/player/components/PlayerControls.tsx
index 9594a91ba..027d94e7e 100644
--- a/packages/studio/src/player/components/PlayerControls.tsx
+++ b/packages/studio/src/player/components/PlayerControls.tsx
@@ -1,6 +1,7 @@
import { useRef, useState, useCallback, useEffect, memo } from "react";
import { useMountEffect } from "../../hooks/useMountEffect";
import { formatFrameTime, frameToSeconds, stepFrameTime, formatTime } from "../lib/time";
+import { shouldMutePreviewAudio } from "../lib/timelineIframeHelpers";
import { usePlayerStore, liveTime } from "../store/playerStore";
const SPEED_OPTIONS = [0.25, 0.5, 1, 1.5, 2] as const;
@@ -57,8 +58,10 @@ export const PlayerControls = memo(function PlayerControls({
const duration = usePlayerStore((s) => s.duration);
const timelineReady = usePlayerStore((s) => s.timelineReady);
const playbackRate = usePlayerStore((s) => s.playbackRate);
+ const audioMuted = usePlayerStore((s) => s.audioMuted);
const loopEnabled = usePlayerStore((s) => s.loopEnabled);
const setPlaybackRate = usePlayerStore.getState().setPlaybackRate;
+ const setAudioMuted = usePlayerStore.getState().setAudioMuted;
const setLoopEnabled = usePlayerStore.getState().setLoopEnabled;
const inPoint = usePlayerStore((s) => s.inPoint);
const outPoint = usePlayerStore((s) => s.outPoint);
@@ -84,6 +87,13 @@ export const PlayerControls = memo(function PlayerControls({
const durationRef = useRef(duration);
durationRef.current = duration;
const controlsDisabled = disabled || !timelineReady;
+ const audioAutoMuted = playbackRate > 1;
+ const effectiveAudioMuted = shouldMutePreviewAudio(audioMuted, playbackRate);
+ const muteButtonLabel = audioAutoMuted
+ ? "Audio muted above 1x speed"
+ : audioMuted
+ ? "Unmute audio"
+ : "Mute audio";
useMountEffect(() => {
const updateProgress = (t: number) => {
currentTimeRef.current = t;
@@ -420,6 +430,57 @@ export const PlayerControls = memo(function PlayerControls({
+ {/* Mute toggle */}
+
+
{/* Speed control */}