diff --git a/frontend-tools/chapters-editor/client/src/App.tsx b/frontend-tools/chapters-editor/client/src/App.tsx index 0cecab7e..669803d6 100644 --- a/frontend-tools/chapters-editor/client/src/App.tsx +++ b/frontend-tools/chapters-editor/client/src/App.tsx @@ -39,8 +39,6 @@ const App = () => { isMobile, videoInitialized, setVideoInitialized, - isPlayingSegments, - handlePlaySegments, } = useVideoChapters(); const handlePlay = () => { @@ -52,156 +50,17 @@ const App = () => { if (isPlaying) { video.pause(); setIsPlaying(false); + logger.debug('Video paused'); return; } - const currentPosition = Number(video.currentTime.toFixed(6)); - - // Find the next stopping point based on current position - let stopTime = duration; - let currentSegment = null; - let nextSegment = null; - - // Sort segments by start time to ensure correct order - const sortedSegments = [...clipSegments].sort((a, b) => a.startTime - b.startTime); - - // First, check if we're inside a segment or exactly at its start/end - currentSegment = sortedSegments.find((seg) => { - const segStartTime = Number(seg.startTime.toFixed(6)); - const segEndTime = Number(seg.endTime.toFixed(6)); - - // Check if we're inside the segment - if (currentPosition > segStartTime && currentPosition < segEndTime) { - return true; - } - // Check if we're exactly at the start - if (currentPosition === segStartTime) { - return true; - } - // Check if we're exactly at the end - if (currentPosition === segEndTime) { - // If we're at the end of a segment, we should look for the next one - return false; - } - return false; - }); - - // If we're not in a segment, find the next segment - if (!currentSegment) { - nextSegment = sortedSegments.find((seg) => { - const segStartTime = Number(seg.startTime.toFixed(6)); - return segStartTime > currentPosition; - }); - } - - // Determine where to stop based on position - if (currentSegment) { - // If we're in a segment, stop at its end - stopTime = Number(currentSegment.endTime.toFixed(6)); - } else if (nextSegment) { - // If we're in a cutaway and there's a next segment, stop at its start - stopTime = Number(nextSegment.startTime.toFixed(6)); - } - - // Create a boundary checker function with high precision - const checkBoundary = () => { - if (!video) return; - - const currentPosition = Number(video.currentTime.toFixed(6)); - const timeLeft = Number((stopTime - currentPosition).toFixed(6)); - - // If we've reached or passed the boundary - if (timeLeft <= 0 || currentPosition >= stopTime) { - // First pause playback - video.pause(); - - // Force exact position with multiple verification attempts - const setExactPosition = () => { - if (!video) return; - - // Set to exact boundary time - video.currentTime = stopTime; - handleMobileSafeSeek(stopTime); - - const actualPosition = Number(video.currentTime.toFixed(6)); - const difference = Number(Math.abs(actualPosition - stopTime).toFixed(6)); - - logger.debug('Position verification:', { - target: formatDetailedTime(stopTime), - actual: formatDetailedTime(actualPosition), - difference: difference, - }); - - // If we're not exactly at the target position, try one more time - if (difference > 0) { - video.currentTime = stopTime; - handleMobileSafeSeek(stopTime); - } - }; - - // Multiple attempts to ensure precision, with increasing delays - setExactPosition(); - setTimeout(setExactPosition, 5); // Quick first retry - setTimeout(setExactPosition, 10); // Second retry - setTimeout(setExactPosition, 20); // Third retry if needed - setTimeout(setExactPosition, 50); // Final verification - - // Remove our boundary checker - video.removeEventListener('timeupdate', checkBoundary); - setIsPlaying(false); - - // Log the final position for debugging - logger.debug('Stopped at position:', { - target: formatDetailedTime(stopTime), - actual: formatDetailedTime(video.currentTime), - type: currentSegment ? 'segment end' : nextSegment ? 'next segment start' : 'end of video', - segment: currentSegment - ? { - id: currentSegment.id, - start: formatDetailedTime(currentSegment.startTime), - end: formatDetailedTime(currentSegment.endTime), - } - : null, - nextSegment: nextSegment - ? { - id: nextSegment.id, - start: formatDetailedTime(nextSegment.startTime), - end: formatDetailedTime(nextSegment.endTime), - } - : null, - }); - - return; - } - }; - - // Start our boundary checker - video.addEventListener('timeupdate', checkBoundary); - - // Start playing + // Start playing - no boundary checking, play through entire timeline video .play() .then(() => { setIsPlaying(true); setVideoInitialized(true); - logger.debug('Playback started:', { - from: formatDetailedTime(currentPosition), - to: formatDetailedTime(stopTime), - currentSegment: currentSegment - ? { - id: currentSegment.id, - start: formatDetailedTime(currentSegment.startTime), - end: formatDetailedTime(currentSegment.endTime), - } - : 'None', - nextSegment: nextSegment - ? { - id: nextSegment.id, - start: formatDetailedTime(nextSegment.startTime), - end: formatDetailedTime(nextSegment.endTime), - } - : 'None', - }); + logger.debug('Continuous playback started from:', formatDetailedTime(video.currentTime)); }) .catch((err) => { console.error('Error playing video:', err); @@ -231,10 +90,8 @@ const App = () => { onReset={handleReset} onUndo={handleUndo} onRedo={handleRedo} - onPlaySegments={handlePlaySegments} onPlay={handlePlay} isPlaying={isPlaying} - isPlayingSegments={isPlayingSegments} canUndo={historyPosition > 0} canRedo={historyPosition < history.length - 1} /> @@ -243,6 +100,7 @@ const App = () => { { isPlaying={isPlaying} setIsPlaying={setIsPlaying} onPlayPause={handlePlay} - isPlayingSegments={isPlayingSegments} /> {/* Clip Segments */} diff --git a/frontend-tools/chapters-editor/client/src/components/TimelineControls.tsx b/frontend-tools/chapters-editor/client/src/components/TimelineControls.tsx index e91790bc..89e4a8b4 100644 --- a/frontend-tools/chapters-editor/client/src/components/TimelineControls.tsx +++ b/frontend-tools/chapters-editor/client/src/components/TimelineControls.tsx @@ -561,111 +561,11 @@ const TimelineControls = ({ } }, [currentTime, zoomLevel, duration, selectedSegmentId, showEmptySpaceTooltip, currentTimePercent]); - // Effect to check active segment boundaries during playback + // Effect to check active segment boundaries during playback - DISABLED for continuous playback useEffect(() => { - // Skip if no video or no active segment - const video = videoRef.current; - if (!video || !activeSegment || !isPlayingSegment) { - // Log why we're skipping - if (!video) logger.debug('Skipping segment boundary check - no video element'); - else if (!activeSegment) logger.debug('Skipping segment boundary check - no active segment'); - else if (!isPlayingSegment) logger.debug('Skipping segment boundary check - not in segment playback mode'); - return; - } - - // Skip boundary checking when playing all segments - if (isPlayingSegments) { - logger.debug('Skipping segment boundary check during segments playback'); - return; - } - - logger.debug( - 'Segment boundary check ACTIVATED for segment:', - activeSegment.id, - 'Start:', - formatDetailedTime(activeSegment.startTime), - 'End:', - formatDetailedTime(activeSegment.endTime) - ); - - const handleTimeUpdate = () => { - const timeLeft = activeSegment.endTime - video.currentTime; - - // Log every second to show we're actually checking - if (Math.round(timeLeft * 10) % 10 === 0) { - logger.debug( - 'Segment playback - time remaining:', - formatDetailedTime(timeLeft), - 'Current:', - formatDetailedTime(video.currentTime), - 'End:', - formatDetailedTime(activeSegment.endTime), - 'ContinuePastBoundary:', - continuePastBoundary - ); - } - - // If we've already passed the segment end, stop immediately - if (video.currentTime > activeSegment.endTime) { - video.pause(); - video.currentTime = activeSegment.endTime; - setIsPlayingSegment(false); - // Reset continuePastBoundary when stopping at boundary - setContinuePastBoundary(false); - logger.debug( - 'Passed segment end - setting back to exact boundary:', - formatDetailedTime(activeSegment.endTime) - ); - return; - } - - // If we've reached very close to the end of the active segment - // Use a small tolerance to ensure we stop as close as possible to boundary - // But not exactly at the boundary to avoid rounding errors - if (activeSegment.endTime - video.currentTime < 0.05) { - if (!continuePastBoundary) { - // Pause playback and set the time exactly at the end boundary - video.pause(); - video.currentTime = activeSegment.endTime; - setIsPlayingSegment(false); - logger.debug('Paused at segment end boundary:', formatDetailedTime(activeSegment.endTime)); - - // Look for the next segment after this one (for potential continuation) - const sortedSegments = [...clipSegments].sort((a, b) => a.startTime - b.startTime); - const nextSegment = sortedSegments.find((seg) => seg.startTime > activeSegment.endTime); - - // If there's a next segment immediately after this one, update the tooltip to show that segment - if (nextSegment && Math.abs(nextSegment.startTime - activeSegment.endTime) < 0.1) { - logger.debug('Found adjacent next segment:', nextSegment.id); - setSelectedSegmentId(nextSegment.id); - setActiveSegment(nextSegment); - setDisplayTime(nextSegment.startTime); - setClickedTime(nextSegment.startTime); - video.currentTime = nextSegment.startTime; - } - } else { - // We're continuing past the boundary - logger.debug('Continuing past segment boundary:', formatDetailedTime(activeSegment.endTime)); - - // Reset the flag after we've passed the boundary to ensure we stop at the next boundary - if (video.currentTime > activeSegment.endTime) { - setContinuePastBoundary(false); - logger.debug('Past segment boundary - resetting continuePastBoundary flag'); - // Remove the active segment to avoid boundary checking until next segment is activated - setActiveSegment(null); - sessionStorage.removeItem('continuingPastSegment'); - } - } - } - }; - - // Add event listener for timeupdate to check segment boundaries - video.addEventListener('timeupdate', handleTimeUpdate); - - return () => { - video.removeEventListener('timeupdate', handleTimeUpdate); - logger.debug('Segment boundary check DEACTIVATED'); - }; + // Boundary checking disabled - allow continuous playback through all segments + logger.debug('Segment boundary checking disabled - continuous playback enabled'); + return; }, [activeSegment, isPlayingSegment, continuePastBoundary, clipSegments]); // Update display time and check for transitions between segments and empty spaces @@ -2140,129 +2040,15 @@ const TimelineControls = ({ if (!video) return; const handlePlay = () => { - if (!videoRef.current) return; - - const video = videoRef.current; - const currentPosition = video.currentTime; - - // Reset continuePastBoundary flag when starting new playback - setContinuePastBoundary(false); - - // Find the next stopping point based on current position - let stopTime = duration; - let currentSegment = null; - let nextSegment = null; - - // First, check if we're inside a segment with high precision - currentSegment = clipSegments.find((seg) => { - const isWithinSegment = currentPosition >= seg.startTime && currentPosition <= seg.endTime; - const isAtExactStart = Math.abs(currentPosition - seg.startTime) < 0.001; // Within 1ms of start - const isAtExactEnd = Math.abs(currentPosition - seg.endTime) < 0.001; // Within 1ms of end - return isWithinSegment || isAtExactStart || isAtExactEnd; - }); - - // Find the next segment with high precision - nextSegment = clipSegments - .filter((seg) => { - const isAfterCurrent = seg.startTime > currentPosition; - const isNotAtExactPosition = Math.abs(seg.startTime - currentPosition) > 0.001; - return isAfterCurrent && isNotAtExactPosition; - }) - .sort((a, b) => a.startTime - b.startTime)[0]; - - // Determine where to stop based on position - if (currentSegment) { - // If we're in a segment, stop at its end - stopTime = currentSegment.endTime; - setActiveSegment(currentSegment); - } else if (nextSegment) { - // If we're in a cutaway and there's a next segment, stop at its start - stopTime = nextSegment.startTime; - // Don't set active segment since we're in a cutaway - } - - // Create a boundary checker function with high precision - const checkBoundary = () => { - if (!video) return; - - const currentPosition = video.currentTime; - const timeLeft = stopTime - currentPosition; - - // If we're approaching the boundary (within 1ms) or have passed it - if (timeLeft <= 0.001 || currentPosition >= stopTime) { - // First pause playback - video.pause(); - - // Force exact position with multiple verification attempts - const setExactPosition = () => { - if (!video) return; - - // Set to exact boundary time - video.currentTime = stopTime; - onSeek(stopTime); - setDisplayTime(stopTime); - setClickedTime(stopTime); - - logger.debug('Position verification:', { - target: formatDetailedTime(stopTime), - actual: formatDetailedTime(video.currentTime), - difference: Math.abs(video.currentTime - stopTime).toFixed(3), - }); - }; - - // Multiple attempts to ensure precision - setExactPosition(); - setTimeout(setExactPosition, 10); - setTimeout(setExactPosition, 20); - setTimeout(setExactPosition, 50); - - // Update UI based on where we stopped - if (currentSegment) { - setSelectedSegmentId(currentSegment.id); - setShowEmptySpaceTooltip(false); - } else if (nextSegment) { - setSelectedSegmentId(nextSegment.id); - setShowEmptySpaceTooltip(false); - setActiveSegment(nextSegment); - } else { - setSelectedSegmentId(null); - setShowEmptySpaceTooltip(true); - setActiveSegment(null); - } - - // Remove our boundary checker - video.removeEventListener('timeupdate', checkBoundary); - setIsPlaying(false); - setIsPlayingSegment(false); - // Reset continuePastBoundary flag when stopping at boundary - setContinuePastBoundary(false); - return; - } - }; - - // Start our boundary checker - video.addEventListener('timeupdate', checkBoundary); - - // Start playing - video - .play() - .then(() => { - setIsPlaying(true); - setIsPlayingSegment(true); - logger.debug('Playback started:', { - from: formatDetailedTime(currentPosition), - to: formatDetailedTime(stopTime), - currentSegment: currentSegment ? `Segment ${currentSegment.id}` : 'None', - nextSegment: nextSegment ? `Segment ${nextSegment.id}` : 'None', - }); - }) - .catch((err) => { - console.error('Error playing video:', err); - }); + // Simple play handler - just update UI state, no boundary checking + setIsPlaying(true); + setIsPlayingSegment(true); + logger.debug('Continuous playback started from TimelineControls'); }; const handlePause = () => { logger.debug('Video paused from external control'); + setIsPlaying(false); setIsPlayingSegment(false); }; @@ -2273,7 +2059,7 @@ const TimelineControls = ({ video.removeEventListener('play', handlePlay); video.removeEventListener('pause', handlePause); }; - }, [clipSegments, duration, onSeek]); + }, []); // Handle mouse movement over timeline to remember position const handleTimelineMouseMove = (e: React.MouseEvent) => { @@ -3281,181 +3067,7 @@ const TimelineControls = ({ setActiveSegment(cutawaySegment); }, 0); - // Add a manual boundary check specifically for cutaway playback - // This ensures we detect when we reach the next segment's start - const checkCutawayBoundary = () => { - if (!videoRef.current) return; - - // Check if we've entered a segment (i.e., reached a boundary) - const currentPosition = videoRef.current.currentTime; - const segments = [...clipSegments].sort( - (a, b) => a.startTime - b.startTime - ); - - // Find the next segment we're approaching - use a wider detection range - // to catch the boundary earlier - const nextSegment = segments.find( - (seg) => seg.startTime > currentPosition - 0.3 - ); - - // We need to detect boundaries much earlier to allow for time to react - // This is a key fix - we need to detect the boundary BEFORE we reach it - // But don't stop if we're in continuePastBoundary mode - const shouldStop = - nextSegment && - currentPosition >= nextSegment.startTime - 0.25 && - currentPosition <= nextSegment.startTime + 0.1 && - !continuePastBoundary; - - // Add logging to show boundary check decisions - if ( - nextSegment && - currentPosition >= nextSegment.startTime - 0.25 && - currentPosition <= nextSegment.startTime + 0.1 - ) { - logger.debug( - `Approaching boundary at ${formatDetailedTime( - nextSegment.startTime - )}, continuePastBoundary=${continuePastBoundary}, willStop=${shouldStop}` - ); - } - - // Also check if we've entered a different segment - we need to detect this too - const segmentAtCurrentTime = segments.find( - (seg) => - currentPosition >= seg.startTime && - currentPosition <= seg.endTime - ); - - // If we've moved directly into a segment during playback, we need to update the active segment - if ( - segmentAtCurrentTime && - activeSegment?.id !== segmentAtCurrentTime.id - ) { - logger.debug( - `Entered segment ${segmentAtCurrentTime.id} during cutaway playback` - ); - setActiveSegment(segmentAtCurrentTime); - setSelectedSegmentId(segmentAtCurrentTime.id); - setShowEmptySpaceTooltip(false); - - // Remove our boundary checker since we're now in a standard segment - videoRef.current.removeEventListener( - 'timeupdate', - checkCutawayBoundary - ); - - // Reset continuation flags - setContinuePastBoundary(false); - sessionStorage.removeItem('continuingPastSegment'); - return; - } - - // If we've entered a segment, stop at its boundary - if (shouldStop && nextSegment) { - logger.debug( - `CUTAWAY MANUAL BOUNDARY CHECK: Current position ${formatDetailedTime( - currentPosition - )} approaching segment at ${formatDetailedTime( - nextSegment.startTime - )} (distance: ${Math.abs( - currentPosition - nextSegment.startTime - ).toFixed(3)}s) - STOPPING` - ); - - videoRef.current.pause(); - // Force exact time position with high precision and multiple attempts - setTimeout(() => { - if (videoRef.current) { - // First seek directly to exact start time, no offset - videoRef.current.currentTime = nextSegment.startTime; - // Update UI immediately to match video position - onSeek(nextSegment.startTime); - // Also update tooltip time displays - setDisplayTime(nextSegment.startTime); - setClickedTime(nextSegment.startTime); - - // Reset continuePastBoundary when stopping at a boundary - setContinuePastBoundary(false); - - // Update tooltip to show the segment at the boundary - setSelectedSegmentId(nextSegment.id); - setShowEmptySpaceTooltip(false); - setActiveSegment(nextSegment); - - // Force multiple adjustments to ensure exact precision - const verifyPosition = () => { - if (videoRef.current) { - // Always force the exact time in every verification - videoRef.current.currentTime = - nextSegment.startTime; - - // Make sure we update the UI to reflect the corrected position - onSeek(nextSegment.startTime); - - // Update the displayTime and clickedTime state to match exact position - setDisplayTime(nextSegment.startTime); - setClickedTime(nextSegment.startTime); - - logger.debug( - `Position corrected to exact segment boundary: ${formatDetailedTime( - videoRef.current.currentTime - )} (target: ${formatDetailedTime(nextSegment.startTime)})` - ); - } - }; - - // Apply multiple correction attempts with increasing delays - setTimeout(verifyPosition, 10); // Immediate correction - setTimeout(verifyPosition, 20); // First correction - setTimeout(verifyPosition, 50); // Second correction - setTimeout(verifyPosition, 100); // Third correction - setTimeout(verifyPosition, 200); // Final correction - - // Also add event listeners to ensure position is corrected whenever video state changes - videoRef.current.addEventListener('seeked', verifyPosition); - videoRef.current.addEventListener( - 'canplay', - verifyPosition - ); - videoRef.current.addEventListener( - 'waiting', - verifyPosition - ); - - // Remove these event listeners after a short time - setTimeout(() => { - if (videoRef.current) { - videoRef.current.removeEventListener( - 'seeked', - verifyPosition - ); - videoRef.current.removeEventListener( - 'canplay', - verifyPosition - ); - videoRef.current.removeEventListener( - 'waiting', - verifyPosition - ); - } - }, 300); - } - }, 10); - setIsPlayingSegment(false); - setActiveSegment(null); - - // Remove our boundary checker - videoRef.current.removeEventListener( - 'timeupdate', - checkCutawayBoundary - ); - return; - } - }; - - // Start our manual boundary checker - videoRef.current.addEventListener('timeupdate', checkCutawayBoundary); + // No boundary checking - allow continuous playback // Start playing with proper promise handling - use setTimeout to ensure // that our activeSegment setting has had time to take effect