From 321287d009748ead906d4d9417197b2aac436f29 Mon Sep 17 00:00:00 2001 From: Yiannis Christodoulou Date: Wed, 11 Jun 2025 04:12:24 +0300 Subject: [PATCH] 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. --- .../src/components/TimelineControls.tsx | 144 +++++++++++++----- .../client/src/hooks/useVideoTrimmer.tsx | 17 +++ .../client/src/styles/TimelineControls.css | 35 ++--- 3 files changed, 133 insertions(+), 63 deletions(-) diff --git a/frontend-tools/video-editor/client/src/components/TimelineControls.tsx b/frontend-tools/video-editor/client/src/components/TimelineControls.tsx index ca1bf569..aaeca32c 100644 --- a/frontend-tools/video-editor/client/src/components/TimelineControls.tsx +++ b/frontend-tools/video-editor/client/src/components/TimelineControls.tsx @@ -1201,20 +1201,51 @@ const TimelineControls = ({ setActiveSegment(segmentAtClickedTime); } - // Resume playback in two cases: - // 1. If it was playing before (regular playback) - // 2. If we're in preview mode (regardless of previous playing state) - if ((wasPlaying || isPreviewMode) && videoRef.current) { - 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); - }); + // Resume playback based on the current mode + if (videoRef.current) { + // Special handling for segments playback mode + if (isPlayingSegments && wasPlaying) { + // Update the current segment index if we clicked into a segment + if (segmentAtClickedTime) { + const orderedSegments = [...clipSegments].sort((a, b) => a.startTime - b.startTime); + const targetSegmentIndex = orderedSegments.findIndex(seg => seg.id === segmentAtClickedTime.id); + + if (targetSegmentIndex !== -1) { + // Dispatch a custom event to update the current segment index + const updateSegmentIndexEvent = new CustomEvent('update-segment-index', { + 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 @@ -1678,34 +1709,63 @@ const TimelineControls = ({ // Seek to this position (this will update the video's current time) onSeek(boundedTime); - // If video was playing before OR we're in preview mode, ensure it continues playing - if ((wasPlaying || isPreviewMode) && videoRef.current) { - // 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 - if (isPreviewMode && videoRef.current) { - 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); - }); + // Handle playback continuation based on the current mode + if (videoRef.current) { + // Special handling for segments playback mode + if (isPlayingSegments && wasPlaying) { + // Update the current segment index for segments playback mode + const orderedSegments = [...clipSegments].sort((a, b) => a.startTime - b.startTime); + const targetSegmentIndex = orderedSegments.findIndex(seg => seg.id === segmentId); + + if (targetSegmentIndex !== -1) { + // Dispatch a custom event to update the current segment index + const updateSegmentIndexEvent = new CustomEvent('update-segment-index', { + detail: { segmentIndex: targetSegmentIndex } + }); + document.dispatchEvent(updateSegmentIndexEvent); + logger.debug(`Segments playback mode: updating segment index to ${targetSegmentIndex} for segment ${segmentId}`); + } + + // In segments playback mode, we want to continue the segments playback from the new position + // The segments playback will naturally handle continuing to the next segments + logger.debug("Segments playback mode: continuing playback from new position"); + videoRef.current.play() + .then(() => { + setIsPlayingSegment(true); + logger.debug("Continued segments playback after segment click"); + }) + .catch(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 diff --git a/frontend-tools/video-editor/client/src/hooks/useVideoTrimmer.tsx b/frontend-tools/video-editor/client/src/hooks/useVideoTrimmer.tsx index 35aa8789..e5cf9296 100644 --- a/frontend-tools/video-editor/client/src/hooks/useVideoTrimmer.tsx +++ b/frontend-tools/video-editor/client/src/hooks/useVideoTrimmer.tsx @@ -1035,6 +1035,23 @@ const useVideoTrimmer = () => { }; }, [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 const handlePlaySegments = () => { const video = videoRef.current; diff --git a/frontend-tools/video-editor/client/src/styles/TimelineControls.css b/frontend-tools/video-editor/client/src/styles/TimelineControls.css index 157f4bd8..a030b0e9 100644 --- a/frontend-tools/video-editor/client/src/styles/TimelineControls.css +++ b/frontend-tools/video-editor/client/src/styles/TimelineControls.css @@ -852,30 +852,23 @@ background-color: inherit !important; } -/* Segments playback mode styles */ -.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 styles - minimal functional styling */ .segments-playback-mode .tooltip-time-btn { - opacity: 0.5; + opacity: 1; + cursor: pointer; } -.segments-playback-mode .clip-segment:hover { - box-shadow: none; - border-color: rgba(0, 0, 0, 0.15); - background-color: inherit !important; +.segments-playback-mode .tooltip-action-btn.set-in, +.segments-playback-mode .tooltip-action-btn.set-out, +.segments-playback-mode .tooltip-action-btn.play-from-start { + 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 */