Handle offset timezones

This commit is contained in:
Nicolas Mowen 2024-05-19 12:06:54 -06:00
parent 171a142adb
commit 22d977661a
5 changed files with 62 additions and 15 deletions

View File

@ -10,7 +10,7 @@ import useSWR from "swr";
import { FrigateConfig } from "@/types/frigateConfig"; import { FrigateConfig } from "@/types/frigateConfig";
import { Preview } from "@/types/preview"; import { Preview } from "@/types/preview";
import { PreviewPlayback } from "@/types/playback"; import { PreviewPlayback } from "@/types/playback";
import { isCurrentHour } from "@/utils/dateUtil"; import { getUTCOffset, isCurrentHour } from "@/utils/dateUtil";
import { baseUrl } from "@/api/baseUrl"; import { baseUrl } from "@/api/baseUrl";
import { isAndroid, isChrome, isMobile } from "react-device-detect"; import { isAndroid, isChrome, isMobile } from "react-device-detect";
import { TimeRange } from "@/types/timeline"; import { TimeRange } from "@/types/timeline";
@ -41,11 +41,21 @@ export default function PreviewPlayer({
const [currentHourFrame, setCurrentHourFrame] = useState<string>(); const [currentHourFrame, setCurrentHourFrame] = useState<string>();
const currentPreview = useMemo(() => { const currentPreview = useMemo(() => {
const timeRangeOffset =
(getUTCOffset(new Date(timeRange.before * 1000)) % 60) * 60;
console.log(`the offset is ${timeRangeOffset}`);
cameraPreviews.forEach((preview) =>
console.log(
`check ${new Date(Math.round(preview.start) * 1000)} >= ${new Date((timeRange.after + timeRangeOffset) * 1000)}`,
),
);
return cameraPreviews.find( return cameraPreviews.find(
(preview) => (preview) =>
preview.camera == camera && preview.camera == camera &&
Math.round(preview.start) >= timeRange.after && Math.round(preview.start) >= timeRange.after + timeRangeOffset &&
Math.floor(preview.end) <= timeRange.before, Math.floor(preview.end) <= timeRange.before + timeRangeOffset,
); );
}, [cameraPreviews, camera, timeRange]); }, [cameraPreviews, camera, timeRange]);
@ -225,11 +235,14 @@ function PreviewVideoPlayer({
return; return;
} }
const timeRangeOffset =
getUTCOffset(new Date(timeRange.before * 1000)) % 60;
const preview = cameraPreviews.find( const preview = cameraPreviews.find(
(preview) => (preview) =>
preview.camera == camera && preview.camera == camera &&
Math.round(preview.start) >= timeRange.after && Math.round(preview.start) >= timeRange.after + timeRangeOffset &&
Math.floor(preview.end) <= timeRange.before, Math.floor(preview.end) <= timeRange.before + timeRangeOffset,
); );
if (preview != currentPreview) { if (preview != currentPreview) {

View File

@ -12,6 +12,7 @@ import ActivityIndicator from "@/components/indicators/activity-indicator";
import { VideoResolutionType } from "@/types/live"; import { VideoResolutionType } from "@/types/live";
import axios from "axios"; import axios from "axios";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { getUTCOffset } from "@/utils/dateUtil";
/** /**
* Dynamically switches between video playback and scrubbing preview player. * Dynamically switches between video playback and scrubbing preview player.
@ -49,6 +50,12 @@ export default function DynamicVideoPlayer({
const apiHost = useApiHost(); const apiHost = useApiHost();
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
useEffect(() => {
console.log(
`the time range is ${new Date(timeRange.after * 1000)} -> ${new Date(timeRange.before * 1000)}`,
);
}, [timeRange]);
// controlling playback // controlling playback
const playerRef = useRef<HTMLVideoElement | null>(null); const playerRef = useRef<HTMLVideoElement | null>(null);
@ -148,9 +155,12 @@ export default function DynamicVideoPlayer({
// state of playback player // state of playback player
const recordingParams = useMemo(() => { const recordingParams = useMemo(() => {
const timeRangeOffset =
(getUTCOffset(new Date(timeRange.before * 1000)) % 60) * 60;
return { return {
before: timeRange.before, before: timeRange.before + timeRangeOffset,
after: timeRange.after, after: timeRange.after + timeRangeOffset,
}; };
}, [timeRange]); }, [timeRange]);
const { data: recordings } = useSWR<Recording[]>( const { data: recordings } = useSWR<Recording[]>(
@ -168,7 +178,7 @@ export default function DynamicVideoPlayer({
} }
setSource( setSource(
`${apiHost}vod/${camera}/start/${timeRange.after}/end/${timeRange.before}/master.m3u8`, `${apiHost}vod/${camera}/start/${recordingParams.after}/end/${recordingParams.before}/master.m3u8`,
); );
setLoadingTimeout(setTimeout(() => setIsLoading(true), 1000)); setLoadingTimeout(setTimeout(() => setIsLoading(true), 1000));

View File

@ -1,5 +1,6 @@
import strftime from "strftime"; import strftime from "strftime";
import { fromUnixTime, intervalToDuration, formatDuration } from "date-fns"; import { fromUnixTime, intervalToDuration, formatDuration } from "date-fns";
import { useMemo } from "react";
export const longToDate = (long: number): Date => new Date(long * 1000); export const longToDate = (long: number): Date => new Date(long * 1000);
export const epochToLong = (date: number): number => date / 1000; export const epochToLong = (date: number): number => date / 1000;
export const dateToLong = (date: Date): number => epochToLong(date.getTime()); export const dateToLong = (date: Date): number => epochToLong(date.getTime());
@ -235,7 +236,10 @@ export const getDurationFromTimestamps = (
* @param timezone string representation of the timezone the user is requesting * @param timezone string representation of the timezone the user is requesting
* @returns number of minutes offset from UTC * @returns number of minutes offset from UTC
*/ */
export const getUTCOffset = (date: Date, timezone: string): number => { export const getUTCOffset = (
date: Date,
timezone: string = getResolvedTimeZone(),
): number => {
// If timezone is in UTC±HH:MM format, parse it to get offset // If timezone is in UTC±HH:MM format, parse it to get offset
const utcOffsetMatch = timezone.match(/^UTC([+-])(\d{2}):(\d{2})$/); const utcOffsetMatch = timezone.match(/^UTC([+-])(\d{2}):(\d{2})$/);
if (utcOffsetMatch) { if (utcOffsetMatch) {
@ -259,10 +263,10 @@ export const getUTCOffset = (date: Date, timezone: string): number => {
target = new Date(`${iso}+000`); target = new Date(`${iso}+000`);
} }
return ( return Math.round(
(target.getTime() - utcDate.getTime() - date.getTimezoneOffset()) / (target.getTime() - utcDate.getTime() - date.getTimezoneOffset()) /
60 / 60 /
1000 1000,
); );
}; };

View File

@ -20,7 +20,7 @@ import {
MdOutlinePictureInPictureAlt, MdOutlinePictureInPictureAlt,
} from "react-icons/md"; } from "react-icons/md";
import { FaBicycle } from "react-icons/fa"; import { FaBicycle } from "react-icons/fa";
import { endOfHourOrCurrentTime } from "./dateUtil"; import { endOfHourOrCurrentTime, getUTCOffset } from "./dateUtil";
import { TimeRange, Timeline } from "@/types/timeline"; import { TimeRange, Timeline } from "@/types/timeline";
export function getTimelineIcon(timelineItem: Timeline) { export function getTimelineIcon(timelineItem: Timeline) {
@ -131,12 +131,25 @@ export function getChunkedTimeDay(timeRange: TimeRange): TimeRange[] {
endOfThisHour.setSeconds(0, 0); endOfThisHour.setSeconds(0, 0);
const data: TimeRange[] = []; const data: TimeRange[] = [];
const startDay = new Date(timeRange.after * 1000); const startDay = new Date(timeRange.after * 1000);
startDay.setMinutes(0, 0, 0); const timezoneMinuteOffset =
getUTCOffset(new Date(timeRange.before * 1000)) % 60;
if (timezoneMinuteOffset == 0) {
startDay.setMinutes(0, 0, 0);
} else {
startDay.setSeconds(0, 0);
}
let start = startDay.getTime() / 1000; let start = startDay.getTime() / 1000;
let end = 0; let end = 0;
for (let i = 0; i < 24; i++) { for (let i = 0; i < 24; i++) {
startDay.setHours(startDay.getHours() + 1, 0, 0, 0); startDay.setHours(
startDay.getHours() + 1,
Math.abs(timezoneMinuteOffset),
0,
0,
);
if (startDay > endOfThisHour) { if (startDay > endOfThisHour) {
break; break;

View File

@ -101,6 +101,13 @@ export function RecordingView({
() => getChunkedTimeDay(timeRange), () => getChunkedTimeDay(timeRange),
[timeRange], [timeRange],
); );
useEffect(() => {
chunkedTimeRange.forEach((c) =>
console.log(
`the chunk is ${new Date(c.after * 1000)} -> ${new Date(c.before * 1000)}`,
),
);
}, [chunkedTimeRange]);
const [selectedRangeIdx, setSelectedRangeIdx] = useState( const [selectedRangeIdx, setSelectedRangeIdx] = useState(
chunkedTimeRange.findIndex((chunk) => { chunkedTimeRange.findIndex((chunk) => {
return chunk.after <= startTime && chunk.before >= startTime; return chunk.after <= startTime && chunk.before >= startTime;