diff --git a/web/src/components/scrubber/ActivityScrubber.tsx b/web/src/components/scrubber/ActivityScrubber.tsx index 9f43e98c5..22af9b85e 100644 --- a/web/src/components/scrubber/ActivityScrubber.tsx +++ b/web/src/components/scrubber/ActivityScrubber.tsx @@ -76,7 +76,7 @@ const domEvents: TimelineEventsWithMissing[] = [ type ActivityScrubberProps = { className?: string; items?: TimelineItem[]; - timeBars?: { time: DateType; id?: IdType | undefined }[]; + timeBars?: { time: DateType; id: IdType }[]; groups?: TimelineGroup[]; options?: TimelineOptions; } & TimelineEventsHandlers; @@ -94,6 +94,9 @@ function ActivityScrubber({ timeline: null, }); const [currentTime, setCurrentTime] = useState(Date.now()); + const [customTimes, setCustomTimes] = useState< + { id: IdType; time: DateType }[] + >([]); const defaultOptions: TimelineOptions = { width: "100%", @@ -161,6 +164,41 @@ function ActivityScrubber({ }; }, [containerRef]); + // need to keep custom times in sync + useEffect(() => { + if (!timelineRef.current.timeline || timeBars == undefined) { + return; + } + + setCustomTimes((prevTimes) => { + if (prevTimes.length == 0 && timeBars.length == 0) { + return []; + } + + prevTimes + .filter((x) => timeBars.find((y) => x.id == y.id) == undefined) + .forEach((time) => { + try { + timelineRef.current.timeline?.removeCustomTime(time.id); + } catch {} + }); + + timeBars.forEach((time) => { + try { + const existing = timelineRef.current.timeline?.getCustomTime(time.id); + + if (existing != time.time) { + timelineRef.current.timeline?.setCustomTime(time.time, time.id); + } + } catch { + timelineRef.current.timeline?.addCustomTime(time.time, time.id); + } + }); + + return timeBars; + }); + }, [timeBars, timelineRef]); + return (
diff --git a/web/src/utils/historyUtil.ts b/web/src/utils/historyUtil.ts index c4928fb44..78bfe77cb 100644 --- a/web/src/utils/historyUtil.ts +++ b/web/src/utils/historyUtil.ts @@ -111,10 +111,20 @@ export function getTimelineHoursForDay( const now = new Date(); const data: TimelinePlayback[] = []; const startDay = new Date(timestamp * 1000); + startDay.setHours(23, 59, 59, 999); + const dayEnd = startDay.getTime() / 1000; startDay.setHours(0, 0, 0, 0); let start = startDay.getTime() / 1000; let end = 0; + const relevantPreviews = allPreviews.filter((preview) => { + return ( + preview.camera == camera && + preview.start >= start && + Math.floor(preview.end - 1) <= dayEnd + ); + }); + const dayIdx = Object.keys(cards).find((day) => { if (parseInt(day) > start) { return false; @@ -156,12 +166,9 @@ export function getTimelineHoursForDay( return []; }) : []; - const previewCheck = start + 30; // preview can start after the hour - const relevantPreview = Object.values(allPreviews || []).find( + const relevantPreview = relevantPreviews.find( (preview) => - preview.camera == camera && - preview.start < previewCheck && - preview.end > previewCheck + Math.round(preview.start) >= start && Math.floor(preview.end) <= end ); data.push({ camera, diff --git a/web/src/views/history/DesktopTimelineView.tsx b/web/src/views/history/DesktopTimelineView.tsx index 94705feaf..ebb688cdb 100644 --- a/web/src/views/history/DesktopTimelineView.tsx +++ b/web/src/views/history/DesktopTimelineView.tsx @@ -42,6 +42,11 @@ export default function DesktopTimelineView({ const [seeking, setSeeking] = useState(false); const [timeToSeek, setTimeToSeek] = useState(undefined); + const [timelineTime, setTimelineTime] = useState( + initialPlayback.timelineItems.length > 0 + ? initialPlayback.timelineItems[0].timestamp + : initialPlayback.range.start + ); const annotationOffset = useMemo(() => { if (!config) { @@ -54,14 +59,6 @@ export default function DesktopTimelineView({ ); }, [config]); - const timelineTime = useMemo(() => { - if (!selectedPlayback || selectedPlayback.timelineItems.length == 0) { - return selectedPlayback.range.start; - } - - return selectedPlayback.timelineItems.at(0)!!.timestamp; - }, [selectedPlayback]); - const recordingParams = useMemo(() => { return { before: selectedPlayback.range.end, @@ -121,36 +118,6 @@ export default function DesktopTimelineView({ [annotationOffset, recordings, playerRef] ); - const onScrubTime = useCallback( - (data: { time: Date }) => { - if (!selectedPlayback.relevantPreview) { - return; - } - - if (playerRef.current?.paused() == false) { - setScrubbing(true); - playerRef.current?.pause(); - } - - const seekTimestamp = data.time.getTime() / 1000; - const seekTime = seekTimestamp - selectedPlayback.relevantPreview.start; - setTimeToSeek(Math.round(seekTime)); - }, - [scrubbing, playerRef, selectedPlayback] - ); - - const onStopScrubbing = useCallback( - (data: { time: Date }) => { - const playbackTime = data.time.getTime() / 1000; - playerRef.current?.currentTime( - playbackTime - selectedPlayback.range.start - ); - setScrubbing(false); - playerRef.current?.play(); - }, - [selectedPlayback, playerRef] - ); - // handle seeking to next frame when seek is finished useEffect(() => { if (seeking) { @@ -165,15 +132,33 @@ export default function DesktopTimelineView({ // handle loading main playback when selected hour changes useEffect(() => { - if (!playerRef.current) { + if (!playerRef.current || !previewRef.current) { return; } + setTimelineTime( + selectedPlayback.timelineItems.length > 0 + ? selectedPlayback.timelineItems[0].timestamp + : selectedPlayback.range.start + ); + playerRef.current.src({ src: playbackUri, type: "application/vnd.apple.mpegurl", }); - }, [playerRef, selectedPlayback]); + + if (selectedPlayback.relevantPreview) { + console.log( + `found relevant preview with start ${new Date( + selectedPlayback.relevantPreview.start * 1000 + )} for ${new Date(selectedPlayback.range.start * 1000)}` + ); + previewRef.current.src({ + src: selectedPlayback.relevantPreview.src, + type: selectedPlayback.relevantPreview.type, + }); + } + }, [playerRef, previewRef, selectedPlayback]); const timelineStack = useMemo( () => @@ -181,7 +166,7 @@ export default function DesktopTimelineView({ selectedPlayback.camera, timelineData, allPreviews, - timelineTime + selectedPlayback.range.start + 60 ), [] ); @@ -216,9 +201,15 @@ export default function DesktopTimelineView({ seekOptions={{ forward: 10, backward: 5 }} onReady={(player) => { playerRef.current = player; - player.currentTime( - timelineTime - selectedPlayback.range.start - ); + + if (selectedPlayback.timelineItems.length > 0) { + player.currentTime( + selectedPlayback.timelineItems[0].timestamp - + selectedPlayback.range.start + ); + } else { + player.currentTime(0); + } player.on("playing", () => { onSelectItem(undefined); }); @@ -285,13 +276,18 @@ export default function DesktopTimelineView({ return (
{ + if (!timeline.relevantPreview) { + return; + } + + if (playerRef.current?.paused() == false) { + setScrubbing(true); + playerRef.current?.pause(); + } + + const seekTimestamp = data.time.getTime() / 1000; + const seekTime = + seekTimestamp - timeline.relevantPreview.start; + setTimelineTime(seekTimestamp); + setTimeToSeek(Math.round(seekTime)); + }} + timechangedHandler={(data) => { + const playbackTime = data.time.getTime() / 1000; + playerRef.current?.currentTime( + playbackTime - timeline.range.start + ); + setScrubbing(false); + playerRef.current?.play(); + }} doubleClickHandler={() => { setSelectedPlayback(timeline); }}