feat: In Preview mode, you can jump in timeline to review the edits

In Preview mode, you can jump in timeline to review the edits, without having to listen to the full video.
This commit is contained in:
Yiannis Christodoulou 2025-06-11 04:12:24 +03:00
parent c989b1515e
commit 321287d009
3 changed files with 133 additions and 63 deletions

View File

@ -1201,20 +1201,51 @@ const TimelineControls = ({
setActiveSegment(segmentAtClickedTime); setActiveSegment(segmentAtClickedTime);
} }
// Resume playback in two cases: // Resume playback based on the current mode
// 1. If it was playing before (regular playback) if (videoRef.current) {
// 2. If we're in preview mode (regardless of previous playing state) // Special handling for segments playback mode
if ((wasPlaying || isPreviewMode) && videoRef.current) { if (isPlayingSegments && wasPlaying) {
logger.debug("Resuming playback after timeline click"); // Update the current segment index if we clicked into a segment
videoRef.current.play() if (segmentAtClickedTime) {
.then(() => { const orderedSegments = [...clipSegments].sort((a, b) => a.startTime - b.startTime);
setIsPlayingSegment(true); const targetSegmentIndex = orderedSegments.findIndex(seg => seg.id === segmentAtClickedTime.id);
logger.debug("Resumed playback after seeking");
}) if (targetSegmentIndex !== -1) {
.catch(err => { // Dispatch a custom event to update the current segment index
console.error("Error resuming playback:", err); const updateSegmentIndexEvent = new CustomEvent('update-segment-index', {
setIsPlayingSegment(false); detail: { segmentIndex: targetSegmentIndex }
}); });
document.dispatchEvent(updateSegmentIndexEvent);
logger.debug(`Segments playback mode: updating segment index to ${targetSegmentIndex} for timeline click in segment ${segmentAtClickedTime.id}`);
}
}
logger.debug("Segments playback mode: resuming playback after timeline click");
videoRef.current.play()
.then(() => {
setIsPlayingSegment(true);
logger.debug("Resumed segments playback after timeline seeking");
})
.catch(err => {
console.error("Error resuming segments playback:", err);
setIsPlayingSegment(false);
});
}
// Resume playback in two cases (but not during segments playback):
// 1. If it was playing before (regular playback)
// 2. If we're in preview mode (regardless of previous playing state)
else if ((wasPlaying || isPreviewMode) && !isPlayingSegments) {
logger.debug("Resuming playback after timeline click");
videoRef.current.play()
.then(() => {
setIsPlayingSegment(true);
logger.debug("Resumed playback after seeking");
})
.catch(err => {
console.error("Error resuming playback:", err);
setIsPlayingSegment(false);
});
}
} }
// Only process tooltip display if clicked on the timeline background or thumbnails, not on other UI elements // Only process tooltip display if clicked on the timeline background or thumbnails, not on other UI elements
@ -1678,34 +1709,63 @@ const TimelineControls = ({
// Seek to this position (this will update the video's current time) // Seek to this position (this will update the video's current time)
onSeek(boundedTime); onSeek(boundedTime);
// If video was playing before OR we're in preview mode, ensure it continues playing // Handle playback continuation based on the current mode
if ((wasPlaying || isPreviewMode) && videoRef.current) { if (videoRef.current) {
// Set current segment as active segment for boundary checking // Special handling for segments playback mode
setActiveSegment(segment); if (isPlayingSegments && wasPlaying) {
// Reset the continuePastBoundary flag when clicking on a segment to ensure boundaries work // Update the current segment index for segments playback mode
setContinuePastBoundary(false); const orderedSegments = [...clipSegments].sort((a, b) => a.startTime - b.startTime);
// Continue playing from the new position const targetSegmentIndex = orderedSegments.findIndex(seg => seg.id === segmentId);
videoRef.current.play()
.then(() => { if (targetSegmentIndex !== -1) {
setIsPlayingSegment(true); // Dispatch a custom event to update the current segment index
logger.debug("Continued preview playback after segment click"); const updateSegmentIndexEvent = new CustomEvent('update-segment-index', {
}) detail: { segmentIndex: targetSegmentIndex }
.catch(err => { });
console.error("Error resuming playback after segment click:", err); document.dispatchEvent(updateSegmentIndexEvent);
}); logger.debug(`Segments playback mode: updating segment index to ${targetSegmentIndex} for segment ${segmentId}`);
} }
// Always continue playback in preview mode, even if video was paused when clicking // In segments playback mode, we want to continue the segments playback from the new position
if (isPreviewMode && videoRef.current) { // The segments playback will naturally handle continuing to the next segments
setActiveSegment(segment); logger.debug("Segments playback mode: continuing playback from new position");
videoRef.current.play() videoRef.current.play()
.then(() => { .then(() => {
setIsPlayingSegment(true); setIsPlayingSegment(true);
logger.debug("Continued preview playback after segment click"); logger.debug("Continued segments playback after segment click");
}) })
.catch(err => { .catch(err => {
console.error("Error continuing preview playback:", err); console.error("Error continuing segments playback after segment click:", err);
}); });
}
// If video was playing before OR we're in preview mode, ensure it continues playing (but not in segments mode)
else if ((wasPlaying || isPreviewMode) && !isPlayingSegments) {
// Set current segment as active segment for boundary checking
setActiveSegment(segment);
// Reset the continuePastBoundary flag when clicking on a segment to ensure boundaries work
setContinuePastBoundary(false);
// Continue playing from the new position
videoRef.current.play()
.then(() => {
setIsPlayingSegment(true);
logger.debug("Continued preview playback after segment click");
})
.catch(err => {
console.error("Error resuming playback after segment click:", err);
});
}
// Always continue playback in preview mode, even if video was paused when clicking (but not in segments mode)
else if (isPreviewMode && !isPlayingSegments) {
setActiveSegment(segment);
videoRef.current.play()
.then(() => {
setIsPlayingSegment(true);
logger.debug("Continued preview playback after segment click");
})
.catch(err => {
console.error("Error continuing preview playback:", err);
});
}
} }
// Calculate tooltip position directly above click point // Calculate tooltip position directly above click point

View File

@ -1035,6 +1035,23 @@ const useVideoTrimmer = () => {
}; };
}, [isPlayingSegments, currentSegmentIndex, clipSegments]); }, [isPlayingSegments, currentSegmentIndex, clipSegments]);
// Effect to handle manual segment index updates during segments playback
useEffect(() => {
const handleSegmentIndexUpdate = (event: CustomEvent) => {
const { segmentIndex } = event.detail;
if (isPlayingSegments && segmentIndex !== currentSegmentIndex) {
logger.debug(`Updating current segment index from ${currentSegmentIndex} to ${segmentIndex}`);
setCurrentSegmentIndex(segmentIndex);
}
};
document.addEventListener('update-segment-index', handleSegmentIndexUpdate as EventListener);
return () => {
document.removeEventListener('update-segment-index', handleSegmentIndexUpdate as EventListener);
};
}, [isPlayingSegments, currentSegmentIndex]);
// Handle play segments // Handle play segments
const handlePlaySegments = () => { const handlePlaySegments = () => {
const video = videoRef.current; const video = videoRef.current;

View File

@ -852,30 +852,23 @@
background-color: inherit !important; background-color: inherit !important;
} }
/* Segments playback mode styles */ /* Segments playback mode styles - minimal functional styling */
.segments-playback-mode {
}
.segments-playback-mode .timeline-container,
.segments-playback-mode .clip-segment,
.segments-playback-mode .clip-segment-handle,
.segments-playback-mode .timeline-marker-head,
.segments-playback-mode .timeline-marker-drag,
.segments-playback-mode .trim-handle {
}
.segments-playback-mode .tooltip-action-btn {
opacity: 0.5;
}
.segments-playback-mode .tooltip-time-btn { .segments-playback-mode .tooltip-time-btn {
opacity: 0.5; opacity: 1;
cursor: pointer;
} }
.segments-playback-mode .clip-segment:hover { .segments-playback-mode .tooltip-action-btn.set-in,
box-shadow: none; .segments-playback-mode .tooltip-action-btn.set-out,
border-color: rgba(0, 0, 0, 0.15); .segments-playback-mode .tooltip-action-btn.play-from-start {
background-color: inherit !important; opacity: 0.5;
pointer-events: none;
}
.segments-playback-mode .tooltip-action-btn.play,
.segments-playback-mode .tooltip-action-btn.pause {
opacity: 1;
cursor: pointer;
} }
/* Show segments playback message */ /* Show segments playback message */