diff --git a/src/components/VideoPlayer.tsx b/src/components/VideoPlayer.tsx
new file mode 100644
index 0000000..2a5bc5b
--- /dev/null
+++ b/src/components/VideoPlayer.tsx
@@ -0,0 +1,21 @@
+import Video from 'react-native-video';
+
+export const VideoPlayer = ({
+ videoURI,
+ isPaused = true,
+}: {
+ videoURI: string;
+ isPaused?: boolean;
+}) => {
+ return (
+
+ );
+};
diff --git a/src/components/index.ts b/src/components/index.ts
index 45a5f92..42b56f8 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -3,3 +3,4 @@ export * from './baseView';
export * from './WebRTCView';
export * from './label';
export * from './snapshotCard';
+export * from './VideoPlayer';
diff --git a/src/components/snapshotCard.tsx b/src/components/snapshotCard.tsx
index 18aaf2e..8516560 100644
--- a/src/components/snapshotCard.tsx
+++ b/src/components/snapshotCard.tsx
@@ -18,7 +18,7 @@ export const SnapshotCard = ({
const imageHeight = imageWidth * 0.75;
return (
-
+
{
- before?: string;
- after?: string;
- cameras?: string;
- labels?: string;
- zones?: string;
- limit?: string;
- has_snapshot?: string;
- has_clip?: string;
- include_thumbnails?: string;
- in_progress?: string;
-}
-
-interface SnapshotQueryParams extends Record {
- h?: string;
- bbox?: string;
- timestamp?: string;
- crop?: string;
- quality?: string;
-}
+const URL = `${API_BASE}api/events`;
const buildSnapshotURL = (
eventId: string,
@@ -60,6 +23,11 @@ const buildSnapshotURL = (
return url;
};
+const buildEventUrl = (eventId: string) => {
+ const VOD_URL = `${API_BASE}vod/event/${eventId}/index.m3u8`;
+ return VOD_URL;
+};
+
const fetchEvents = async (
queryParams?: CameraEventParams,
snapShotQueryParams?: SnapshotQueryParams,
@@ -77,11 +45,16 @@ const fetchEvents = async (
if (data) {
const returnData: FrigateEvent[] = [];
for (const event of data) {
+ const enrichedEvent: FrigateEvent = {...event};
+
+ const vodURL = buildEventUrl(event.id);
+ enrichedEvent.vodURL = vodURL;
if (event.has_snapshot) {
const snapshotURL = buildSnapshotURL(event.id, snapShotQueryParams);
- returnData.push({...event, snapshotURL});
+ enrichedEvent.snapshotURL = snapshotURL;
+ returnData.push(enrichedEvent);
} else {
- returnData.push(event);
+ returnData.push(enrichedEvent);
}
}
return Promise.resolve(returnData);
diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts
index 81e8375..1bd5473 100644
--- a/src/lib/api/index.ts
+++ b/src/lib/api/index.ts
@@ -1,3 +1,6 @@
export * from './hooks/useCameraEvents';
export * from './hooks/useConfig';
export * from './getLatestCameraFrame';
+
+//? Types
+export * from './types';
diff --git a/src/lib/api/types/events.ts b/src/lib/api/types/events.ts
new file mode 100644
index 0000000..2a30ef3
--- /dev/null
+++ b/src/lib/api/types/events.ts
@@ -0,0 +1,45 @@
+export interface FrigateEvent {
+ //? Unsure what the non null types should be here
+ area: null | any;
+ box: null | any;
+ plus_id: null | any;
+ false_positive: null | any;
+ ratio: null | any;
+ camera: string;
+ end_time: number;
+ has_clip: boolean;
+ has_snapshot: boolean;
+ id: string;
+ label: string;
+ region: null | string;
+ retain_indefinitely: boolean;
+ start_time: number;
+ sub_label: null | string;
+ thumbnail: string;
+ top_score: number;
+ zones?: string[];
+ snapshotURL?: string;
+ vodURL: string;
+}
+
+export interface CameraEventParams extends Record {
+ before?: string;
+ after?: string;
+ cameras?: string;
+ labels?: string;
+ zones?: string;
+ limit?: string;
+ has_snapshot?: string;
+ has_clip?: string;
+ include_thumbnails?: string;
+ in_progress?: string;
+}
+
+export interface SnapshotQueryParams
+ extends Record {
+ h?: string;
+ bbox?: string;
+ timestamp?: string;
+ crop?: string;
+ quality?: string;
+}
diff --git a/src/lib/api/types/index.ts b/src/lib/api/types/index.ts
new file mode 100644
index 0000000..ad9d583
--- /dev/null
+++ b/src/lib/api/types/index.ts
@@ -0,0 +1,2 @@
+export * from './config';
+export * from './events';
diff --git a/src/screens/EventsScreen/EventsScreen.tsx b/src/screens/EventsScreen/EventsScreen.tsx
index 6fc5ddf..2c39956 100644
--- a/src/screens/EventsScreen/EventsScreen.tsx
+++ b/src/screens/EventsScreen/EventsScreen.tsx
@@ -1,9 +1,21 @@
-import {ActivityIndicator} from 'react-native';
+import {ActivityIndicator, FlatList, View} from 'react-native';
+
+import clsx from 'clsx';
import {CameraEvent} from './components';
import {useCameraEvents} from '@api';
import {BaseText, BaseView} from '@components';
import {useAppDataStore} from '@stores';
+import {bgBackground} from '@utils';
+
+const FooterComponent = ({length}: {length: number}) => (
+ //? I don't know why but we get some layout shift and that requires adding this height value here
+
+
+ Showing {length} event{length > 1 && 's'}.
+
+
+);
export const EventsScreen = () => {
const currentCamera = useAppDataStore(state => state.currentCamera);
@@ -46,14 +58,17 @@ export const EventsScreen = () => {
}
return (
-
- {events.map(camEvent => {
- return ;
- })}
+
+ }
+ showsVerticalScrollIndicator={false}
+ data={events}
+ renderItem={({item: camEvent}) => (
+
+ )}
+ />
{/* // TODO: Get total event info and group by date. Add pagination heree */}
-
- Showing {events.length} event{events.length > 1 && 's'}.
-
-
+
);
};
diff --git a/src/screens/EventsScreen/components/CameraEvent.tsx b/src/screens/EventsScreen/components/CameraEvent.tsx
index bab9bba..c8ec077 100644
--- a/src/screens/EventsScreen/components/CameraEvent.tsx
+++ b/src/screens/EventsScreen/components/CameraEvent.tsx
@@ -1,14 +1,25 @@
import React from 'react';
-import {useWindowDimensions} from 'react-native';
+import {
+ NativeScrollEvent,
+ NativeSyntheticEvent,
+ Pressable,
+ ScrollView,
+ useWindowDimensions,
+} from 'react-native';
+import {EventDetails} from './EventDetails';
import {FrigateEvent} from '@api';
-import {BaseView, SnapshotCard} from '@components';
+import {BaseView, SnapshotCard, VideoPlayer} from '@components';
export const CameraEvent = ({camEvent}: {camEvent: FrigateEvent}) => {
const {width} = useWindowDimensions();
const imageWidth = width * 0.97;
const imageHeight = imageWidth * 0.75;
+ const scrollviewRef = React.useRef(null);
+
+ const [videoIsPaused, setVideoIsPaused] = React.useState(true);
+
const getDateString = (date: Date) => {
return (
date.toLocaleString(undefined, {
@@ -21,19 +32,65 @@ export const CameraEvent = ({camEvent}: {camEvent: FrigateEvent}) => {
);
};
+ const onEventPress = () => {
+ scrollviewRef?.current?.scrollToEnd();
+ setVideoIsPaused(false);
+ };
+
+ const onScrollEnd = (event: NativeSyntheticEvent) => {
+ if (scrollviewRef.current) {
+ const xOffset = event.nativeEvent.contentOffset.x;
+ const scrollIndex = Math.round(xOffset / width);
+ if (scrollIndex === 2) {
+ setVideoIsPaused(false);
+ } else {
+ if (videoIsPaused === false) {
+ setVideoIsPaused(true);
+ }
+ }
+ }
+ };
+
const lastEventEnded = getDateString(new Date(camEvent?.end_time * 1000));
const lastThumbnail = 'data:image/png;base64,' + camEvent.thumbnail;
const lastEventImage = camEvent && camEvent.snapshotURL;
return (
-
- {(lastEventImage || lastThumbnail) && (
-
+
+ {/* //? Details on left */}
+
+
+
+
+ {/* //? snapshot in middle, default view */}
+
+ {(lastEventImage || lastThumbnail) && (
+
+
+
+ )}
+
+
+ {/* //? if there's a video, show that to the right */}
+ {camEvent.has_clip && (
+
+
+
)}
-
+
);
};
diff --git a/src/screens/EventsScreen/components/EventDetails.tsx b/src/screens/EventsScreen/components/EventDetails.tsx
new file mode 100644
index 0000000..3664d53
--- /dev/null
+++ b/src/screens/EventsScreen/components/EventDetails.tsx
@@ -0,0 +1,63 @@
+import {ViewProps} from 'react-native';
+
+import {FrigateEvent} from '@api';
+import {BaseText, BaseView} from '@components';
+import {toTitleCase} from '@utils';
+
+const Row = (props: ViewProps) => (
+
+);
+
+export const EventDetails = ({camEvent}: {camEvent: FrigateEvent}) => {
+ const startDate = new Date(camEvent.start_time * 1000);
+ const endDate = new Date(camEvent.end_time * 1000);
+ // TODO: format minutes here too.
+ const eventDuration = Math.round(camEvent.end_time - camEvent.start_time);
+
+ return (
+
+ Event Details
+
+ Start Time
+
+ {startDate.toLocaleDateString() +
+ ' @ ' +
+ startDate.toLocaleTimeString()}
+
+
+
+ End Time
+
+ {endDate.toLocaleDateString() + ' @ ' + endDate.toLocaleTimeString()}
+
+
+
+ Event Duration
+ {eventDuration} seconds
+
+
+ Object Label
+ {toTitleCase(camEvent.label)}
+
+
+ Confidence
+ {Math.round(camEvent.top_score * 10000) / 100}%
+
+ {!!camEvent?.zones?.length && (
+
+ Zones
+ LABEL
+
+ )}
+ {!!camEvent.region && (
+
+ Region
+ LABEL
+
+ )}
+
+ );
+};