refactor: improve playback controls by replacing inline functions with useCallback for clip manipulation and audio separation
This commit is contained in:
@ -1,4 +1,4 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect, useCallback } from "react";
|
||||||
import { usePlaybackStore } from "@/stores/playback-store";
|
import { usePlaybackStore } from "@/stores/playback-store";
|
||||||
import { useTimelineStore } from "@/stores/timeline-store";
|
import { useTimelineStore } from "@/stores/timeline-store";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@ -15,8 +15,101 @@ export const usePlaybackControls = () => {
|
|||||||
separateAudio,
|
separateAudio,
|
||||||
} = useTimelineStore();
|
} = useTimelineStore();
|
||||||
|
|
||||||
useEffect(() => {
|
const handleSplitSelectedClip = useCallback(() => {
|
||||||
const handleKeyPress = (e: KeyboardEvent) => {
|
if (selectedClips.length !== 1) {
|
||||||
|
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");
|
||||||
|
}, [selectedClips, tracks, currentTime, splitClip]);
|
||||||
|
|
||||||
|
const handleSplitAndKeepLeftCallback = useCallback(() => {
|
||||||
|
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");
|
||||||
|
}, [selectedClips, tracks, currentTime, splitAndKeepLeft]);
|
||||||
|
|
||||||
|
const handleSplitAndKeepRightCallback = useCallback(() => {
|
||||||
|
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");
|
||||||
|
}, [selectedClips, tracks, currentTime, splitAndKeepRight]);
|
||||||
|
|
||||||
|
const handleSeparateAudioCallback = useCallback(() => {
|
||||||
|
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");
|
||||||
|
}, [selectedClips, tracks, separateAudio]);
|
||||||
|
|
||||||
|
const handleKeyPress = useCallback(
|
||||||
|
(e: KeyboardEvent) => {
|
||||||
if (
|
if (
|
||||||
e.target instanceof HTMLInputElement ||
|
e.target instanceof HTMLInputElement ||
|
||||||
e.target instanceof HTMLTextAreaElement
|
e.target instanceof HTMLTextAreaElement
|
||||||
@ -44,131 +137,38 @@ export const usePlaybackControls = () => {
|
|||||||
case "q":
|
case "q":
|
||||||
if (e.ctrlKey || e.metaKey) {
|
if (e.ctrlKey || e.metaKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleSplitAndKeepLeft();
|
handleSplitAndKeepLeftCallback();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "w":
|
case "w":
|
||||||
if (e.ctrlKey || e.metaKey) {
|
if (e.ctrlKey || e.metaKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleSplitAndKeepRight();
|
handleSplitAndKeepRightCallback();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "d":
|
case "d":
|
||||||
if (e.ctrlKey || e.metaKey) {
|
if (e.ctrlKey || e.metaKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleSeparateAudio();
|
handleSeparateAudioCallback();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
[
|
||||||
const handleSplitSelectedClip = () => {
|
|
||||||
if (selectedClips.length !== 1) {
|
|
||||||
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,
|
isPlaying,
|
||||||
currentTime,
|
|
||||||
selectedClips,
|
|
||||||
tracks,
|
|
||||||
play,
|
play,
|
||||||
pause,
|
pause,
|
||||||
splitClip,
|
handleSplitSelectedClip,
|
||||||
splitAndKeepLeft,
|
handleSplitAndKeepLeftCallback,
|
||||||
splitAndKeepRight,
|
handleSplitAndKeepRightCallback,
|
||||||
separateAudio,
|
handleSeparateAudioCallback,
|
||||||
]);
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener("keydown", handleKeyPress);
|
||||||
|
return () => document.removeEventListener("keydown", handleKeyPress);
|
||||||
|
}, [handleKeyPress]);
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user