From e57c15cc378e4fd0516a279a3edd0fe374a4ca9e Mon Sep 17 00:00:00 2001 From: JohnMark Sill Date: Wed, 12 Jan 2022 23:09:39 -0600 Subject: [PATCH] feat: moved events to camera and added logic to track video progress --- web/src/components/Timeline.jsx | 102 ++++++++---------- web/src/routes/Camera_V2.jsx | 179 +++++++++++++++++++++++--------- 2 files changed, 175 insertions(+), 106 deletions(-) diff --git a/web/src/components/Timeline.jsx b/web/src/components/Timeline.jsx index eee7f86b5..8b47496a0 100644 --- a/web/src/components/Timeline.jsx +++ b/web/src/components/Timeline.jsx @@ -1,12 +1,8 @@ -import { Fragment, h } from 'preact'; +import { h } from 'preact'; import { useCallback, useEffect, useRef, useState } from 'preact/hooks'; -import { Next } from '../icons/Next'; -import { Play } from '../icons/Play'; -import { Previous } from '../icons/Previous'; -import { TextTab } from './Tabs'; import { longToDate } from '../utils/dateUtil'; -export default function Timeline({ events, onChange }) { +export default function Timeline({ events, offset, currentIndex, onChange }) { const timelineContainerRef = useRef(undefined); const [timeline, setTimeline] = useState([]); @@ -18,14 +14,13 @@ export default function Timeline({ events, onChange }) { useEffect(() => { if (events && timelineOffset) { - const filteredEvents = [...events].reverse().filter((e) => e.end_time !== undefined); - const firstEvent = events[events.length - 1]; + const firstEvent = events[0]; if (firstEvent) { setMarkerTime(longToDate(firstEvent.start_time)); } const firstEventTime = longToDate(firstEvent.start_time); - const eventsMap = filteredEvents.map((e, i) => { + const eventsMap = events.map((e, i) => { const startTime = longToDate(e.start_time); const endTime = e.end_time ? longToDate(e.end_time) : new Date(); const seconds = Math.round(Math.abs(endTime - startTime) / 1000); @@ -53,6 +48,32 @@ export default function Timeline({ events, onChange }) { } }, [events, timelineOffset]); + useEffect(() => { + if (currentEvent && offset >= 0) { + setScrollActive(false); + timelineContainerRef.current.scroll({ + left: currentEvent.positionX + offset - timelineOffset, + behavior: 'smooth', + }); + } else { + setScrollActive(true); + } + }, [offset]); + + useEffect(() => { + if (currentIndex !== undefined && currentIndex !== currentEvent.index) { + const event = timeline[currentIndex]; + setCurrentEvent({ + ...event, + id: event.id, + index: currentIndex, + startTime: event.start_time, + endTime: event.end_time, + }); + timelineContainerRef.current.scroll({left: event.positionX - timelineOffset, behavior: "smooth"}) + } + }, [currentIndex]); + const checkMarkerForEvent = (markerTime) => { if (!scrollActive) { setScrollActive(true); @@ -133,57 +154,24 @@ export default function Timeline({ events, onChange }) { } }, [timeline]); - const setNextCurrentEvent = function (offset) { - setScrollActive(false); - setCurrentEvent((currentEvent) => { - const index = currentEvent.index + offset; - const nextEvent = timeline[index]; - const positionX = nextEvent.positionX - timelineOffset; - timelineContainerRef.current.scrollLeft = positionX; - return { - ...nextEvent, - id: nextEvent.id, - index, - startTime: nextEvent.start_time, - endTime: nextEvent.end_time, - }; - }); - }; - - const handlePrevious = function () { - setNextCurrentEvent(-1); - }; - - const handleNext = function () { - setNextCurrentEvent(1); - }; - return ( - -
-
-
- {markerTime && {markerTime.toLocaleTimeString()}} -
-
-
-
- +
+
+
+ {markerTime && {markerTime.toLocaleTimeString()}} +
- -
- } /> - } /> - } /> +
+
- +
); } diff --git a/web/src/routes/Camera_V2.jsx b/web/src/routes/Camera_V2.jsx index 5a355de68..11e54deb1 100644 --- a/web/src/routes/Camera_V2.jsx +++ b/web/src/routes/Camera_V2.jsx @@ -1,11 +1,11 @@ -import { h, Fragment } from 'preact'; +import { h, Fragment, render } from 'preact'; import AutoUpdatingCameraImage from '../components/AutoUpdatingCameraImage'; import JSMpegPlayer from '../components/JSMpegPlayer'; import Heading from '../components/Heading'; import Link from '../components/Link'; import Switch from '../components/Switch'; import { usePersistence } from '../context'; -import { useCallback, useMemo, useState } from 'preact/hooks'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks'; import { useApiHost, useConfig, useEvents } from '../api'; import { Tabs, TextTab } from '../components/Tabs'; import Timeline from '../components/Timeline'; @@ -13,15 +13,22 @@ import { LiveChip } from '../components/LiveChip'; import { HistoryHeader } from './HistoryHeader'; import { longToDate } from '../utils/dateUtil'; import { useSearchString } from '../hooks/useSearchString'; +import { Previous } from '../icons/Previous'; +import { Play } from '../icons/Play'; +import { Next } from '../icons/Next'; const emptyObject = Object.freeze({}); export default function Camera({ camera }) { const apiHost = useApiHost(); + const videoRef = useRef(); const { data: config } = useConfig(); - const { searchString } = useSearchString(25, `camera=${camera}`); + + const beginningOfDay = new Date().setHours(0, 0, 0) / 1000; + const { searchString } = useSearchString(200, `camera=${camera}&after=${beginningOfDay}`); const { data: events } = useEvents(searchString); + const [timelineEvents, setTimelineEvents] = useState(); const [hideBanner, setHideBanner] = useState(false); const [playerType, setPlayerType] = useState('live'); @@ -30,7 +37,15 @@ export default function Camera({ camera }) { const liveWidth = Math.round(cameraConfig.live.height * (cameraConfig.detect.width / cameraConfig.detect.height)); const [options, setOptions] = usePersistence(`${camera}-feed`, emptyObject); - const [currentEvent, setCurrentEvent] = useState(undefined); + const [currentEvent, setCurrentEvent] = useState(); + const [currentEventIndex, setCurrentEventIndex] = useState(); + const [timelineOffset, setTimelineOffset] = useState(0); + + useEffect(() => { + if (events) { + setTimelineEvents([...events].reverse().filter((e) => e.end_time !== undefined)); + } + }, [events]); const handleSetOption = useCallback( (id, value) => { @@ -52,58 +67,63 @@ export default function Camera({ camera }) { ); const optionContent = ( -
+
- - + + Mask & Zone creator
); - const RenderPlayer = useCallback(() => { - if (playerType === 'live') { - return ; - } else if (playerType === 'debug') { - return ( -
- - {/* {optionContent} */} -
+ let renderPlayer; + + switch (playerType) { + case 'live': + renderPlayer = ( + +
+ +
+
); - } else if (playerType === 'history') { - return ( - currentEvent && ( + break; + case 'history': + if (currentEvent) { + renderPlayer = ( - ) + ); + } + break; + case 'debug': + renderPlayer = ( + +
+ +
+ {optionContent} +
); + break; + default: + break; + } + + const handleTimeUpdate = () => { + const timestamp = Math.round(videoRef.current.currentTime); + const offset = Math.round(timestamp); + const triggerStateChange = offset !== timelineOffset; + if (triggerStateChange) { + setTimelineOffset(offset); } - }, [playerType, currentEvent]); + }; const handleVideoTouch = () => { setHideBanner(true); @@ -131,19 +172,38 @@ export default function Camera({ camera }) { }; const handleTimelineChange = (event) => { - setCurrentEvent(event); + if (event !== undefined) { + setCurrentEvent(event); + setCurrentEventIndex(event.index); + } + }; + + const handlePlay = function () { + videoRef.current.play(); + }; + + const handlePaused = () => { + setTimelineOffset(undefined); + }; + + const handlePrevious = function () { + setCurrentEventIndex((index) => index - 1); + }; + + const handleNext = function () { + setCurrentEventIndex((index) => index + 1); }; return ( -
-
+
+
-
+
{(playerType === 'live' || playerType === 'debug') && ( - + {camera} @@ -152,27 +212,48 @@ export default function Camera({ camera }) {
-
-
+
+
{currentEvent && ( )} - + {renderPlayer}
- {playerType === 'history' && } + {playerType === 'history' && ( + + + +
+ + + +
+
+ )}
-
- - - - +
+ + + +