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 */}