mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-11 05:35:25 +03:00
Fix current hour
This commit is contained in:
parent
223b6c19de
commit
9b72f2dedd
@ -12,8 +12,7 @@ import { Preview } from "@/types/preview";
|
|||||||
import { PreviewPlayback } from "@/types/playback";
|
import { PreviewPlayback } from "@/types/playback";
|
||||||
import { isCurrentHour } from "@/utils/dateUtil";
|
import { isCurrentHour } from "@/utils/dateUtil";
|
||||||
import { baseUrl } from "@/api/baseUrl";
|
import { baseUrl } from "@/api/baseUrl";
|
||||||
import { isAndroid, isChrome, isMobile, isSafari } from "react-device-detect";
|
import { isAndroid, isChrome, isMobile } from "react-device-detect";
|
||||||
import { Skeleton } from "../ui/skeleton";
|
|
||||||
import { TimeRange } from "@/types/timeline";
|
import { TimeRange } from "@/types/timeline";
|
||||||
|
|
||||||
type PreviewPlayerProps = {
|
type PreviewPlayerProps = {
|
||||||
@ -34,7 +33,6 @@ export default function PreviewPlayer({
|
|||||||
cameraPreviews,
|
cameraPreviews,
|
||||||
startTime,
|
startTime,
|
||||||
isScrubbing,
|
isScrubbing,
|
||||||
forceAspect,
|
|
||||||
onControllerReady,
|
onControllerReady,
|
||||||
onClick,
|
onClick,
|
||||||
}: PreviewPlayerProps) {
|
}: PreviewPlayerProps) {
|
||||||
@ -62,7 +60,6 @@ export default function PreviewPlayer({
|
|||||||
cameraPreviews={cameraPreviews}
|
cameraPreviews={cameraPreviews}
|
||||||
startTime={startTime}
|
startTime={startTime}
|
||||||
isScrubbing={isScrubbing}
|
isScrubbing={isScrubbing}
|
||||||
forceAspect={forceAspect}
|
|
||||||
currentHourFrame={currentHourFrame}
|
currentHourFrame={currentHourFrame}
|
||||||
onControllerReady={onControllerReady}
|
onControllerReady={onControllerReady}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
@ -92,7 +89,6 @@ type PreviewVideoPlayerProps = {
|
|||||||
cameraPreviews: Preview[];
|
cameraPreviews: Preview[];
|
||||||
startTime?: number;
|
startTime?: number;
|
||||||
isScrubbing: boolean;
|
isScrubbing: boolean;
|
||||||
forceAspect?: number;
|
|
||||||
currentHourFrame?: string;
|
currentHourFrame?: string;
|
||||||
onControllerReady: (controller: PreviewVideoController) => void;
|
onControllerReady: (controller: PreviewVideoController) => void;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
@ -105,7 +101,6 @@ function PreviewVideoPlayer({
|
|||||||
cameraPreviews,
|
cameraPreviews,
|
||||||
startTime,
|
startTime,
|
||||||
isScrubbing,
|
isScrubbing,
|
||||||
forceAspect,
|
|
||||||
currentHourFrame,
|
currentHourFrame,
|
||||||
onControllerReady,
|
onControllerReady,
|
||||||
onClick,
|
onClick,
|
||||||
@ -148,8 +143,6 @@ function PreviewVideoPlayer({
|
|||||||
|
|
||||||
// initial state
|
// initial state
|
||||||
|
|
||||||
const [loaded, setLoaded] = useState(false);
|
|
||||||
const [hasCanvas, setHasCanvas] = useState(false);
|
|
||||||
const initialPreview = useMemo(() => {
|
const initialPreview = useMemo(() => {
|
||||||
return cameraPreviews.find(
|
return cameraPreviews.find(
|
||||||
(preview) =>
|
(preview) =>
|
||||||
@ -191,7 +184,6 @@ function PreviewVideoPlayer({
|
|||||||
|
|
||||||
if (preview != currentPreview) {
|
if (preview != currentPreview) {
|
||||||
setCurrentPreview(preview);
|
setCurrentPreview(preview);
|
||||||
setLoaded(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
controller.newPlayback({
|
controller.newPlayback({
|
||||||
@ -215,21 +207,20 @@ function PreviewVideoPlayer({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canvasRef.current) {
|
if (!canvasRef.current && videoSize[0] > 0) {
|
||||||
const context = canvasRef.current.getContext("2d");
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = videoSize[0];
|
||||||
if (context) {
|
canvas.height = videoSize[1];
|
||||||
context.drawImage(previewRef.current, 0, 0, videoSize[0], videoSize[1]);
|
canvasRef.current = canvas;
|
||||||
}
|
|
||||||
|
|
||||||
setHasCanvas(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSafari) {
|
const context = canvasRef.current?.getContext("2d");
|
||||||
setTimeout(() => previewRef.current?.load(), 100);
|
|
||||||
} else {
|
if (context) {
|
||||||
previewRef.current.load();
|
context.drawImage(previewRef.current, 0, 0, videoSize[0], videoSize[1]);
|
||||||
|
setCurrentHourFrame(canvasRef.current?.toDataURL("image/webp"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// we only want this to change when current preview changes
|
// we only want this to change when current preview changes
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [currentPreview, previewRef]);
|
}, [currentPreview, previewRef]);
|
||||||
@ -237,26 +228,16 @@ function PreviewVideoPlayer({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`relative rounded-2xl bg-black overflow-hidden ${onClick ? "cursor-pointer" : ""} ${className ?? ""}`}
|
className={`relative rounded-2xl bg-black overflow-hidden ${onClick ? "cursor-pointer" : ""} ${className ?? ""}`}
|
||||||
style={{
|
|
||||||
aspectRatio: forceAspect,
|
|
||||||
}}
|
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
{currentHourFrame && (
|
<img
|
||||||
<img
|
className={`absolute size-full object-contain ${currentHourFrame ? "visible" : "invisible"}`}
|
||||||
className="absolute size-full object-contain"
|
src={currentHourFrame}
|
||||||
src={currentHourFrame}
|
onLoad={() => previewRef.current?.load()}
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<canvas
|
|
||||||
ref={canvasRef}
|
|
||||||
width={videoSize[0]}
|
|
||||||
height={videoSize[1]}
|
|
||||||
className={`h-full absolute left-1/2 -translate-x-1/2 ${!loaded && hasCanvas ? "" : "hidden"}`}
|
|
||||||
/>
|
/>
|
||||||
<video
|
<video
|
||||||
ref={previewRef}
|
ref={previewRef}
|
||||||
className="size-full"
|
className="absolute size-full"
|
||||||
preload="auto"
|
preload="auto"
|
||||||
autoPlay
|
autoPlay
|
||||||
playsInline
|
playsInline
|
||||||
@ -265,7 +246,6 @@ function PreviewVideoPlayer({
|
|||||||
onSeeked={onPreviewSeeked}
|
onSeeked={onPreviewSeeked}
|
||||||
onLoadedData={() => {
|
onLoadedData={() => {
|
||||||
setCurrentHourFrame(undefined);
|
setCurrentHourFrame(undefined);
|
||||||
setLoaded(true);
|
|
||||||
|
|
||||||
if (controller) {
|
if (controller) {
|
||||||
controller.previewReady();
|
controller.previewReady();
|
||||||
@ -289,9 +269,6 @@ function PreviewVideoPlayer({
|
|||||||
<source src={currentPreview.src} type={currentPreview.type} />
|
<source src={currentPreview.src} type={currentPreview.type} />
|
||||||
)}
|
)}
|
||||||
</video>
|
</video>
|
||||||
{!loaded && !hasCanvas && !currentHourFrame && (
|
|
||||||
<Skeleton className="absolute inset-0" />
|
|
||||||
)}
|
|
||||||
{cameraPreviews && !currentPreview && (
|
{cameraPreviews && !currentPreview && (
|
||||||
<div className="absolute inset-0 text-white rounded-2xl flex justify-center items-center">
|
<div className="absolute inset-0 text-white rounded-2xl flex justify-center items-center">
|
||||||
No Preview Found
|
No Preview Found
|
||||||
@ -479,7 +456,7 @@ function PreviewFramesPlayer({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`relative w-full ${className ?? ""} ${onClick ? "cursor-pointer" : ""}`}
|
className={`relative ${className ?? ""} ${onClick ? "cursor-pointer" : ""}`}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import PreviewPlayer, { PreviewController } from "../PreviewPlayer";
|
|||||||
import { DynamicVideoController } from "./DynamicVideoController";
|
import { DynamicVideoController } from "./DynamicVideoController";
|
||||||
import HlsVideoPlayer from "../HlsVideoPlayer";
|
import HlsVideoPlayer from "../HlsVideoPlayer";
|
||||||
import { TimeRange, Timeline } from "@/types/timeline";
|
import { TimeRange, Timeline } from "@/types/timeline";
|
||||||
import { isDesktop } from "react-device-detect";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dynamically switches between video playback and scrubbing preview player.
|
* Dynamically switches between video playback and scrubbing preview player.
|
||||||
@ -151,7 +150,7 @@ export default function DynamicVideoPlayer({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HlsVideoPlayer
|
<HlsVideoPlayer
|
||||||
className={isDesktop ? `w-full ${className}` : "max-h-[50dvh]"}
|
className={className ?? ""}
|
||||||
videoRef={playerRef}
|
videoRef={playerRef}
|
||||||
visible={!(isScrubbing || isLoading)}
|
visible={!(isScrubbing || isLoading)}
|
||||||
currentSource={source}
|
currentSource={source}
|
||||||
@ -175,7 +174,7 @@ export default function DynamicVideoPlayer({
|
|||||||
)}
|
)}
|
||||||
</HlsVideoPlayer>
|
</HlsVideoPlayer>
|
||||||
<PreviewPlayer
|
<PreviewPlayer
|
||||||
className={`${isScrubbing || isLoading ? "visible" : "hidden"} ${isDesktop ? `w-full ${className}` : "max-h-[50dvh]"}`}
|
className={`${isScrubbing || isLoading ? "visible" : "hidden"} ${className}`}
|
||||||
camera={camera}
|
camera={camera}
|
||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
cameraPreviews={cameraPreviews}
|
cameraPreviews={cameraPreviews}
|
||||||
|
|||||||
@ -127,8 +127,8 @@ export function getTimelineItemDescription(timelineItem: Timeline) {
|
|||||||
* @returns timeRange chunked into individual hours
|
* @returns timeRange chunked into individual hours
|
||||||
*/
|
*/
|
||||||
export function getChunkedTimeDay(timeRange: TimeRange): TimeRange[] {
|
export function getChunkedTimeDay(timeRange: TimeRange): TimeRange[] {
|
||||||
const endOfThisHour = new Date();
|
const endOfThisHour = new Date(timeRange.before * 1000);
|
||||||
endOfThisHour.setHours(endOfThisHour.getHours() + 1, 0, 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);
|
startDay.setMinutes(0, 0, 0);
|
||||||
@ -136,7 +136,7 @@ export function getChunkedTimeDay(timeRange: TimeRange): TimeRange[] {
|
|||||||
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);
|
startDay.setHours(startDay.getHours() + 1, 0, 0, 0);
|
||||||
|
|
||||||
if (startDay > endOfThisHour) {
|
if (startDay > endOfThisHour) {
|
||||||
break;
|
break;
|
||||||
@ -150,6 +150,11 @@ export function getChunkedTimeDay(timeRange: TimeRange): TimeRange[] {
|
|||||||
start = startDay.getTime() / 1000;
|
start = startDay.getTime() / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data.push({
|
||||||
|
after: start,
|
||||||
|
before: Math.floor(timeRange.before),
|
||||||
|
});
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -226,14 +226,14 @@ export function RecordingView({
|
|||||||
}, [getCameraAspect, mainCamera]);
|
}, [getCameraAspect, mainCamera]);
|
||||||
|
|
||||||
const grow = useMemo(() => {
|
const grow = useMemo(() => {
|
||||||
if (isMobile) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mainCameraAspect == "wide") {
|
if (mainCameraAspect == "wide") {
|
||||||
return "w-full aspect-wide";
|
return "w-full aspect-wide";
|
||||||
} else if (isDesktop && mainCameraAspect == "tall") {
|
} else if (mainCameraAspect == "tall") {
|
||||||
return "h-full aspect-tall flex flex-col justify-center";
|
if (isDesktop) {
|
||||||
|
return "h-full aspect-tall flex flex-col justify-center";
|
||||||
|
} else {
|
||||||
|
return "size-full";
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return "w-full aspect-video";
|
return "w-full aspect-video";
|
||||||
}
|
}
|
||||||
@ -352,10 +352,11 @@ export function RecordingView({
|
|||||||
: `w-full ${mainCameraAspect == "wide" ? "aspect-wide" : "aspect-video"}`
|
: `w-full ${mainCameraAspect == "wide" ? "aspect-wide" : "aspect-video"}`
|
||||||
}
|
}
|
||||||
style={{
|
style={{
|
||||||
aspectRatio:
|
aspectRatio: isDesktop
|
||||||
mainCameraAspect == "tall"
|
? mainCameraAspect == "tall"
|
||||||
? getCameraAspect(mainCamera)
|
? getCameraAspect(mainCamera)
|
||||||
: undefined,
|
: undefined
|
||||||
|
: Math.max(1, getCameraAspect(mainCamera) ?? 0),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DynamicVideoPlayer
|
<DynamicVideoPlayer
|
||||||
@ -381,35 +382,38 @@ export function RecordingView({
|
|||||||
</div>
|
</div>
|
||||||
{isDesktop && (
|
{isDesktop && (
|
||||||
<div
|
<div
|
||||||
className={`flex gap-2 ${mainCameraAspect == "tall" ? "h-full w-[16%] flex-col overflow-y-auto" : "w-full justify-center overflow-x-auto"}`}
|
className={`flex gap-2 ${mainCameraAspect == "tall" ? "h-full w-[12%] flex-col justify-center overflow-y-auto" : "w-full h-[14%] justify-center items-center overflow-x-auto"} `}
|
||||||
>
|
>
|
||||||
{allCameras.map((cam) => {
|
{allCameras.map((cam) => {
|
||||||
if (cam !== mainCamera) {
|
if (cam == mainCamera) {
|
||||||
const preview = (
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={cam}
|
||||||
|
className={
|
||||||
|
mainCameraAspect == "tall" ? undefined : "h-full"
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
aspectRatio: getCameraAspect(cam),
|
||||||
|
}}
|
||||||
|
>
|
||||||
<PreviewPlayer
|
<PreviewPlayer
|
||||||
key={cam}
|
className="size-full"
|
||||||
className={`${mainCameraAspect == "wide" ? "flex-grow" : ""}`}
|
|
||||||
camera={cam}
|
camera={cam}
|
||||||
timeRange={currentTimeRange}
|
timeRange={currentTimeRange}
|
||||||
cameraPreviews={allPreviews ?? []}
|
cameraPreviews={allPreviews ?? []}
|
||||||
startTime={startTime}
|
startTime={startTime}
|
||||||
isScrubbing={scrubbing}
|
isScrubbing={scrubbing}
|
||||||
forceAspect={getCameraAspect(cam)}
|
|
||||||
onControllerReady={(controller) => {
|
onControllerReady={(controller) => {
|
||||||
previewRefs.current[cam] = controller;
|
previewRefs.current[cam] = controller;
|
||||||
controller.scrubToTimestamp(startTime);
|
controller.scrubToTimestamp(startTime);
|
||||||
}}
|
}}
|
||||||
onClick={() => onSelectCamera(cam)}
|
onClick={() => onSelectCamera(cam)}
|
||||||
/>
|
/>
|
||||||
);
|
</div>
|
||||||
|
);
|
||||||
if (mainCameraAspect == "tall") {
|
|
||||||
return <div key={`${cam}-t`}>{preview}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return preview;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user