Use webrtc for safari live view (#9839)

* Move safari function

* Use webrtc for safari

* Use taller aspect ratio for tall thumbnails

* Simplify thumbnail sizing

* Use correct aspect ratio on all devices
This commit is contained in:
Nicolas Mowen 2024-02-14 17:19:55 -07:00 committed by GitHub
parent 764736b223
commit 8c4811ed69
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 38 additions and 33 deletions

View File

@ -22,30 +22,23 @@ export function AnimatedEventThumbnail({ event }: AnimatedEventThumbnailProps) {
return `${baseUrl}api/events/${event.id}/preview.gif`; return `${baseUrl}api/events/${event.id}/preview.gif`;
}, [event]); }, [event]);
const aspect = useMemo(() => { const aspectRatio = useMemo(() => {
if (!config) { if (!config) {
return ""; return 1;
} }
const detect = config.cameras[event.camera].detect; const detect = config.cameras[event.camera].detect;
const aspect = detect.width / detect.height; return detect.width / detect.height;
}, [config]);
if (aspect > 2) {
return "aspect-wide";
} else if (aspect < 1) {
return "aspect-tall";
} else {
return "aspect-video";
}
}, [config, event]);
return ( return (
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div <div
className={`relative rounded bg-cover h-24 bg-no-repeat bg-center mr-4 ${aspect}`} className="h-24 relative rounded bg-cover bg-no-repeat bg-center mr-4"
style={{ style={{
backgroundImage: `url(${imageUrl})`, backgroundImage: `url(${imageUrl})`,
aspectRatio: aspectRatio,
}} }}
> >
<div className="absolute bottom-0 w-full h-6 bg-gradient-to-t from-slate-900/50 to-transparent rounded"> <div className="absolute bottom-0 w-full h-6 bg-gradient-to-t from-slate-900/50 to-transparent rounded">

View File

@ -87,6 +87,7 @@ export default function LivePlayer({
<WebRtcPlayer <WebRtcPlayer
className={`rounded-2xl h-full ${liveReady ? "" : "hidden"}`} className={`rounded-2xl h-full ${liveReady ? "" : "hidden"}`}
camera={cameraConfig.live.stream_name} camera={cameraConfig.live.stream_name}
playbackEnabled={cameraActive}
onPlaying={() => setLiveReady(true)} onPlaying={() => setLiveReady(true)}
/> />
); );

View File

@ -2,7 +2,6 @@ import VideoPlayer from "./VideoPlayer";
import React, { import React, {
useCallback, useCallback,
useEffect, useEffect,
useMemo,
useRef, useRef,
useState, useState,
} from "react"; } from "react";
@ -11,6 +10,7 @@ import Player from "video.js/dist/types/player";
import { AspectRatio } from "../ui/aspect-ratio"; import { AspectRatio } from "../ui/aspect-ratio";
import { LuPlayCircle } from "react-icons/lu"; import { LuPlayCircle } from "react-icons/lu";
import { isCurrentHour } from "@/utils/dateUtil"; import { isCurrentHour } from "@/utils/dateUtil";
import { isSafari } from "@/utils/browserUtil";
type PreviewPlayerProps = { type PreviewPlayerProps = {
camera: string; camera: string;
@ -38,9 +38,6 @@ export default function PreviewThumbnailPlayer({
onClick, onClick,
}: PreviewPlayerProps) { }: PreviewPlayerProps) {
const playerRef = useRef<Player | null>(null); const playerRef = useRef<Player | null>(null);
const isSafari = useMemo(() => {
return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
}, []);
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [isInitiallyVisible, setIsInitiallyVisible] = useState(false); const [isInitiallyVisible, setIsInitiallyVisible] = useState(false);
@ -135,7 +132,6 @@ export default function PreviewThumbnailPlayer({
camera={camera} camera={camera}
eventId={eventId} eventId={eventId}
isMobile={isMobile} isMobile={isMobile}
isSafari={isSafari}
onClick={onClick} onClick={onClick}
/> />
</AspectRatio> </AspectRatio>
@ -151,7 +147,6 @@ type PreviewContentProps = {
isInitiallyVisible: boolean; isInitiallyVisible: boolean;
startTs: number; startTs: number;
isMobile: boolean; isMobile: boolean;
isSafari: boolean;
onClick?: () => void; onClick?: () => void;
}; };
function PreviewContent({ function PreviewContent({
@ -163,10 +158,10 @@ function PreviewContent({
isInitiallyVisible, isInitiallyVisible,
startTs, startTs,
isMobile, isMobile,
isSafari,
onClick, onClick,
}: PreviewContentProps) { }: PreviewContentProps) {
const apiHost = useApiHost(); const apiHost = useApiHost();
const slowPlayack = isSafari();
// handle touchstart -> touchend as click // handle touchstart -> touchend as click
const [touchStart, setTouchStart] = useState(0); const [touchStart, setTouchStart] = useState(0);
@ -237,7 +232,7 @@ function PreviewContent({
player.pause(); // autoplay + pause is required for iOS player.pause(); // autoplay + pause is required for iOS
} }
player.playbackRate(isSafari ? 2 : 8); player.playbackRate(slowPlayack ? 2 : 8);
player.currentTime(startTs - relevantPreview.start); player.currentTime(startTs - relevantPreview.start);
if (isMobile && onClick) { if (isMobile && onClick) {
player.on("touchstart", handleTouchStart); player.on("touchstart", handleTouchStart);

View File

@ -4,16 +4,21 @@ import { useCallback, useEffect, useRef } from "react";
type WebRtcPlayerProps = { type WebRtcPlayerProps = {
className?: string; className?: string;
camera: string; camera: string;
playbackEnabled?: boolean;
onPlaying?: () => void; onPlaying?: () => void;
}; };
export default function WebRtcPlayer({ export default function WebRtcPlayer({
className, className,
camera, camera,
playbackEnabled = true,
onPlaying, onPlaying,
}: WebRtcPlayerProps) { }: WebRtcPlayerProps) {
// camera states
const pcRef = useRef<RTCPeerConnection | undefined>(); const pcRef = useRef<RTCPeerConnection | undefined>();
const videoRef = useRef<HTMLVideoElement | null>(null); const videoRef = useRef<HTMLVideoElement | null>(null);
const PeerConnection = useCallback( const PeerConnection = useCallback(
async (media: string) => { async (media: string) => {
if (!videoRef.current) { if (!videoRef.current) {
@ -129,6 +134,10 @@ export default function WebRtcPlayer({
return; return;
} }
if (!playbackEnabled) {
return;
}
const url = `${baseUrl.replace( const url = `${baseUrl.replace(
/^http/, /^http/,
"ws" "ws"
@ -143,18 +152,16 @@ export default function WebRtcPlayer({
pcRef.current = undefined; pcRef.current = undefined;
} }
}; };
}, [camera, connect, PeerConnection, pcRef, videoRef]); }, [camera, connect, PeerConnection, pcRef, videoRef, playbackEnabled]);
return ( return (
<div> <video
<video ref={videoRef}
ref={videoRef} className={className}
className={className} autoPlay
autoPlay playsInline
playsInline muted
muted onLoadedData={onPlaying}
onLoadedData={onPlaying} />
/>
</div>
); );
} }

View File

@ -5,6 +5,7 @@ import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
import { TooltipProvider } from "@/components/ui/tooltip"; import { TooltipProvider } from "@/components/ui/tooltip";
import { Event as FrigateEvent } from "@/types/event"; import { Event as FrigateEvent } from "@/types/event";
import { FrigateConfig } from "@/types/frigateConfig"; import { FrigateConfig } from "@/types/frigateConfig";
import { isSafari } from "@/utils/browserUtil";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import useSWR from "swr"; import useSWR from "swr";
@ -64,6 +65,7 @@ function Live() {
.sort((aConf, bConf) => aConf.ui.order - bConf.ui.order); .sort((aConf, bConf) => aConf.ui.order - bConf.ui.order);
}, [config]); }, [config]);
const safari = isSafari();
const [windowVisible, setWindowVisible] = useState(true); const [windowVisible, setWindowVisible] = useState(true);
const visibilityListener = useCallback(() => { const visibilityListener = useCallback(() => {
setWindowVisible(document.visibilityState == "visible"); setWindowVisible(document.visibilityState == "visible");
@ -109,7 +111,7 @@ function Live() {
className={`mb-2 md:mb-0 rounded-2xl bg-black ${grow}`} className={`mb-2 md:mb-0 rounded-2xl bg-black ${grow}`}
windowVisible={windowVisible} windowVisible={windowVisible}
cameraConfig={camera} cameraConfig={camera}
preferredLiveMode="mse" preferredLiveMode={safari ? "webrtc" : "mse"}
/> />
); );
})} })}

View File

@ -0,0 +1,7 @@
import { useMemo } from "react";
export function isSafari() {
return useMemo(() => {
return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
}, []);
}