From 4b7bc4fe138ea73fc9d0d02391e67b7afd0de204 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sun, 21 Jun 2026 12:23:28 -0600 Subject: [PATCH] Handle back seeking going to previous clip --- web/src/components/player/HlsVideoPlayer.tsx | 12 ++++++++++-- .../components/player/dynamic/DynamicVideoPlayer.tsx | 3 +++ web/src/views/recording/RecordingView.tsx | 11 +++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/web/src/components/player/HlsVideoPlayer.tsx b/web/src/components/player/HlsVideoPlayer.tsx index 91cf0c210d..c1aaa09d68 100644 --- a/web/src/components/player/HlsVideoPlayer.tsx +++ b/web/src/components/player/HlsVideoPlayer.tsx @@ -47,6 +47,7 @@ type HlsVideoPlayerProps = { frigateControls?: boolean; inpointOffset?: number; onClipEnded?: (currentTime: number) => void; + onClipPrevious?: (diff: number) => void; onPlayerLoaded?: () => void; onTimeUpdate?: (time: number) => void; onPlaying?: () => void; @@ -74,6 +75,7 @@ export default function HlsVideoPlayer({ frigateControls = true, inpointOffset = 0, onClipEnded, + onClipPrevious, onPlayerLoaded, onTimeUpdate, onPlaying, @@ -339,11 +341,17 @@ export default function HlsVideoPlayer({ onSeek={(diff) => { const currentTime = videoRef.current?.currentTime; - if (!videoRef.current || !currentTime) { + if (!videoRef.current || currentTime == undefined) { return; } - videoRef.current.currentTime = Math.max(0, currentTime + diff); + const newTime = currentTime + diff; + + if (newTime < 0 && onClipPrevious) { + onClipPrevious(diff); + } else { + videoRef.current.currentTime = Math.max(0, newTime); + } }} onSetPlaybackRate={(rate) => { setPlaybackRate(rate, true); diff --git a/web/src/components/player/dynamic/DynamicVideoPlayer.tsx b/web/src/components/player/dynamic/DynamicVideoPlayer.tsx index 1578f4d1d7..8998be7a60 100644 --- a/web/src/components/player/dynamic/DynamicVideoPlayer.tsx +++ b/web/src/components/player/dynamic/DynamicVideoPlayer.tsx @@ -49,6 +49,7 @@ type DynamicVideoPlayerProps = { onControllerReady: (controller: DynamicVideoController) => void; onTimestampUpdate?: (timestamp: number) => void; onClipEnded?: () => void; + onClipPrevious?: (diff: number) => void; onSeekToTime?: (timestamp: number, play?: boolean) => void; setFullResolution: React.Dispatch>; toggleFullscreen: () => void; @@ -68,6 +69,7 @@ export default function DynamicVideoPlayer({ onControllerReady, onTimestampUpdate, onClipEnded, + onClipPrevious, onSeekToTime, setFullResolution, toggleFullscreen, @@ -343,6 +345,7 @@ export default function DynamicVideoPlayer({ onTimeUpdate={onTimeUpdate} onPlayerLoaded={onPlayerLoaded} onClipEnded={onValidateClipEnd} + onClipPrevious={onClipPrevious} onSeekToTime={(timestamp, play) => { if (onSeekToTime) { onSeekToTime(timestamp, play); diff --git a/web/src/views/recording/RecordingView.tsx b/web/src/views/recording/RecordingView.tsx index 30d3152b4b..924538cff5 100644 --- a/web/src/views/recording/RecordingView.tsx +++ b/web/src/views/recording/RecordingView.tsx @@ -336,6 +336,16 @@ export function RecordingView({ [currentTimeRange, updateSelectedSegment], ); + const onClipPrevious = useCallback( + (diff: number) => { + manuallySetCurrentTime( + currentTime + diff, + mainControllerRef.current?.isPlaying() ?? false, + ); + }, + [currentTime, manuallySetCurrentTime], + ); + const onShareReviewLink = useCallback( (timestamp: number) => { const reviewUrl = createRecordingReviewUrl(location.pathname, { @@ -902,6 +912,7 @@ export function RecordingView({ ); }} onClipEnded={onClipEnded} + onClipPrevious={onClipPrevious} onSeekToTime={manuallySetCurrentTime} onControllerReady={(controller) => { mainControllerRef.current = controller;