refactor: used hls system instead

This commit is contained in:
JohnMark Sill 2022-02-10 14:39:47 -06:00
parent 46020693cd
commit 7401789b3b
2 changed files with 132 additions and 56 deletions

View File

@ -14,13 +14,14 @@ const getLast24Hours = () => {
}; };
export default function HistoryViewer({ camera }) { export default function HistoryViewer({ camera }) {
console.log('history', { camera });
const apiHost = useApiHost(); const apiHost = useApiHost();
const videoRef = useRef(); const videoRef = useRef();
const { searchString } = useSearchString(200, `camera=${camera}&after=${getLast24Hours()}`); const { searchString } = useSearchString(500, `camera=${camera}&after=${getLast24Hours()}`);
const { data: events } = useEvents(searchString); const { data: events } = useEvents(searchString);
const [timelineEvents, setTimelineEvents] = useState(); const [timelineEvents, setTimelineEvents] = useState();
const [hasPlayed, setHasPlayed] = useState();
const [isPlaying, setIsPlaying] = useState(false);
const [currentEvent, setCurrentEvent] = useState(); const [currentEvent, setCurrentEvent] = useState();
const [currentEventIndex, setCurrentEventIndex] = useState(); const [currentEventIndex, setCurrentEventIndex] = useState();
const [timelineOffset, setTimelineOffset] = useState(0); const [timelineOffset, setTimelineOffset] = useState(0);
@ -28,11 +29,18 @@ export default function HistoryViewer({ camera }) {
useEffect(() => { useEffect(() => {
if (events) { if (events) {
setTimelineEvents([...events].reverse().filter((e) => e.end_time !== undefined)); const filteredEvents = [...events].reverse().filter((e) => e.end_time !== undefined);
setTimelineEvents(filteredEvents);
setCurrentEventIndex(filteredEvents.length - 1);
} }
}, [events]); }, [events]);
const handleTimeUpdate = () => { const handleTimeUpdate = () => {
const videoContainer = videoRef.current;
if (videoContainer.paused) {
return;
}
const timestamp = Math.round(videoRef.current.currentTime); const timestamp = Math.round(videoRef.current.currentTime);
const offset = Math.round(timestamp); const offset = Math.round(timestamp);
const triggerStateChange = offset !== timelineOffset; const triggerStateChange = offset !== timelineOffset;
@ -41,18 +49,50 @@ export default function HistoryViewer({ camera }) {
} }
}; };
const handleTimelineChange = (event) => { const handleTimelineChange = (timelineChangedEvent) => {
if (event !== undefined) { if (timelineChangedEvent.seekComplete) {
setCurrentEvent(event); const currentEventExists = currentEvent !== undefined;
if (!currentEventExists || currentEvent.id !== timelineChangedEvent.event.id) {
setCurrentEvent(timelineChangedEvent.event);
}
}
const videoContainer = videoRef.current;
if (videoContainer) {
if (!videoContainer.paused) {
videoContainer.pause();
setHasPlayed(true);
}
const videoHasPermissionToPlay = hasPlayed !== undefined;
if (videoHasPermissionToPlay && timelineChangedEvent.seekComplete) {
const markerTime = Math.abs(timelineChangedEvent.time - timelineChangedEvent.event.startTime) / 1000;
videoContainer.currentTime = markerTime;
if (hasPlayed) {
videoContainer.play();
setHasPlayed(false);
}
}
} }
}; };
const handlePlay = function () { const handlePlay = function () {
videoRef.current.play(); const videoContainer = videoRef.current;
if (videoContainer) {
if (videoContainer.paused) {
videoContainer.play();
} else {
videoContainer.pause();
}
}
};
const handlePlayed = () => {
setIsPlaying(true);
}; };
const handlePaused = () => { const handlePaused = () => {
setTimelineOffset(undefined); setIsPlaying(false);
}; };
const handlePrevious = function () { const handlePrevious = function () {
@ -64,35 +104,36 @@ export default function HistoryViewer({ camera }) {
}; };
const handleMetadataLoad = () => { const handleMetadataLoad = () => {
if (videoRef.current) { const videoContainer = videoRef.current;
setMinHeight(videoRef.current.clientHeight); if (videoContainer) {
setMinHeight(videoContainer.clientHeight);
} }
}; };
const RenderVideo = useCallback(() => { const RenderVideo = useCallback(() => {
return ( if (currentEvent) {
<video return (
ref={videoRef} <video
onTimeUpdate={handleTimeUpdate} ref={videoRef}
onPause={handlePaused} onTimeUpdate={handleTimeUpdate}
poster={`${apiHost}/api/events/${currentEvent.id}/snapshot.jpg`} onPause={handlePaused}
onLoadedMetadata={handleMetadataLoad} onPlay={handlePlayed}
preload='metadata' poster={`${apiHost}/api/events/${currentEvent.id}/snapshot.jpg`}
style={ onLoadedMetadata={handleMetadataLoad}
minHeight preload='metadata'
? { style={
minHeight: `${minHeight}px`, minHeight
} ? {
: {} minHeight: `${minHeight}px`,
} }
playsInline : {}
controls }
> playsInline
<source >
src={`${apiHost}/api/${camera}/start/${currentEvent.start_time}/end/${currentEvent.end_time}/clip.mp4`} <source type='application/vnd.apple.mpegurl' src={`${apiHost}/vod/event/${currentEvent.id}/index.m3u8`} />
/> </video>
</video> );
); }
}, [currentEvent, apiHost, camera, videoRef]); }, [currentEvent, apiHost, camera, videoRef]);
return ( return (
@ -115,6 +156,7 @@ export default function HistoryViewer({ camera }) {
events={timelineEvents} events={timelineEvents}
offset={timelineOffset} offset={timelineOffset}
currentIndex={currentEventIndex} currentIndex={currentEventIndex}
disabled={isPlaying}
onChange={handleTimelineChange} onChange={handleTimelineChange}
/> />

View File

@ -2,7 +2,7 @@ import { h } from 'preact';
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'; import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
import { longToDate } from '../utils/dateUtil'; import { longToDate } from '../utils/dateUtil';
export default function Timeline({ events, offset, currentIndex, onChange }) { export default function Timeline({ events, offset, currentIndex, disabled, onChange }) {
const timelineContainerRef = useRef(undefined); const timelineContainerRef = useRef(undefined);
const [timeline, setTimeline] = useState([]); const [timeline, setTimeline] = useState([]);
@ -11,6 +11,7 @@ export default function Timeline({ events, offset, currentIndex, onChange }) {
const [currentEvent, setCurrentEvent] = useState(); const [currentEvent, setCurrentEvent] = useState();
const [scrollTimeout, setScrollTimeout] = useState(); const [scrollTimeout, setScrollTimeout] = useState();
const [scrollActive, setScrollActive] = useState(true); const [scrollActive, setScrollActive] = useState(true);
const [eventsEnabled, setEventsEnable] = useState(true);
useEffect(() => { useEffect(() => {
if (events && events.length > 0 && timelineOffset) { if (events && events.length > 0 && timelineOffset) {
@ -40,8 +41,8 @@ export default function Timeline({ events, offset, currentIndex, onChange }) {
...firstTimelineEvent, ...firstTimelineEvent,
id: firstTimelineEvent.id, id: firstTimelineEvent.id,
index: 0, index: 0,
startTime: firstTimelineEvent.start_time, startTime: longToDate(firstTimelineEvent.start_time),
endTime: firstTimelineEvent.end_time, endTime: longToDate(firstTimelineEvent.end_time),
}); });
setTimeline(timelineEvents); setTimeline(timelineEvents);
} }
@ -54,25 +55,22 @@ export default function Timeline({ events, offset, currentIndex, onChange }) {
useEffect(() => { useEffect(() => {
const cEvent = getCurrentEvent(); const cEvent = getCurrentEvent();
if (cEvent && offset >= 0) { if (cEvent && offset >= 0) {
setScrollActive(false);
timelineContainerRef.current.scroll({ timelineContainerRef.current.scroll({
left: cEvent.positionX + offset - timelineOffset, left: cEvent.positionX + offset - timelineOffset,
behavior: 'smooth', behavior: 'smooth',
}); });
} else {
setScrollActive(true);
} }
}, [offset, timelineContainerRef]); }, [offset, timelineContainerRef]);
useEffect(() => { useEffect(() => {
if (currentIndex !== undefined) { if (timeline.length > 0 && currentIndex !== undefined) {
const event = timeline[currentIndex]; const event = timeline[currentIndex];
setCurrentEvent({ setCurrentEvent({
...event, ...event,
id: event.id, id: event.id,
index: currentIndex, index: currentIndex,
startTime: event.start_time, startTime: longToDate(event.start_time),
endTime: event.end_time, endTime: longToDate(event.end_time),
}); });
timelineContainerRef.current.scroll({ left: event.positionX - timelineOffset, behavior: 'smooth' }); timelineContainerRef.current.scroll({ left: event.positionX - timelineOffset, behavior: 'smooth' });
} }
@ -88,32 +86,53 @@ export default function Timeline({ events, offset, currentIndex, onChange }) {
const foundIndex = timeline.findIndex((event) => event.startTime <= markerTime && markerTime <= event.endTime); const foundIndex = timeline.findIndex((event) => event.startTime <= markerTime && markerTime <= event.endTime);
if (foundIndex > -1) { if (foundIndex > -1) {
const found = timeline[foundIndex]; const found = timeline[foundIndex];
setCurrentEvent({ if (found !== currentEvent && found.id !== currentEvent.id) {
...found, setCurrentEvent({
id: found.id, ...found,
index: foundIndex, id: found.id,
startTime: found.start_time, index: foundIndex,
endTime: found.end_time, startTime: longToDate(found.start_time),
}); endTime: longToDate(found.end_time),
});
return found;
}
} }
} }
}; };
const handleScroll = (event) => { const scrollLogic = () => {
clearTimeout(scrollTimeout); clearTimeout(scrollTimeout);
const scrollPosition = event.target.scrollLeft; const scrollPosition = timelineContainerRef.current.scrollLeft;
const startTime = longToDate(timeline[0].start_time); const startTime = longToDate(timeline[0].start_time);
const markerTime = new Date(startTime.getTime() + scrollPosition * 1000); const markerTime = new Date(startTime.getTime() + scrollPosition * 1000);
setMarkerTime(markerTime); setMarkerTime(markerTime);
handleChange(currentEvent, markerTime, false);
setScrollTimeout( setScrollTimeout(
setTimeout(() => { setTimeout(() => {
checkMarkerForEvent(markerTime); const foundEvent = checkMarkerForEvent(markerTime);
handleChange(foundEvent ? foundEvent : currentEvent, markerTime, true);
}, 250) }, 250)
); );
}; };
const handleWheel = (event) => {
if (!disabled) {
return;
}
scrollLogic(event);
};
const handleScroll = (event) => {
if (disabled) {
return;
}
scrollLogic(event);
};
useEffect(() => { useEffect(() => {
if (timelineContainerRef) { if (timelineContainerRef) {
const timelineContainerWidth = timelineContainerRef.current.offsetWidth; const timelineContainerWidth = timelineContainerRef.current.offsetWidth;
@ -122,9 +141,18 @@ export default function Timeline({ events, offset, currentIndex, onChange }) {
} }
}, [timelineContainerRef]); }, [timelineContainerRef]);
useEffect(() => { const handleChange = useCallback(
onChange && onChange(currentEvent); (event, time, seekComplete) => {
}, [onChange, currentEvent]); if (onChange !== undefined) {
onChange({
event,
time,
seekComplete,
});
}
},
[onChange]
);
const RenderTimeline = useCallback(() => { const RenderTimeline = useCallback(() => {
if (timeline && timeline.length > 0) { if (timeline && timeline.length > 0) {
@ -175,7 +203,13 @@ export default function Timeline({ events, offset, currentIndex, onChange }) {
></div> ></div>
</div> </div>
</div> </div>
<div ref={timelineContainerRef} className='overflow-x-auto hide-scroll' onScroll={handleScroll}> <div
ref={timelineContainerRef}
onWheel={handleWheel}
onTouchMove={handleWheel}
onScroll={handleScroll}
className='overflow-x-auto hide-scroll'
>
<RenderTimeline /> <RenderTimeline />
</div> </div>
</div> </div>