feat: enhance playback controls with keyboard shortcuts for clip manipulation and audio separation
This commit is contained in:
@ -1,18 +1,174 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { usePlaybackStore } from "@/stores/playback-store";
|
import { usePlaybackStore } from "@/stores/playback-store";
|
||||||
|
import { useTimelineStore } from "@/stores/timeline-store";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
export function usePlaybackControls() {
|
export const usePlaybackControls = () => {
|
||||||
const { toggle } = usePlaybackStore();
|
const { isPlaying, currentTime, play, pause, seek } = usePlaybackStore();
|
||||||
|
|
||||||
|
const {
|
||||||
|
selectedClips,
|
||||||
|
tracks,
|
||||||
|
splitClip,
|
||||||
|
splitAndKeepLeft,
|
||||||
|
splitAndKeepRight,
|
||||||
|
separateAudio,
|
||||||
|
} = useTimelineStore();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyPress = (e: KeyboardEvent) => {
|
||||||
if (e.code === "Space" && e.target === document.body) {
|
if (
|
||||||
|
e.target instanceof HTMLInputElement ||
|
||||||
|
e.target instanceof HTMLTextAreaElement
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (e.key) {
|
||||||
|
case " ":
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
toggle();
|
if (isPlaying) {
|
||||||
|
pause();
|
||||||
|
} else {
|
||||||
|
play();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "s":
|
||||||
|
if (e.ctrlKey || e.metaKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
handleSplitSelectedClip();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "q":
|
||||||
|
if (e.ctrlKey || e.metaKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
handleSplitAndKeepLeft();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "w":
|
||||||
|
if (e.ctrlKey || e.metaKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
handleSplitAndKeepRight();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "d":
|
||||||
|
if (e.ctrlKey || e.metaKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
handleSeparateAudio();
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener("keydown", handleKeyDown);
|
const handleSplitSelectedClip = () => {
|
||||||
return () => document.removeEventListener("keydown", handleKeyDown);
|
if (selectedClips.length !== 1) {
|
||||||
}, [toggle]);
|
toast.error("Select exactly one clip to split");
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { trackId, clipId } = selectedClips[0];
|
||||||
|
const track = tracks.find((t) => t.id === trackId);
|
||||||
|
const clip = track?.clips.find((c) => c.id === clipId);
|
||||||
|
|
||||||
|
if (!clip) return;
|
||||||
|
|
||||||
|
const effectiveStart = clip.startTime;
|
||||||
|
const effectiveEnd =
|
||||||
|
clip.startTime + (clip.duration - clip.trimStart - clip.trimEnd);
|
||||||
|
|
||||||
|
if (currentTime <= effectiveStart || currentTime >= effectiveEnd) {
|
||||||
|
toast.error("Playhead must be within selected clip");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
splitClip(trackId, clipId, currentTime);
|
||||||
|
toast.success("Clip split at playhead");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSplitAndKeepLeft = () => {
|
||||||
|
if (selectedClips.length !== 1) {
|
||||||
|
toast.error("Select exactly one clip");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { trackId, clipId } = selectedClips[0];
|
||||||
|
const track = tracks.find((t) => t.id === trackId);
|
||||||
|
const clip = track?.clips.find((c) => c.id === clipId);
|
||||||
|
|
||||||
|
if (!clip) return;
|
||||||
|
|
||||||
|
const effectiveStart = clip.startTime;
|
||||||
|
const effectiveEnd =
|
||||||
|
clip.startTime + (clip.duration - clip.trimStart - clip.trimEnd);
|
||||||
|
|
||||||
|
if (currentTime <= effectiveStart || currentTime >= effectiveEnd) {
|
||||||
|
toast.error("Playhead must be within selected clip");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
splitAndKeepLeft(trackId, clipId, currentTime);
|
||||||
|
toast.success("Split and kept left portion");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSplitAndKeepRight = () => {
|
||||||
|
if (selectedClips.length !== 1) {
|
||||||
|
toast.error("Select exactly one clip");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { trackId, clipId } = selectedClips[0];
|
||||||
|
const track = tracks.find((t) => t.id === trackId);
|
||||||
|
const clip = track?.clips.find((c) => c.id === clipId);
|
||||||
|
|
||||||
|
if (!clip) return;
|
||||||
|
|
||||||
|
const effectiveStart = clip.startTime;
|
||||||
|
const effectiveEnd =
|
||||||
|
clip.startTime + (clip.duration - clip.trimStart - clip.trimEnd);
|
||||||
|
|
||||||
|
if (currentTime <= effectiveStart || currentTime >= effectiveEnd) {
|
||||||
|
toast.error("Playhead must be within selected clip");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
splitAndKeepRight(trackId, clipId, currentTime);
|
||||||
|
toast.success("Split and kept right portion");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSeparateAudio = () => {
|
||||||
|
if (selectedClips.length !== 1) {
|
||||||
|
toast.error("Select exactly one video clip to separate audio");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { trackId, clipId } = selectedClips[0];
|
||||||
|
const track = tracks.find((t) => t.id === trackId);
|
||||||
|
|
||||||
|
if (!track || track.type !== "video") {
|
||||||
|
toast.error("Select a video clip to separate audio");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
separateAudio(trackId, clipId);
|
||||||
|
toast.success("Audio separated to audio track");
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("keydown", handleKeyPress);
|
||||||
|
return () => document.removeEventListener("keydown", handleKeyPress);
|
||||||
|
}, [
|
||||||
|
isPlaying,
|
||||||
|
currentTime,
|
||||||
|
selectedClips,
|
||||||
|
tracks,
|
||||||
|
play,
|
||||||
|
pause,
|
||||||
|
splitClip,
|
||||||
|
splitAndKeepLeft,
|
||||||
|
splitAndKeepRight,
|
||||||
|
separateAudio,
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
Reference in New Issue
Block a user