Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/studio/src/player/components/PlayerControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ const SHORTCUT_SECTIONS = [
title: "Playback",
hints: [
{ key: "Space", label: "Play / Pause" },
{ key: "M", label: "Mute / Unmute" },
{ key: "J", label: "Play backward" },
{ key: "K", label: "Stop" },
{ key: "L", label: "Play forward" },
{ key: "Shift+L", label: "Toggle loop" },
{ key: "Ctrl+L", label: "Toggle loop" },
{ key: "←/→", label: "Step 1 frame" },
{ key: "⇧←/⇧→", label: "Step 10 frames" },
],
Expand Down
45 changes: 44 additions & 1 deletion packages/studio/src/player/hooks/usePlaybackKeyboard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,17 @@ function setupHook(): HookHandle {
};
}

function keydown(init: { code: string; key: string; shiftKey?: boolean }): KeyboardEvent {
function keydown(init: {
code: string;
key: string;
shiftKey?: boolean;
ctrlKey?: boolean;
}): KeyboardEvent {
return new KeyboardEvent("keydown", {
code: init.code,
key: init.key,
shiftKey: init.shiftKey ?? false,
ctrlKey: init.ctrlKey ?? false,
cancelable: true,
});
}
Expand Down Expand Up @@ -171,4 +177,41 @@ describe("usePlaybackKeyboard — keyboard layout independence (#834)", () => {

expect(spies.play).toHaveBeenCalledTimes(1);
});

it("M toggles preview audio mute", () => {
const { dispatch } = setupHook();
usePlayerStore.setState({ audioMuted: false });

act(() => {
dispatch(keydown({ code: "KeyM", key: "m" }));
});

expect(usePlayerStore.getState().audioMuted).toBe(true);
});

it("Shift+L toggles loop instead of forward shuttle", () => {
const { dispatch, spies } = setupHook();
usePlayerStore.setState({ loopEnabled: false });

act(() => {
dispatch(keydown({ code: "KeyL", key: "L", shiftKey: true }));
});

expect(usePlayerStore.getState().loopEnabled).toBe(true);
expect(spies.play).not.toHaveBeenCalled();
});

it("Ctrl+L toggles loop and prevents the browser location shortcut", () => {
const { dispatch, spies } = setupHook();
usePlayerStore.setState({ loopEnabled: false });
const event = keydown({ code: "KeyL", key: "l", ctrlKey: true });

act(() => {
dispatch(event);
});

expect(usePlayerStore.getState().loopEnabled).toBe(true);
expect(event.defaultPrevented).toBe(true);
expect(spies.play).not.toHaveBeenCalled();
});
});
31 changes: 28 additions & 3 deletions packages/studio/src/player/hooks/usePlaybackKeyboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@

import { useRef, useCallback } from "react";
import { useCaptionStore } from "../../captions/store";
import { shouldIgnorePlaybackShortcutEvent, SHUTTLE_SPEEDS } from "../lib/playbackShortcuts";
import {
shouldIgnorePlaybackShortcutEvent,
shouldIgnorePlaybackShortcutTarget,
SHUTTLE_SPEEDS,
} from "../lib/playbackShortcuts";
import { usePlayerStore } from "../store/playerStore";
import { stepFrameTime, STUDIO_PREVIEW_FPS } from "../lib/time";
import type { PlaybackAdapter } from "../lib/playbackTypes";
Expand Down Expand Up @@ -78,10 +82,27 @@ export function usePlaybackKeyboard({
}
}, [play, pause]);

const toggleAudioMuted = useCallback(() => {
const { audioMuted, setAudioMuted } = usePlayerStore.getState();
setAudioMuted(!audioMuted);
}, []);

const toggleLoop = useCallback(() => {
const { loopEnabled, setLoopEnabled } = usePlayerStore.getState();
setLoopEnabled(!loopEnabled);
}, []);

const handlePlaybackKeyDown = useCallback(
(e: KeyboardEvent) => {
if (e.defaultPrevented) return;
const captionState = useCaptionStore.getState();
if (shouldIgnorePlaybackShortcutTarget(e.target)) return;
const key = e.key.toLowerCase();
if (key === "l" && (e.ctrlKey || e.shiftKey) && !e.altKey && !e.metaKey) {
e.preventDefault();
toggleLoop();
return;
}
if (
shouldIgnorePlaybackShortcutEvent(e, {
isCaptionEditMode: captionState.isEditMode,
Expand All @@ -90,7 +111,6 @@ export function usePlaybackKeyboard({
) {
return;
}
const key = e.key.toLowerCase();
pressedKeysRef.current.add(key);
if (e.code === "Space") {
e.preventDefault();
Expand All @@ -108,6 +128,11 @@ export function usePlaybackKeyboard({
return;
}
if (e.repeat) return;
if (key === "m") {
e.preventDefault();
toggleAudioMuted();
return;
}
if (key === "k") {
e.preventDefault();
pause();
Expand Down Expand Up @@ -157,7 +182,7 @@ export function usePlaybackKeyboard({
return;
}
},
[pause, shuttle, stepFrames, togglePlay, getAdapter, seek],
[pause, shuttle, stepFrames, toggleAudioMuted, toggleLoop, togglePlay, getAdapter, seek],
);

const handlePlaybackKeyUp = useCallback((e: KeyboardEvent) => {
Expand Down