blakeblackshear.frigate/web/src/routes/Recording.jsx

160 lines
5.2 KiB
React
Raw Normal View History

2021-05-28 19:13:48 +02:00
import { h } from 'preact';
2022-05-10 14:48:29 +02:00
import { parseISO, endOfHour, startOfHour, getUnixTime } from 'date-fns';
import { useEffect, useMemo } from 'preact/hooks';
2021-05-28 19:13:48 +02:00
import ActivityIndicator from '../components/ActivityIndicator';
import Heading from '../components/Heading';
2021-06-02 08:41:26 +02:00
import RecordingPlaylist from '../components/RecordingPlaylist';
2021-05-28 19:13:48 +02:00
import VideoPlayer from '../components/VideoPlayer';
2022-02-26 20:11:00 +01:00
import { useApiHost } from '../api';
import useSWR from 'swr';
2021-05-28 19:13:48 +02:00
2022-05-10 14:48:29 +02:00
export default function Recording({ camera, date, hour = '00', minute = '00', second = '00' }) {
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
2022-05-10 14:48:29 +02:00
const currentDate = useMemo(
() => (date ? parseISO(`${date}T${hour || '00'}:${minute || '00'}:${second || '00'}`) : new Date()),
[date, hour, minute, second]
);
2021-05-28 19:13:48 +02:00
const apiHost = useApiHost();
const { data: recordingsSummary } = useSWR([`${camera}/recordings/summary`, { timezone }], {
revalidateOnFocus: false,
});
2021-05-28 19:13:48 +02:00
2022-05-10 14:48:29 +02:00
const recordingParams = {
before: getUnixTime(endOfHour(currentDate)),
after: getUnixTime(startOfHour(currentDate)),
};
2022-07-07 14:05:05 +02:00
const { data: recordings } = useSWR([`${camera}/recordings`, recordingParams], { revalidateOnFocus: false });
2021-05-28 19:13:48 +02:00
2022-05-10 14:48:29 +02:00
// calculates the seek seconds by adding up all the seconds in the segments prior to the playback time
const seekSeconds = useMemo(() => {
if (!recordings) {
return 0;
}
const currentUnix = getUnixTime(currentDate);
2022-05-10 14:48:29 +02:00
const hourStart = getUnixTime(startOfHour(currentDate));
let seekSeconds = 0;
recordings.every((segment) => {
// if the next segment is past the desired time, stop calculating
if (segment.start_time > currentUnix) {
return false;
}
// if the segment starts before the hour, skip the seconds before the hour
const start = segment.start_time < hourStart ? hourStart : segment.start_time;
// if the segment ends after the selected time, use the selected time for end
const end = segment.end_time > currentUnix ? currentUnix : segment.end_time;
seekSeconds += end - start;
return true;
});
return seekSeconds;
}, [recordings, currentDate]);
const playlist = useMemo(() => {
if (!recordingsSummary) {
return [];
}
const selectedDayRecordingData = recordingsSummary.find((s) => !date || s.day === date);
if (!selectedDayRecordingData) {
return [];
}
2022-05-10 14:48:29 +02:00
const [year, month, day] = selectedDayRecordingData.day.split('-');
return selectedDayRecordingData.hours
.map((h) => {
return {
name: h.hour,
description: `${camera} recording @ ${h.hour}:00.`,
2021-05-28 19:13:48 +02:00
sources: [
{
src: `${apiHost}vod/${year}-${month}/${day}/${h.hour}/${camera}/${timezone.replaceAll(
'/',
'_'
)}/master.m3u8`,
2021-05-28 19:13:48 +02:00
type: 'application/vnd.apple.mpegurl',
},
],
2022-05-10 14:48:29 +02:00
};
})
.reverse();
}, [apiHost, date, recordingsSummary, camera, timezone]);
2022-05-10 14:48:29 +02:00
const playlistIndex = useMemo(() => {
const index = playlist.findIndex((item) => item.name === hour);
if (index === -1) {
return 0;
2021-05-28 19:13:48 +02:00
}
2022-05-10 14:48:29 +02:00
return index;
}, [playlist, hour]);
useEffect(() => {
if (this.player) {
this.player.playlist(playlist);
}
}, [playlist]);
2021-05-28 19:13:48 +02:00
2022-05-10 14:48:29 +02:00
useEffect(() => {
if (this.player) {
this.player.playlist.currentItem(playlistIndex);
}
}, [playlistIndex]);
2021-05-28 19:13:48 +02:00
2022-05-10 14:48:29 +02:00
useEffect(() => {
if (this.player) {
// if the playlist has moved on to the next item, then reset
if (this.player.playlist.currentItem() !== playlistIndex) {
this.player.playlist.currentItem(playlistIndex);
2021-06-02 10:27:07 +02:00
}
2022-05-10 14:48:29 +02:00
this.player.currentTime(seekSeconds);
// try and play since the user is likely to have interacted with the dom
this.player.play();
2021-05-28 19:13:48 +02:00
}
2022-05-10 14:48:29 +02:00
}, [seekSeconds, playlistIndex]);
if (!recordingsSummary || !recordings) {
2022-05-10 14:48:29 +02:00
return <ActivityIndicator />;
}
if (recordingsSummary.length === 0) {
return (
<div className="space-y-4">
<Heading>{camera} Recordings</Heading>
<div class="bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4" role="alert">
<p class="font-bold">No Recordings Found</p>
<p>Make sure you have enabled the record role in your configuration for the {camera} camera.</p>
</div>
</div>
);
2021-05-28 19:13:48 +02:00
}
return (
2022-02-27 15:04:12 +01:00
<div className="space-y-4 p-2 px-4">
<Heading>{camera.replaceAll('_', ' ')} Recordings</Heading>
<div className="text-xs">Dates and times are based on the browser's timezone {timezone}</div>
2021-05-28 19:13:48 +02:00
<VideoPlayer
options={{
preload: 'auto',
}}
2021-05-28 19:13:48 +02:00
onReady={(player) => {
2022-05-10 14:48:29 +02:00
player.on('ratechange', () => player.defaultPlaybackRate(player.playbackRate()));
2021-05-28 19:13:48 +02:00
if (player.playlist) {
player.playlist(playlist);
player.playlist.autoadvance(0);
2022-05-10 14:48:29 +02:00
player.playlist.currentItem(playlistIndex);
player.currentTime(seekSeconds);
2021-05-28 19:13:48 +02:00
this.player = player;
}
}}
onDispose={() => {
this.player = null;
}}
2021-06-02 08:41:26 +02:00
>
2022-05-10 14:48:29 +02:00
<RecordingPlaylist camera={camera} recordings={recordingsSummary} selectedDate={currentDate} />
2021-06-02 08:41:26 +02:00
</VideoPlayer>
2021-05-28 19:13:48 +02:00
</div>
);
}