mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-02 09:15:22 +03:00
refactor: used hls system instead
This commit is contained in:
parent
46020693cd
commit
7401789b3b
@ -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,17 +104,20 @@ 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(() => {
|
||||||
|
if (currentEvent) {
|
||||||
return (
|
return (
|
||||||
<video
|
<video
|
||||||
ref={videoRef}
|
ref={videoRef}
|
||||||
onTimeUpdate={handleTimeUpdate}
|
onTimeUpdate={handleTimeUpdate}
|
||||||
onPause={handlePaused}
|
onPause={handlePaused}
|
||||||
|
onPlay={handlePlayed}
|
||||||
poster={`${apiHost}/api/events/${currentEvent.id}/snapshot.jpg`}
|
poster={`${apiHost}/api/events/${currentEvent.id}/snapshot.jpg`}
|
||||||
onLoadedMetadata={handleMetadataLoad}
|
onLoadedMetadata={handleMetadataLoad}
|
||||||
preload='metadata'
|
preload='metadata'
|
||||||
@ -86,13 +129,11 @@ export default function HistoryViewer({ camera }) {
|
|||||||
: {}
|
: {}
|
||||||
}
|
}
|
||||||
playsInline
|
playsInline
|
||||||
controls
|
|
||||||
>
|
>
|
||||||
<source
|
<source type='application/vnd.apple.mpegurl' src={`${apiHost}/vod/event/${currentEvent.id}/index.m3u8`} />
|
||||||
src={`${apiHost}/api/${camera}/start/${currentEvent.start_time}/end/${currentEvent.end_time}/clip.mp4`}
|
|
||||||
/>
|
|
||||||
</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}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -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];
|
||||||
|
if (found !== currentEvent && found.id !== currentEvent.id) {
|
||||||
setCurrentEvent({
|
setCurrentEvent({
|
||||||
...found,
|
...found,
|
||||||
id: found.id,
|
id: found.id,
|
||||||
index: foundIndex,
|
index: foundIndex,
|
||||||
startTime: found.start_time,
|
startTime: longToDate(found.start_time),
|
||||||
endTime: found.end_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>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user