mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-08 20:25:26 +03:00
Cleanup preview thumb player
This commit is contained in:
parent
34dde65fc3
commit
b960b7c41b
@ -2,7 +2,7 @@ import VideoPlayer from "./VideoPlayer";
|
|||||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { useApiHost } from "@/api";
|
import { useApiHost } from "@/api";
|
||||||
import Player from "video.js/dist/types/player";
|
import Player from "video.js/dist/types/player";
|
||||||
import { formatUnixTimestampToDateTime, isCurrentHour } from "@/utils/dateUtil";
|
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
|
||||||
import { isSafari } from "@/utils/browserUtil";
|
import { isSafari } from "@/utils/browserUtil";
|
||||||
import { ReviewSegment } from "@/types/review";
|
import { ReviewSegment } from "@/types/review";
|
||||||
import { Slider } from "../ui/slider";
|
import { Slider } from "../ui/slider";
|
||||||
@ -34,12 +34,12 @@ export default function PreviewThumbnailPlayer({
|
|||||||
setReviewed,
|
setReviewed,
|
||||||
onClick,
|
onClick,
|
||||||
}: PreviewPlayerProps) {
|
}: PreviewPlayerProps) {
|
||||||
|
const apiHost = useApiHost();
|
||||||
const { data: config } = useSWR<FrigateConfig>("config");
|
const { data: config } = useSWR<FrigateConfig>("config");
|
||||||
const playerRef = useRef<Player | null>(null);
|
const playerRef = useRef<Player | null>(null);
|
||||||
|
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [hover, setHover] = useState(false);
|
const [hover, setHover] = useState(false);
|
||||||
const [isInitiallyVisible, setIsInitiallyVisible] = useState(false);
|
|
||||||
const [progress, setProgress] = useState(0);
|
const [progress, setProgress] = useState(0);
|
||||||
|
|
||||||
const onPlayback = useCallback(
|
const onPlayback = useCallback(
|
||||||
@ -48,25 +48,19 @@ export default function PreviewThumbnailPlayer({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!playerRef.current) {
|
|
||||||
if (isHovered) {
|
|
||||||
setIsInitiallyVisible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isHovered) {
|
if (isHovered) {
|
||||||
setHover(true);
|
setHover(true);
|
||||||
playerRef.current.play();
|
|
||||||
} else {
|
} else {
|
||||||
setHover(false);
|
setHover(false);
|
||||||
setProgress(0);
|
setProgress(0);
|
||||||
|
|
||||||
|
if (playerRef.current) {
|
||||||
playerRef.current.pause();
|
playerRef.current.pause();
|
||||||
playerRef.current.currentTime(
|
playerRef.current.currentTime(
|
||||||
review.start_time - relevantPreview.start
|
review.start_time - relevantPreview.start
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[relevantPreview, review, playerRef]
|
[relevantPreview, review, playerRef]
|
||||||
);
|
);
|
||||||
@ -127,17 +121,23 @@ export default function PreviewThumbnailPlayer({
|
|||||||
onMouseEnter={() => onPlayback(true)}
|
onMouseEnter={() => onPlayback(true)}
|
||||||
onMouseLeave={() => onPlayback(false)}
|
onMouseLeave={() => onPlayback(false)}
|
||||||
>
|
>
|
||||||
|
{hover ? (
|
||||||
<PreviewContent
|
<PreviewContent
|
||||||
playerRef={playerRef}
|
playerRef={playerRef}
|
||||||
review={review}
|
review={review}
|
||||||
relevantPreview={relevantPreview}
|
relevantPreview={relevantPreview}
|
||||||
isVisible={visible}
|
isVisible={visible}
|
||||||
isInitiallyVisible={isInitiallyVisible}
|
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
setProgress={setProgress}
|
setProgress={setProgress}
|
||||||
setReviewed={setReviewed}
|
setReviewed={setReviewed}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<img
|
||||||
|
className="h-full w-full"
|
||||||
|
src={`${apiHost}${review.thumb_path.replace("/media/frigate/", "")}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{!hover &&
|
{!hover &&
|
||||||
(review.severity == "alert" || review.severity == "detection") && (
|
(review.severity == "alert" || review.severity == "detection") && (
|
||||||
<div className="absolute top-1 left-[6px] flex gap-1">
|
<div className="absolute top-1 left-[6px] flex gap-1">
|
||||||
@ -184,7 +184,6 @@ type PreviewContentProps = {
|
|||||||
review: ReviewSegment;
|
review: ReviewSegment;
|
||||||
relevantPreview: Preview | undefined;
|
relevantPreview: Preview | undefined;
|
||||||
isVisible: boolean;
|
isVisible: boolean;
|
||||||
isInitiallyVisible: boolean;
|
|
||||||
isMobile: boolean;
|
isMobile: boolean;
|
||||||
setProgress?: (progress: number) => void;
|
setProgress?: (progress: number) => void;
|
||||||
setReviewed?: () => void;
|
setReviewed?: () => void;
|
||||||
@ -195,13 +194,11 @@ function PreviewContent({
|
|||||||
review,
|
review,
|
||||||
relevantPreview,
|
relevantPreview,
|
||||||
isVisible,
|
isVisible,
|
||||||
isInitiallyVisible,
|
|
||||||
isMobile,
|
isMobile,
|
||||||
setProgress,
|
setProgress,
|
||||||
setReviewed,
|
setReviewed,
|
||||||
onClick,
|
onClick,
|
||||||
}: PreviewContentProps) {
|
}: PreviewContentProps) {
|
||||||
const apiHost = useApiHost();
|
|
||||||
const slowPlayack = isSafari();
|
const slowPlayack = isSafari();
|
||||||
|
|
||||||
// handle touchstart -> touchend as click
|
// handle touchstart -> touchend as click
|
||||||
@ -228,23 +225,7 @@ function PreviewContent({
|
|||||||
});
|
});
|
||||||
}, [playerRef, touchStart]);
|
}, [playerRef, touchStart]);
|
||||||
|
|
||||||
if (relevantPreview && !isVisible) {
|
if (relevantPreview && isVisible) {
|
||||||
return <div />;
|
|
||||||
} else if (!relevantPreview && isCurrentHour(review.start_time)) {
|
|
||||||
return (
|
|
||||||
<img
|
|
||||||
className="w-full"
|
|
||||||
src={`${apiHost}api/preview/${review.camera}/${review.start_time}/thumbnail.jpg`}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else if (!relevantPreview && !isCurrentHour(review.start_time)) {
|
|
||||||
return (
|
|
||||||
<img
|
|
||||||
className="w-[160px]"
|
|
||||||
src={`${apiHost}api/events/${review.id}/thumbnail.jpg`}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
return (
|
||||||
<VideoPlayer
|
<VideoPlayer
|
||||||
options={{
|
options={{
|
||||||
@ -253,6 +234,7 @@ function PreviewContent({
|
|||||||
controls: false,
|
controls: false,
|
||||||
muted: true,
|
muted: true,
|
||||||
fluid: true,
|
fluid: true,
|
||||||
|
aspectRatio: '16:9',
|
||||||
loadingSpinner: false,
|
loadingSpinner: false,
|
||||||
sources: relevantPreview
|
sources: relevantPreview
|
||||||
? [
|
? [
|
||||||
@ -273,10 +255,6 @@ function PreviewContent({
|
|||||||
|
|
||||||
const playerStartTime = review.start_time - relevantPreview.start;
|
const playerStartTime = review.start_time - relevantPreview.start;
|
||||||
|
|
||||||
if (!isInitiallyVisible) {
|
|
||||||
player.pause(); // autoplay + pause is required for iOS
|
|
||||||
}
|
|
||||||
|
|
||||||
player.playbackRate(slowPlayack ? 2 : 8);
|
player.playbackRate(slowPlayack ? 2 : 8);
|
||||||
player.currentTime(playerStartTime);
|
player.currentTime(playerStartTime);
|
||||||
player.on("timeupdate", () => {
|
player.on("timeupdate", () => {
|
||||||
|
|||||||
@ -211,7 +211,6 @@ export default function Events() {
|
|||||||
<div className="flex flex-wrap gap-2 mt-2">
|
<div className="flex flex-wrap gap-2 mt-2">
|
||||||
{reviewItems[severity]?.map((value, segIdx) => {
|
{reviewItems[severity]?.map((value, segIdx) => {
|
||||||
const lastRow = segIdx == reviewItems[severity].length - 1;
|
const lastRow = segIdx == reviewItems[severity].length - 1;
|
||||||
const detectConfig = config.cameras[value.camera].detect;
|
|
||||||
const relevantPreview = Object.values(allPreviews || []).find(
|
const relevantPreview = Object.values(allPreviews || []).find(
|
||||||
(preview) =>
|
(preview) =>
|
||||||
preview.camera == value.camera &&
|
preview.camera == value.camera &&
|
||||||
@ -223,10 +222,7 @@ export default function Events() {
|
|||||||
<div key={value.id}>
|
<div key={value.id}>
|
||||||
<div
|
<div
|
||||||
ref={lastRow ? lastReviewRef : null}
|
ref={lastRow ? lastReviewRef : null}
|
||||||
className="relative h-[234px] rounded-lg overflow-hidden"
|
className="relative h-[234px] aspect-video rounded-lg overflow-hidden"
|
||||||
style={{
|
|
||||||
aspectRatio: detectConfig.width / detectConfig.height,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<PreviewThumbnailPlayer
|
<PreviewThumbnailPlayer
|
||||||
review={value}
|
review={value}
|
||||||
|
|||||||
@ -1,21 +1,19 @@
|
|||||||
import {
|
import { BsPersonWalking } from "react-icons/bs";
|
||||||
LuBox,
|
import { FaCarSide, FaCat, FaDog } from "react-icons/fa";
|
||||||
LuCar,
|
import { LuBox, LuLassoSelect } from "react-icons/lu";
|
||||||
LuDog,
|
|
||||||
LuLassoSelect,
|
|
||||||
LuPersonStanding,
|
|
||||||
} from "react-icons/lu";
|
|
||||||
|
|
||||||
export function getIconForLabel(label: string, className?: string) {
|
export function getIconForLabel(label: string, className?: string) {
|
||||||
switch (label) {
|
switch (label) {
|
||||||
case "car":
|
case "car":
|
||||||
return <LuCar key={label} className={className} />;
|
return <FaCarSide key={label} className={className} />;
|
||||||
|
case "cat":
|
||||||
|
return <FaCat key={label} className={className} />;
|
||||||
case "dog":
|
case "dog":
|
||||||
return <LuDog key={label} className={className} />;
|
return <FaDog key={label} className={className} />;
|
||||||
case "package":
|
case "package":
|
||||||
return <LuBox key={label} className={className} />;
|
return <LuBox key={label} className={className} />;
|
||||||
case "person":
|
case "person":
|
||||||
return <LuPersonStanding key={label} className={className} />;
|
return <BsPersonWalking key={label} className={className} />;
|
||||||
default:
|
default:
|
||||||
return <LuLassoSelect key={label} className={className} />;
|
return <LuLassoSelect key={label} className={className} />;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user