Metadata Timeline (#6194)

* Create timeline table

* Fix indexes

* Add other fields

* Adjust schema to be less descriptive

* Handle timeline queue from tracked object data

* Setup timeline queue in events

* Add source id for index

* Add other fields

* Fixes

* Formatting

* Store better data

* Add api with filtering

* Setup basic UI for timeline in events

* Cleanups

* Add recordings snapshot url

* Start working on timeline ui

* Add tooltip with info

* Improve icons

* Fix start time with clip

* Move player logic back to clips

* Make box in timeline relative coordinates

* Make region relative

* Get box overlay working

* Remove overlay when playing again

* Add disclaimer when selecting overlay points

* Add docs for new apis

* Fix mobile

* Fix docs

* Change color of bottom center box

* Fix vscode formatting
This commit is contained in:
Nicolas Mowen
2023-04-23 09:45:19 -06:00
committed by GitHub
parent 3c72b96042
commit fbaab71d78
12 changed files with 494 additions and 23 deletions

View File

@@ -26,6 +26,7 @@ import Dialog from '../components/Dialog';
import MultiSelect from '../components/MultiSelect';
import { formatUnixTimestampToDateTime, getDurationFromTimestamps } from '../utils/dateUtil';
import TimeAgo from '../components/TimeAgo';
import TimelineSummary from '../components/TimelineSummary';
const API_LIMIT = 25;
@@ -60,6 +61,7 @@ export default function Events({ path, ...props }) {
});
const [uploading, setUploading] = useState([]);
const [viewEvent, setViewEvent] = useState();
const [eventOverlay, setEventOverlay] = useState();
const [eventDetailType, setEventDetailType] = useState('clip');
const [downloadEvent, setDownloadEvent] = useState({
id: null,
@@ -180,6 +182,18 @@ export default function Events({ path, ...props }) {
onFilter(name, items);
};
const onEventFrameSelected = (event, frame) => {
const eventDuration = event.end_time - event.start_time;
if (this.player) {
this.player.pause();
const videoOffset = this.player.duration() - eventDuration;
const startTime = videoOffset + (frame.timestamp - event.start_time);
this.player.currentTime(startTime);
setEventOverlay(frame);
}
};
const datePicker = useRef();
const downloadButton = useRef();
@@ -526,7 +540,7 @@ export default function Events({ path, ...props }) {
</div>
</div>
<div class="hidden sm:flex flex-col justify-end mr-2">
{(event.end_time && event.has_snapshot) && (
{event.end_time && event.has_snapshot && (
<Fragment>
{event.plus_id ? (
<div className="uppercase text-xs">Sent to Frigate+</div>
@@ -573,20 +587,52 @@ export default function Events({ path, ...props }) {
<div>
{eventDetailType == 'clip' && event.has_clip ? (
<VideoPlayer
options={{
preload: 'auto',
autoplay: true,
sources: [
{
src: `${apiHost}vod/event/${event.id}/master.m3u8`,
type: 'application/vnd.apple.mpegurl',
},
],
}}
seekOptions={{ forward: 10, backward: 5 }}
onReady={() => {}}
/>
<div>
<TimelineSummary
event={event}
onFrameSelected={(frame) => onEventFrameSelected(event, frame)}
/>
<div>
<VideoPlayer
options={{
preload: 'auto',
autoplay: true,
sources: [
{
src: `${apiHost}vod/event/${event.id}/master.m3u8`,
type: 'application/vnd.apple.mpegurl',
},
],
}}
seekOptions={{ forward: 10, backward: 5 }}
onReady={(player) => {
this.player = player;
this.player.on('playing', () => {
setEventOverlay(undefined);
});
}}
onDispose={() => {
this.player = null;
}}
>
{eventOverlay ? (
<div
className="absolute border-4 border-red-600"
style={{
left: `${Math.round(eventOverlay.data.box[0] * 100)}%`,
top: `${Math.round(eventOverlay.data.box[1] * 100)}%`,
right: `${Math.round((1 - eventOverlay.data.box[2]) * 100)}%`,
bottom: `${Math.round((1 - eventOverlay.data.box[3]) * 100)}%`,
}}
>
{eventOverlay.class_type == 'entered_zone' ? (
<div className="absolute w-2 h-2 bg-yellow-500 left-[50%] bottom-0" />
) : null}
</div>
) : null}
</VideoPlayer>
</div>
</div>
) : null}
{eventDetailType == 'image' || !event.has_clip ? (