Use poster image for preview on video player instead of using separate image view

This commit is contained in:
Nick Mowen 2024-01-14 19:59:08 -07:00
parent d09e142b22
commit 2ac3173b4a
4 changed files with 83 additions and 73 deletions

View File

@ -6,6 +6,7 @@ import useSWR from "swr";
import { FrigateConfig } from "@/types/frigateConfig";
import VideoPlayer from "../player/VideoPlayer";
import { Card } from "../ui/card";
import { useApiHost } from "@/api";
type TimelineItemCardProps = {
timeline: Timeline;
@ -18,35 +19,44 @@ export default function TimelineItemCard({
onSelect,
}: TimelineItemCardProps) {
const { data: config } = useSWR<FrigateConfig>("config");
const apiHost = useApiHost();
return (
<Card className="relative m-2 flex w-full h-32 cursor-pointer" onClick={onSelect}>
<Card
className="relative m-2 flex w-full h-32 cursor-pointer"
onClick={onSelect}
>
<div className="p-2">
{relevantPreview && (
<VideoPlayer
options={{
preload: "auto",
height: "114",
width: "202",
autoplay: true,
controls: false,
fluid: false,
muted: true,
loadingSpinner: false,
sources: [
{
src: `${relevantPreview.src}`,
type: "video/mp4",
},
],
}}
seekOptions={{}}
onReady={(player) => {
<VideoPlayer
options={{
preload: "auto",
height: "114",
width: "202",
autoplay: true,
controls: false,
fluid: false,
muted: true,
loadingSpinner: false,
poster: relevantPreview
? ""
: `${apiHost}api/preview/${timeline.camera}/${timeline.timestamp}/thumbnail.jpg`,
sources: relevantPreview
? [
{
src: `${relevantPreview.src}`,
type: "video/mp4",
},
]
: [],
}}
seekOptions={{}}
onReady={(player) => {
if (relevantPreview) {
player.pause(); // autoplay + pause is required for iOS
player.currentTime(timeline.timestamp - relevantPreview.start);
}}
/>
)}
}
}}
/>
</div>
<div className="py-1">
<div className="capitalize font-semibold text-sm">

View File

@ -14,6 +14,10 @@ export default function TimelineEventOverlay({
timeline,
cameraConfig,
}: TimelineEventOverlayProps) {
if (!timeline.data.box) {
return null;
}
const boxLeftEdge = Math.round(timeline.data.box[0] * 100);
const boxTopEdge = Math.round(timeline.data.box[1] * 100);
const boxRightEdge = Math.round(
@ -25,6 +29,10 @@ export default function TimelineEventOverlay({
const [isHovering, setIsHovering] = useState<boolean>(false);
const getHoverStyle = () => {
if (!timeline.data.box) {
return {};
}
if (boxLeftEdge < 15) {
// show object stats on right side
return {
@ -40,12 +48,20 @@ export default function TimelineEventOverlay({
};
const getObjectArea = () => {
if (!timeline.data.box) {
return 0;
}
const width = timeline.data.box[2] * cameraConfig.detect.width;
const height = timeline.data.box[3] * cameraConfig.detect.height;
return Math.round(width * height);
};
const getObjectRatio = () => {
if (!timeline.data.box) {
return 0.0;
}
const width = timeline.data.box[2] * cameraConfig.detect.width;
const height = timeline.data.box[3] * cameraConfig.detect.height;
return Math.round(100 * (width / height)) / 100;

View File

@ -1,6 +1,4 @@
import { FrigateConfig } from "@/types/frigateConfig";
import VideoPlayer from "./VideoPlayer";
import useSWR from "swr";
import React, {
useCallback,
useEffect,
@ -12,6 +10,7 @@ import { useApiHost } from "@/api";
import Player from "video.js/dist/types/player";
import { AspectRatio } from "../ui/aspect-ratio";
import { LuPlayCircle } from "react-icons/lu";
import { isCurrentHour } from "@/utils/dateUtil";
type PreviewPlayerProps = {
camera: string;
@ -38,7 +37,6 @@ export default function PreviewThumbnailPlayer({
isMobile,
onClick,
}: PreviewPlayerProps) {
const { data: config } = useSWR("config");
const playerRef = useRef<Player | null>(null);
const isSafari = useMemo(() => {
return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
@ -105,6 +103,7 @@ export default function PreviewThumbnailPlayer({
{
threshold: 1.0,
root: document.getElementById("pageRoot"),
rootMargin: "-15% 0px -15% 0px",
}
);
if (node) autoPlayObserver.current.observe(node);
@ -131,7 +130,6 @@ export default function PreviewThumbnailPlayer({
isInitiallyVisible={isInitiallyVisible}
startTs={startTs}
camera={camera}
config={config}
eventId={eventId}
isMobile={isMobile}
isSafari={isSafari}
@ -143,7 +141,6 @@ export default function PreviewThumbnailPlayer({
type PreviewContentProps = {
playerRef: React.MutableRefObject<Player | null>;
config: FrigateConfig;
camera: string;
relevantPreview: Preview | undefined;
eventId: string;
@ -156,7 +153,6 @@ type PreviewContentProps = {
};
function PreviewContent({
playerRef,
config,
camera,
relevantPreview,
eventId,
@ -195,22 +191,13 @@ function PreviewContent({
if (relevantPreview && !isVisible) {
return <div />;
} else if (!relevantPreview) {
if (isCurrentHour(startTs)) {
return (
<img
className={`${getPreviewWidth(camera, config)}`}
src={`${apiHost}api/preview/${camera}/${startTs}/thumbnail.jpg`}
/>
);
} else {
return (
<img
className="w-[160px]"
src={`${apiHost}api/events/${eventId}/thumbnail.jpg`}
/>
);
}
} else if (!relevantPreview && !isCurrentHour(startTs)) {
return (
<img
className="w-[160px]"
src={`${apiHost}api/events/${eventId}/thumbnail.jpg`}
/>
);
} else {
return (
<>
@ -223,17 +210,26 @@ function PreviewContent({
controls: false,
muted: true,
loadingSpinner: false,
sources: [
{
src: `${relevantPreview.src}`,
type: "video/mp4",
},
],
poster: relevantPreview
? ""
: `${apiHost}api/preview/${camera}/${startTs}/thumbnail.jpg`,
sources: relevantPreview
? [
{
src: `${relevantPreview.src}`,
type: "video/mp4",
},
]
: [],
}}
seekOptions={{}}
onReady={(player) => {
playerRef.current = player;
if (!relevantPreview) {
return;
}
if (!isInitiallyVisible) {
player.pause(); // autoplay + pause is required for iOS
}
@ -249,28 +245,10 @@ function PreviewContent({
}}
/>
</div>
<LuPlayCircle className="absolute z-10 left-1 bottom-1 w-4 h-4 text-white text-opacity-60" />
{relevantPreview && (
<LuPlayCircle className="absolute z-10 left-1 bottom-1 w-4 h-4 text-white text-opacity-60" />
)}
</>
);
}
}
function isCurrentHour(timestamp: number) {
const now = new Date();
now.setMinutes(0, 0, 0);
return timestamp > now.getTime() / 1000;
}
function getPreviewWidth(camera: string, config: FrigateConfig) {
const detect = config.cameras[camera].detect;
if (detect.width / detect.height < 1) {
return "w-1/2";
}
if (detect.width / detect.height < 16 / 9) {
return "w-2/3";
}
return "w-full";
}

View File

@ -285,3 +285,9 @@ export function getRangeForTimestamp(timestamp: number) {
const end = date.getTime() / 1000;
return { start, end };
}
export function isCurrentHour(timestamp: number) {
const now = new Date();
now.setMinutes(0, 0, 0);
return timestamp > now.getTime() / 1000;
}