Handle autoplay for mobile

This commit is contained in:
Nick Mowen 2023-12-13 14:09:07 -07:00
parent 08e9120db5
commit 7e13501787
4 changed files with 46 additions and 8 deletions

View File

@ -22,11 +22,13 @@ import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
type HistoryCardProps = { type HistoryCardProps = {
timeline: Card; timeline: Card;
relevantPreview?: Preview; relevantPreview?: Preview;
shouldAutoPlay: boolean;
}; };
export default function HistoryCard({ export default function HistoryCard({
relevantPreview, relevantPreview,
timeline, timeline,
shouldAutoPlay,
}: HistoryCardProps) { }: HistoryCardProps) {
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
@ -41,6 +43,7 @@ export default function HistoryCard({
relevantPreview={relevantPreview} relevantPreview={relevantPreview}
startTs={Object.values(timeline.entries)[0].timestamp} startTs={Object.values(timeline.entries)[0].timestamp}
eventId={Object.values(timeline.entries)[0].source_id} eventId={Object.values(timeline.entries)[0].source_id}
shouldAutoPlay={shouldAutoPlay}
/> />
<div className="p-2"> <div className="p-2">
<div className="text-sm flex"> <div className="text-sm flex">

View File

@ -11,6 +11,7 @@ type PreviewPlayerProps = {
relevantPreview?: Preview; relevantPreview?: Preview;
startTs: number; startTs: number;
eventId: string; eventId: string;
shouldAutoPlay: boolean;
}; };
type Preview = { type Preview = {
@ -26,12 +27,13 @@ export default function PreviewThumbnailPlayer({
relevantPreview, relevantPreview,
startTs, startTs,
eventId, eventId,
shouldAutoPlay,
}: PreviewPlayerProps) { }: PreviewPlayerProps) {
const { data: config } = useSWR("config"); const { data: config } = useSWR("config");
const playerRef = useRef<Player | null>(null); const playerRef = useRef<Player | null>(null);
const apiHost = useApiHost(); const apiHost = useApiHost();
const onHover = useCallback( const onPlayback = useCallback(
(isHovered: Boolean) => { (isHovered: Boolean) => {
if (!relevantPreview || !playerRef.current) { if (!relevantPreview || !playerRef.current) {
return; return;
@ -47,6 +49,32 @@ export default function PreviewThumbnailPlayer({
[relevantPreview, startTs] [relevantPreview, startTs]
); );
const observer = useRef<IntersectionObserver | null>();
const inViewRef = useCallback(
(node: HTMLElement | null) => {
if (!shouldAutoPlay || observer.current) {
return;
}
try {
observer.current = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
onPlayback(true);
} else {
onPlayback(false);
}
},
{ threshold: 1.0 }
);
if (node) observer.current.observe(node);
} catch (e) {
// no op
}
},
[observer, onPlayback]
);
if (!relevantPreview) { if (!relevantPreview) {
if (isCurrentHour(startTs)) { if (isCurrentHour(startTs)) {
return ( return (
@ -56,6 +84,7 @@ export default function PreviewThumbnailPlayer({
> >
<img <img
className={`${getPreviewWidth(camera, config)}`} className={`${getPreviewWidth(camera, config)}`}
loading="lazy"
src={`${apiHost}api/preview/${camera}/${startTs}/thumbnail.jpg`} src={`${apiHost}api/preview/${camera}/${startTs}/thumbnail.jpg`}
/> />
</AspectRatio> </AspectRatio>
@ -69,6 +98,7 @@ export default function PreviewThumbnailPlayer({
> >
<img <img
className="w-[160px]" className="w-[160px]"
loading="lazy"
src={`${apiHost}api/events/${eventId}/thumbnail.jpg`} src={`${apiHost}api/events/${eventId}/thumbnail.jpg`}
/> />
</AspectRatio> </AspectRatio>
@ -77,10 +107,11 @@ export default function PreviewThumbnailPlayer({
return ( return (
<AspectRatio <AspectRatio
ref={shouldAutoPlay ? inViewRef : null}
ratio={16 / 9} ratio={16 / 9}
className="bg-black flex justify-center items-center" className="bg-black flex justify-center items-center"
onMouseEnter={() => onHover(true)} onMouseEnter={() => onPlayback(true)}
onMouseLeave={() => onHover(false)} onMouseLeave={() => onPlayback(false)}
> >
<div className={`${getPreviewWidth(camera, config)}`}> <div className={`${getPreviewWidth(camera, config)}`}>
<VideoPlayer <VideoPlayer

View File

@ -44,7 +44,7 @@ export default function VideoPlayer({ children, options, seekOptions = {forward:
videoElement.classList.add('small-player'); videoElement.classList.add('small-player');
videoElement.classList.add('video-js'); videoElement.classList.add('video-js');
videoElement.classList.add('vjs-default-skin'); videoElement.classList.add('vjs-default-skin');
videoRef.current.appendChild(videoElement); videoRef.current?.appendChild(videoElement);
const player = playerRef.current = videojs(videoElement, { ...defaultOptions, ...options }, () => { const player = playerRef.current = videojs(videoElement, { ...defaultOptions, ...options }, () => {
onReady && onReady(player); onReady && onReady(player);

View File

@ -5,11 +5,10 @@ import { FrigateConfig } from "@/types/frigateConfig";
import Heading from "@/components/ui/heading"; import Heading from "@/components/ui/heading";
import ActivityIndicator from "@/components/ui/activity-indicator"; import ActivityIndicator from "@/components/ui/activity-indicator";
import HistoryCard from "@/components/card/HistoryCard"; import HistoryCard from "@/components/card/HistoryCard";
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil"; import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
import axios from "axios"; import axios from "axios";
const API_LIMIT = 200; const API_LIMIT = 100;
function History() { function History() {
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
@ -26,11 +25,15 @@ function History() {
const getKey = useCallback((index: number, prevData: HourlyTimeline) => { const getKey = useCallback((index: number, prevData: HourlyTimeline) => {
if (index > 0) { if (index > 0) {
const lastDate = prevData.end; const lastDate = prevData.end;
const pagedParams = { before: lastDate, timezone }; const pagedParams = { before: lastDate, timezone, limit: API_LIMIT };
return ["timeline/hourly", pagedParams]; return ["timeline/hourly", pagedParams];
} }
return ["timeline/hourly", { timezone }]; return ["timeline/hourly", { timezone, limit: API_LIMIT }];
}, []);
const shouldAutoPlay = useMemo(() => {
return window.innerWidth < 480;
}, []); }, []);
const { const {
@ -207,6 +210,7 @@ function History() {
<HistoryCard <HistoryCard
key={key} key={key}
timeline={timeline} timeline={timeline}
shouldAutoPlay={shouldAutoPlay}
relevantPreview={Object.values( relevantPreview={Object.values(
allPreviews || [] allPreviews || []
).find( ).find(