play segments (duplicate of preview)

This commit is contained in:
Yiannis Christodoulou 2025-05-22 03:41:21 +03:00
parent 8390aa3ae0
commit 39fcf3c97c
3 changed files with 230 additions and 91 deletions

View File

@ -42,6 +42,8 @@ const App = () => {
isMobile, isMobile,
videoInitialized, videoInitialized,
setVideoInitialized, setVideoInitialized,
isPlayingSegments,
handlePlaySegments,
} = useVideoTrimmer(); } = useVideoTrimmer();
// Function to play from the beginning // Function to play from the beginning
@ -250,9 +252,11 @@ const App = () => {
onUndo={handleUndo} onUndo={handleUndo}
onRedo={handleRedo} onRedo={handleRedo}
onPreview={handlePreview} onPreview={handlePreview}
onPlaySegments={handlePlaySegments}
onPlay={handlePlay} onPlay={handlePlay}
isPreviewMode={isPreviewMode} isPreviewMode={isPreviewMode}
isPlaying={isPlaying} isPlaying={isPlaying}
isPlayingSegments={isPlayingSegments}
canUndo={historyPosition > 0} canUndo={historyPosition > 0}
canRedo={historyPosition < history.length - 1} canRedo={historyPosition < history.length - 1}
/> />

View File

@ -6,11 +6,13 @@ interface EditingToolsProps {
onUndo: () => void; onUndo: () => void;
onRedo: () => void; onRedo: () => void;
onPreview: () => void; onPreview: () => void;
onPlaySegments: () => void;
onPlay: () => void; onPlay: () => void;
canUndo: boolean; canUndo: boolean;
canRedo: boolean; canRedo: boolean;
isPreviewMode?: boolean; isPreviewMode?: boolean;
isPlaying?: boolean; isPlaying?: boolean;
isPlayingSegments?: boolean;
} }
const EditingTools = ({ const EditingTools = ({
@ -19,11 +21,13 @@ const EditingTools = ({
onUndo, onUndo,
onRedo, onRedo,
onPreview, onPreview,
onPlaySegments,
onPlay, onPlay,
canUndo, canUndo,
canRedo, canRedo,
isPreviewMode = false, isPreviewMode = false,
isPlaying = false, isPlaying = false,
isPlayingSegments = false,
}: EditingToolsProps) => { }: EditingToolsProps) => {
// Handle play button click with iOS fix // Handle play button click with iOS fix
const handlePlay = () => { const handlePlay = () => {
@ -41,6 +45,35 @@ const EditingTools = ({
<div className="flex-container single-row"> <div className="flex-container single-row">
{/* Left side - Play buttons group */} {/* Left side - Play buttons group */}
<div className="button-group play-buttons-group"> <div className="button-group play-buttons-group">
{/* Play Segments button */}
<button
className="button segments-button"
onClick={onPlaySegments}
data-tooltip={isPlayingSegments ? "Stop segments playback" : "Play all segments once"}
style={{ fontSize: '0.875rem' }}
>
{isPlayingSegments ? (
<>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10" />
<line x1="10" y1="15" x2="10" y2="9" />
<line x1="14" y1="15" x2="14" y2="9" />
</svg>
<span className="full-text">Stop Segments</span>
<span className="short-text">Stop</span>
</>
) : (
<>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10" />
<polygon points="10 8 16 12 10 16 10 8" />
</svg>
<span className="full-text">Play Segments</span>
<span className="short-text">Segments</span>
</>
)}
</button>
{/* Play Preview button */} {/* Play Preview button */}
<button <button
className="button preview-button" className="button preview-button"

View File

@ -42,6 +42,10 @@ const useVideoTrimmer = () => {
// Track unsaved changes // Track unsaved changes
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
// State for playing segments
const [isPlayingSegments, setIsPlayingSegments] = useState(false);
const [currentSegmentIndex, setCurrentSegmentIndex] = useState(0);
// Monitor for history changes // Monitor for history changes
useEffect(() => { useEffect(() => {
if (history.length > 0) { if (history.length > 0) {
@ -550,68 +554,149 @@ const useVideoTrimmer = () => {
useEffect(() => { useEffect(() => {
if (!isPreviewMode || !videoRef.current) return; if (!isPreviewMode || !videoRef.current) return;
// logger.debug("Preview mode effect running, previewSegmentIndex:", previewSegmentIndex);
// Sort segments by start time // Sort segments by start time
const orderedSegments = [...clipSegments].sort((a, b) => a.startTime - b.startTime); const orderedSegments = [...clipSegments].sort((a, b) => a.startTime - b.startTime);
if (orderedSegments.length === 0) return;
// Check if we've reached the end of the current segment const video = videoRef.current;
// Function to handle segment playback
const handleSegmentPlayback = () => { const handleSegmentPlayback = () => {
if (!isPreviewMode || !videoRef.current) return; if (!isPreviewMode || !video) return;
const currentSegment = orderedSegments[previewSegmentIndex]; const currentSegment = orderedSegments[previewSegmentIndex];
if (!currentSegment) return; if (!currentSegment) return;
// Make sure we're inside the current segment const currentTime = video.currentTime;
const currentTime = videoRef.current.currentTime;
// If the current time is before the segment start, move to the segment start // If we're before the current segment's start, jump to it
if (currentTime < currentSegment.startTime) { if (currentTime < currentSegment.startTime) {
videoRef.current.currentTime = currentSegment.startTime; video.currentTime = currentSegment.startTime;
return; return;
} }
// If we've reached the end of the current segment // If we've reached the end of the current segment
if (currentTime >= currentSegment.endTime - 0.05) { // Slightly before the end to ensure smooth transition if (currentTime >= currentSegment.endTime - 0.01) { // Small threshold to ensure smooth transition
// Move to the next segment if available // Move to the next segment if available
if (previewSegmentIndex < orderedSegments.length - 1) { if (previewSegmentIndex < orderedSegments.length - 1) {
// Play next segment
const nextSegment = orderedSegments[previewSegmentIndex + 1]; const nextSegment = orderedSegments[previewSegmentIndex + 1];
video.currentTime = nextSegment.startTime;
// Play through the next segment immediately
videoRef.current.currentTime = nextSegment.startTime;
setPreviewSegmentIndex(previewSegmentIndex + 1); setPreviewSegmentIndex(previewSegmentIndex + 1);
logger.debug("Moving to next segment in preview:", nextSegment.name,
"from", formatDetailedTime(currentSegment.endTime),
"to", formatDetailedTime(nextSegment.startTime));
// Ensure playback continues logger.debug("Preview: Moving to next segment", {
videoRef.current.play().catch(err => { from: formatDetailedTime(currentSegment.endTime),
console.error("Error continuing preview playback:", err); to: formatDetailedTime(nextSegment.startTime),
segmentIndex: previewSegmentIndex + 1
}); });
} else { } else {
// End of all segments, loop back to the first segment // Loop back to first segment
logger.debug("End of all segments in preview, looping back to first"); logger.debug("Preview: Looping back to first segment");
videoRef.current.currentTime = orderedSegments[0].startTime; video.currentTime = orderedSegments[0].startTime;
setPreviewSegmentIndex(0); setPreviewSegmentIndex(0);
// Ensure playback continues
videoRef.current.play().catch(err => {
console.error("Error restarting preview playback:", err);
});
} }
// Ensure playback continues
video.play().catch(err => {
console.error("Error continuing preview playback:", err);
});
} }
}; };
// Add event listener for timeupdate to check segment boundaries // Add event listener for timeupdate to check segment boundaries
videoRef.current.addEventListener('timeupdate', handleSegmentPlayback); video.addEventListener('timeupdate', handleSegmentPlayback);
// Start playing if not already playing
if (video.paused) {
video.currentTime = orderedSegments[previewSegmentIndex].startTime;
video.play().catch(err => {
console.error("Error starting preview playback:", err);
});
}
return () => { return () => {
if (videoRef.current) { if (video) {
videoRef.current.removeEventListener('timeupdate', handleSegmentPlayback); video.removeEventListener('timeupdate', handleSegmentPlayback);
} }
}; };
}, [isPreviewMode, previewSegmentIndex, clipSegments]); }, [isPreviewMode, previewSegmentIndex, clipSegments]);
// Handle starting preview mode
const handleStartPreview = () => {
const video = videoRef.current;
if (!video || clipSegments.length === 0) return;
// If preview is already active, do nothing
if (isPreviewMode) {
return;
}
// If normal playback is happening, pause it
if (isPlaying) {
video.pause();
setIsPlaying(false);
}
// Sort segments by start time
const orderedSegments = [...clipSegments].sort((a, b) => a.startTime - b.startTime);
if (orderedSegments.length === 0) return;
// Set the preview mode flag
setIsPreviewMode(true);
logger.debug("Entering preview mode");
// Set the first segment as the current one in the preview sequence
setPreviewSegmentIndex(0);
// Move to the start of the first segment
video.currentTime = orderedSegments[0].startTime;
};
// Handle playing/stopping preview mode
const handlePreview = () => {
const video = videoRef.current;
if (!video || clipSegments.length === 0) return;
// If preview is already active, turn it off
if (isPreviewMode) {
setIsPreviewMode(false);
// Always pause the video when exiting preview mode
video.pause();
setIsPlaying(false);
logger.debug("Exiting preview mode - video paused");
return;
}
// Sort segments by start time
const orderedSegments = [...clipSegments].sort((a, b) => a.startTime - b.startTime);
if (orderedSegments.length === 0) return;
// Set the preview mode flag
setIsPreviewMode(true);
logger.debug("Entering preview mode");
// Set the first segment as the current one in the preview sequence
setPreviewSegmentIndex(0);
// Start preview mode by playing the first segment
video.currentTime = orderedSegments[0].startTime;
// Start playback
video.play()
.then(() => {
setIsPlaying(true);
logger.debug("Preview started successfully");
})
.catch(err => {
console.error("Error starting preview:", err);
setIsPreviewMode(false);
setIsPlaying(false);
});
};
// Handle trim start change // Handle trim start change
const handleTrimStartChange = (time: number) => { const handleTrimStartChange = (time: number) => {
setTrimStart(time); setTrimStart(time);
@ -725,60 +810,6 @@ const useVideoTrimmer = () => {
} }
}; };
// Handle playing the preview of all segments
const handlePreview = () => {
const video = videoRef.current;
if (!video || clipSegments.length === 0) return;
// If preview is already active, turn it off
if (isPreviewMode) {
setIsPreviewMode(false);
// Always pause the video when exiting preview mode
video.pause();
setIsPlaying(false);
logger.debug("Exiting preview mode - video paused");
return;
}
// If normal playback is happening, remember that state but pause it
const wasPlaying = isPlaying;
if (wasPlaying) {
video.pause();
}
// Sort segments by start time to ensure proper playback order
const orderedSegments = [...clipSegments].sort((a, b) => a.startTime - b.startTime);
if (orderedSegments.length === 0) return;
// Set the preview mode flag before starting playback
setIsPreviewMode(true);
logger.debug("Entering preview mode");
// Set the first segment as the current one in the preview sequence
setPreviewSegmentIndex(0);
// Start preview mode by playing the first segment
video.currentTime = orderedSegments[0].startTime;
// Force a small delay before playing to ensure the preview mode state is set
setTimeout(() => {
if (videoRef.current) {
videoRef.current.play()
.then(() => {
logger.debug("Preview started successfully");
setIsPlaying(true);
})
.catch(err => {
console.error("Error starting preview:", err);
setIsPreviewMode(false); // Reset preview mode if play fails
setIsPlaying(false);
});
}
}, 50); // Short delay to ensure state updates have processed
};
// Handle zoom level change // Handle zoom level change
const handleZoomChange = (level: number) => { const handleZoomChange = (level: number) => {
setZoomLevel(level); setZoomLevel(level);
@ -947,6 +978,75 @@ const useVideoTrimmer = () => {
// Add videoInitialized state // Add videoInitialized state
const [videoInitialized, setVideoInitialized] = useState(false); const [videoInitialized, setVideoInitialized] = useState(false);
// Effect to handle segments playback
useEffect(() => {
if (!isPlayingSegments || !videoRef.current) return;
const video = videoRef.current;
const orderedSegments = [...clipSegments].sort((a, b) => a.startTime - b.startTime);
const handleSegmentsPlayback = () => {
const currentSegment = orderedSegments[currentSegmentIndex];
if (!currentSegment) return;
const currentTime = video.currentTime;
// If we're before the current segment's start, jump to it
if (currentTime < currentSegment.startTime) {
video.currentTime = currentSegment.startTime;
return;
}
// If we've reached the end of the current segment
if (currentTime >= currentSegment.endTime - 0.01) {
if (currentSegmentIndex < orderedSegments.length - 1) {
// Move to next segment
const nextSegment = orderedSegments[currentSegmentIndex + 1];
video.currentTime = nextSegment.startTime;
setCurrentSegmentIndex(currentSegmentIndex + 1);
video.play().catch(console.error);
} else {
// End of all segments
video.pause();
setIsPlayingSegments(false);
setCurrentSegmentIndex(0);
video.removeEventListener('timeupdate', handleSegmentsPlayback);
}
}
};
video.addEventListener('timeupdate', handleSegmentsPlayback);
// Start playing if not already playing
if (video.paused && orderedSegments.length > 0) {
video.currentTime = orderedSegments[0].startTime;
video.play().catch(console.error);
}
return () => {
video.removeEventListener('timeupdate', handleSegmentsPlayback);
};
}, [isPlayingSegments, currentSegmentIndex, clipSegments]);
// Handle play segments
const handlePlaySegments = () => {
const video = videoRef.current;
if (!video || clipSegments.length === 0) return;
if (isPlayingSegments) {
// Stop segments playback
video.pause();
setIsPlayingSegments(false);
setCurrentSegmentIndex(0);
} else {
// Start segments playback
setIsPlayingSegments(true);
setCurrentSegmentIndex(0);
video.currentTime = clipSegments[0].startTime;
video.play().catch(console.error);
}
};
return { return {
videoRef, videoRef,
currentTime, currentTime,
@ -955,6 +1055,7 @@ const useVideoTrimmer = () => {
setIsPlaying, setIsPlaying,
isMuted, isMuted,
isPreviewMode, isPreviewMode,
isPlayingSegments,
thumbnails, thumbnails,
trimStart, trimStart,
trimEnd, trimEnd,
@ -973,6 +1074,7 @@ const useVideoTrimmer = () => {
handleUndo, handleUndo,
handleRedo, handleRedo,
handlePreview, handlePreview,
handlePlaySegments,
toggleMute, toggleMute,
handleSave, handleSave,
handleSaveACopy, handleSaveACopy,