Yiannis Christodoulou 085e944861 ChapterEditor: Add keyboard shortcuts for video playback controls
Implemented keyboard shortcuts for play/pause (Space), jump backward (ArrowLeft), and jump forward (ArrowRight) in the chapters editor. Shortcuts are disabled when typing in input fields to prevent interference with text entry.
2025-10-16 14:59:32 +03:00

174 lines
6.0 KiB
TypeScript

import { formatDetailedTime } from './lib/timeUtils';
import logger from './lib/logger';
import VideoPlayer from '@/components/VideoPlayer';
import TimelineControls from '@/components/TimelineControls';
import EditingTools from '@/components/EditingTools';
import ClipSegments from '@/components/ClipSegments';
import MobilePlayPrompt from '@/components/IOSPlayPrompt';
import useVideoChapters from '@/hooks/useVideoChapters';
import { useEffect } from 'react';
const App = () => {
const {
videoRef,
currentTime,
duration,
isPlaying,
setIsPlaying,
isMuted,
trimStart,
trimEnd,
splitPoints,
zoomLevel,
clipSegments,
selectedSegmentId,
hasUnsavedChanges,
historyPosition,
history,
handleTrimStartChange,
handleTrimEndChange,
handleZoomChange,
handleMobileSafeSeek,
handleSplit,
handleReset,
handleUndo,
handleRedo,
toggleMute,
handleSegmentUpdate,
handleChapterSave,
handleSelectedSegmentChange,
isMobile,
videoInitialized,
setVideoInitialized,
} = useVideoChapters();
const handlePlay = () => {
if (!videoRef.current) return;
const video = videoRef.current;
// If already playing, just pause the video
if (isPlaying) {
video.pause();
setIsPlaying(false);
logger.debug('Video paused');
return;
}
// Start playing - no boundary checking, play through entire timeline
video
.play()
.then(() => {
setIsPlaying(true);
setVideoInitialized(true);
logger.debug('Continuous playback started from:', formatDetailedTime(video.currentTime));
})
.catch((err) => {
console.error('Error playing video:', err);
});
};
// Handle keyboard shortcuts
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
// Don't handle keyboard shortcuts if user is typing in an input field
const target = event.target as HTMLElement;
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
return;
}
switch (event.code) {
case 'Space':
event.preventDefault(); // Prevent default spacebar behavior (scrolling, button activation)
handlePlay();
break;
case 'ArrowLeft':
event.preventDefault();
if (videoRef.current) {
const newTime = Math.max(currentTime - 10, 0);
handleMobileSafeSeek(newTime);
logger.debug('Jumped backward 10 seconds to:', formatDetailedTime(newTime));
}
break;
case 'ArrowRight':
event.preventDefault();
if (videoRef.current) {
const newTime = Math.min(currentTime + 10, duration);
handleMobileSafeSeek(newTime);
logger.debug('Jumped forward 10 seconds to:', formatDetailedTime(newTime));
}
break;
}
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [handlePlay, handleMobileSafeSeek, currentTime, duration, videoRef]);
return (
<div className="bg-background min-h-screen">
<MobilePlayPrompt videoRef={videoRef} onPlay={handlePlay} />
<div className="container mx-auto px-4 py-6 max-w-6xl">
{/* Video Player */}
<VideoPlayer
videoRef={videoRef}
currentTime={currentTime}
duration={duration}
isPlaying={isPlaying}
isMuted={isMuted}
onPlayPause={handlePlay}
onSeek={handleMobileSafeSeek}
onToggleMute={toggleMute}
/>
{/* Editing Tools */}
<EditingTools
onSplit={handleSplit}
onReset={handleReset}
onUndo={handleUndo}
onRedo={handleRedo}
onPlay={handlePlay}
isPlaying={isPlaying}
canUndo={historyPosition > 0}
canRedo={historyPosition < history.length - 1}
/>
{/* Timeline Controls */}
<TimelineControls
currentTime={currentTime}
duration={duration}
thumbnails={[]}
trimStart={trimStart}
trimEnd={trimEnd}
splitPoints={splitPoints}
zoomLevel={zoomLevel}
clipSegments={clipSegments}
selectedSegmentId={selectedSegmentId}
onSelectedSegmentChange={handleSelectedSegmentChange}
onSegmentUpdate={handleSegmentUpdate}
onChapterSave={handleChapterSave}
onTrimStartChange={handleTrimStartChange}
onTrimEndChange={handleTrimEndChange}
onZoomChange={handleZoomChange}
onSeek={handleMobileSafeSeek}
videoRef={videoRef}
hasUnsavedChanges={hasUnsavedChanges}
isIOSUninitialized={isMobile && !videoInitialized}
isPlaying={isPlaying}
setIsPlaying={setIsPlaying}
onPlayPause={handlePlay}
/>
{/* Clip Segments */}
<ClipSegments segments={clipSegments} selectedSegmentId={selectedSegmentId} />
</div>
</div>
);
};
export default App;