mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-09 00:48:54 -05:00
fix: Chapters: Play should not stop at the end of a chapter or a cutaway area, but should just continue play through.
This commit is contained in:
parent
ee7fb7950c
commit
f67021b17b
@ -39,8 +39,6 @@ const App = () => {
|
|||||||
isMobile,
|
isMobile,
|
||||||
videoInitialized,
|
videoInitialized,
|
||||||
setVideoInitialized,
|
setVideoInitialized,
|
||||||
isPlayingSegments,
|
|
||||||
handlePlaySegments,
|
|
||||||
} = useVideoChapters();
|
} = useVideoChapters();
|
||||||
|
|
||||||
const handlePlay = () => {
|
const handlePlay = () => {
|
||||||
@ -52,156 +50,17 @@ const App = () => {
|
|||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
video.pause();
|
video.pause();
|
||||||
setIsPlaying(false);
|
setIsPlaying(false);
|
||||||
|
logger.debug('Video paused');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentPosition = Number(video.currentTime.toFixed(6));
|
// Start playing - no boundary checking, play through entire timeline
|
||||||
|
|
||||||
// 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
|
|
||||||
video
|
video
|
||||||
.play()
|
.play()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setIsPlaying(true);
|
setIsPlaying(true);
|
||||||
setVideoInitialized(true);
|
setVideoInitialized(true);
|
||||||
logger.debug('Playback started:', {
|
logger.debug('Continuous playback started from:', formatDetailedTime(video.currentTime));
|
||||||
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',
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error('Error playing video:', err);
|
console.error('Error playing video:', err);
|
||||||
@ -231,10 +90,8 @@ const App = () => {
|
|||||||
onReset={handleReset}
|
onReset={handleReset}
|
||||||
onUndo={handleUndo}
|
onUndo={handleUndo}
|
||||||
onRedo={handleRedo}
|
onRedo={handleRedo}
|
||||||
onPlaySegments={handlePlaySegments}
|
|
||||||
onPlay={handlePlay}
|
onPlay={handlePlay}
|
||||||
isPlaying={isPlaying}
|
isPlaying={isPlaying}
|
||||||
isPlayingSegments={isPlayingSegments}
|
|
||||||
canUndo={historyPosition > 0}
|
canUndo={historyPosition > 0}
|
||||||
canRedo={historyPosition < history.length - 1}
|
canRedo={historyPosition < history.length - 1}
|
||||||
/>
|
/>
|
||||||
@ -243,6 +100,7 @@ const App = () => {
|
|||||||
<TimelineControls
|
<TimelineControls
|
||||||
currentTime={currentTime}
|
currentTime={currentTime}
|
||||||
duration={duration}
|
duration={duration}
|
||||||
|
thumbnails={[]}
|
||||||
trimStart={trimStart}
|
trimStart={trimStart}
|
||||||
trimEnd={trimEnd}
|
trimEnd={trimEnd}
|
||||||
splitPoints={splitPoints}
|
splitPoints={splitPoints}
|
||||||
@ -262,7 +120,6 @@ const App = () => {
|
|||||||
isPlaying={isPlaying}
|
isPlaying={isPlaying}
|
||||||
setIsPlaying={setIsPlaying}
|
setIsPlaying={setIsPlaying}
|
||||||
onPlayPause={handlePlay}
|
onPlayPause={handlePlay}
|
||||||
isPlayingSegments={isPlayingSegments}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Clip Segments */}
|
{/* Clip Segments */}
|
||||||
|
|||||||
@ -561,111 +561,11 @@ const TimelineControls = ({
|
|||||||
}
|
}
|
||||||
}, [currentTime, zoomLevel, duration, selectedSegmentId, showEmptySpaceTooltip, currentTimePercent]);
|
}, [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(() => {
|
useEffect(() => {
|
||||||
// Skip if no video or no active segment
|
// Boundary checking disabled - allow continuous playback through all segments
|
||||||
const video = videoRef.current;
|
logger.debug('Segment boundary checking disabled - continuous playback enabled');
|
||||||
if (!video || !activeSegment || !isPlayingSegment) {
|
return;
|
||||||
// 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');
|
|
||||||
};
|
|
||||||
}, [activeSegment, isPlayingSegment, continuePastBoundary, clipSegments]);
|
}, [activeSegment, isPlayingSegment, continuePastBoundary, clipSegments]);
|
||||||
|
|
||||||
// Update display time and check for transitions between segments and empty spaces
|
// Update display time and check for transitions between segments and empty spaces
|
||||||
@ -2140,129 +2040,15 @@ const TimelineControls = ({
|
|||||||
if (!video) return;
|
if (!video) return;
|
||||||
|
|
||||||
const handlePlay = () => {
|
const handlePlay = () => {
|
||||||
if (!videoRef.current) return;
|
// Simple play handler - just update UI state, no boundary checking
|
||||||
|
setIsPlaying(true);
|
||||||
const video = videoRef.current;
|
setIsPlayingSegment(true);
|
||||||
const currentPosition = video.currentTime;
|
logger.debug('Continuous playback started from TimelineControls');
|
||||||
|
|
||||||
// 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);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePause = () => {
|
const handlePause = () => {
|
||||||
logger.debug('Video paused from external control');
|
logger.debug('Video paused from external control');
|
||||||
|
setIsPlaying(false);
|
||||||
setIsPlayingSegment(false);
|
setIsPlayingSegment(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2273,7 +2059,7 @@ const TimelineControls = ({
|
|||||||
video.removeEventListener('play', handlePlay);
|
video.removeEventListener('play', handlePlay);
|
||||||
video.removeEventListener('pause', handlePause);
|
video.removeEventListener('pause', handlePause);
|
||||||
};
|
};
|
||||||
}, [clipSegments, duration, onSeek]);
|
}, []);
|
||||||
|
|
||||||
// Handle mouse movement over timeline to remember position
|
// Handle mouse movement over timeline to remember position
|
||||||
const handleTimelineMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
|
const handleTimelineMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
@ -3281,181 +3067,7 @@ const TimelineControls = ({
|
|||||||
setActiveSegment(cutawaySegment);
|
setActiveSegment(cutawaySegment);
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
// Add a manual boundary check specifically for cutaway playback
|
// No boundary checking - allow continuous 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);
|
|
||||||
|
|
||||||
// Start playing with proper promise handling - use setTimeout to ensure
|
// Start playing with proper promise handling - use setTimeout to ensure
|
||||||
// that our activeSegment setting has had time to take effect
|
// that our activeSegment setting has had time to take effect
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user