Cleanup preview thumb player

This commit is contained in:
Nicolas Mowen 2024-02-20 08:22:03 -07:00
parent 34dde65fc3
commit b960b7c41b
3 changed files with 37 additions and 65 deletions

View File

@ -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,24 +48,18 @@ 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);
playerRef.current.pause();
playerRef.current.currentTime( if (playerRef.current) {
review.start_time - relevantPreview.start playerRef.current.pause();
); playerRef.current.currentTime(
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)}
> >
<PreviewContent {hover ? (
playerRef={playerRef} <PreviewContent
review={review} playerRef={playerRef}
relevantPreview={relevantPreview} review={review}
isVisible={visible} relevantPreview={relevantPreview}
isInitiallyVisible={isInitiallyVisible} isVisible={visible}
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", () => {

View File

@ -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}

View File

@ -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} />;
} }