mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-05 22:57:40 +03:00
fix motion previews on safari and ios
match the logic used in ScrubbablePreview for manually stepping currentTime at the correct rate
This commit is contained in:
parent
361b092007
commit
8f5698f261
@ -9,6 +9,7 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { isCurrentHour } from "@/utils/dateUtil";
|
import { isCurrentHour } from "@/utils/dateUtil";
|
||||||
|
import { isFirefox, isMobile, isSafari } from "react-device-detect";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { CameraConfig } from "@/types/frigateConfig";
|
import { CameraConfig } from "@/types/frigateConfig";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
@ -305,31 +306,46 @@ function MotionPreviewClip({
|
|||||||
);
|
);
|
||||||
}, [clipStart, preview, range.end_time]);
|
}, [clipStart, preview, range.end_time]);
|
||||||
|
|
||||||
|
const compatIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (compatIntervalRef.current) {
|
||||||
|
clearInterval(compatIntervalRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const resetPlayback = useCallback(() => {
|
const resetPlayback = useCallback(() => {
|
||||||
if (!videoRef.current || !preview) {
|
if (!videoRef.current || !preview) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (compatIntervalRef.current) {
|
||||||
|
clearInterval(compatIntervalRef.current);
|
||||||
|
compatIntervalRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
videoRef.current.currentTime = clipStart;
|
videoRef.current.currentTime = clipStart;
|
||||||
videoRef.current.playbackRate = playbackRate;
|
|
||||||
}, [clipStart, playbackRate, preview]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
if (isSafari || (isFirefox && isMobile)) {
|
||||||
if (!videoRef.current || !preview) {
|
// Safari / iOS can't play at speeds > 2x, so manually step through frames
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isVisible) {
|
|
||||||
videoRef.current.pause();
|
videoRef.current.pause();
|
||||||
videoRef.current.currentTime = clipStart;
|
compatIntervalRef.current = setInterval(() => {
|
||||||
return;
|
if (!videoRef.current) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (videoRef.current.readyState >= 2) {
|
videoRef.current.currentTime += 1;
|
||||||
resetPlayback();
|
|
||||||
void videoRef.current.play().catch(() => undefined);
|
if (videoRef.current.currentTime >= clipEnd) {
|
||||||
|
videoRef.current.currentTime = clipStart;
|
||||||
|
}
|
||||||
|
}, 1000 / playbackRate);
|
||||||
|
} else {
|
||||||
|
videoRef.current.playbackRate = playbackRate;
|
||||||
}
|
}
|
||||||
}, [clipStart, isVisible, preview, resetPlayback]);
|
}, [clipStart, clipEnd, playbackRate, preview]);
|
||||||
|
|
||||||
const drawDimOverlay = useCallback(() => {
|
const drawDimOverlay = useCallback(() => {
|
||||||
if (!dimOverlayCanvasRef.current) {
|
if (!dimOverlayCanvasRef.current) {
|
||||||
@ -463,15 +479,17 @@ function MotionPreviewClip({
|
|||||||
{showLoadingIndicator && (
|
{showLoadingIndicator && (
|
||||||
<Skeleton className="absolute inset-0 z-10 rounded-lg md:rounded-2xl" />
|
<Skeleton className="absolute inset-0 z-10 rounded-lg md:rounded-2xl" />
|
||||||
)}
|
)}
|
||||||
{preview ? (
|
{preview && isVisible ? (
|
||||||
<>
|
<>
|
||||||
<video
|
<video
|
||||||
ref={videoRef}
|
ref={videoRef}
|
||||||
className="size-full bg-black object-contain"
|
className="size-full bg-black object-contain"
|
||||||
|
preload="auto"
|
||||||
|
autoPlay
|
||||||
playsInline
|
playsInline
|
||||||
preload={isVisible ? "metadata" : "none"}
|
|
||||||
muted
|
muted
|
||||||
autoPlay={isVisible}
|
disableRemotePlayback
|
||||||
|
loop
|
||||||
onLoadedMetadata={() => {
|
onLoadedMetadata={() => {
|
||||||
setVideoLoaded(true);
|
setVideoLoaded(true);
|
||||||
|
|
||||||
@ -481,36 +499,21 @@ function MotionPreviewClip({
|
|||||||
height: videoRef.current.videoHeight,
|
height: videoRef.current.videoHeight,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isVisible) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
resetPlayback();
|
|
||||||
|
|
||||||
if (videoRef.current) {
|
|
||||||
void videoRef.current.play().catch(() => undefined);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
onCanPlay={() => {
|
onCanPlay={() => {
|
||||||
setVideoLoaded(true);
|
setVideoLoaded(true);
|
||||||
|
|
||||||
if (!isVisible) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (videoRef.current) {
|
|
||||||
void videoRef.current.play().catch(() => undefined);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
onPlay={() => setVideoPlaying(true)}
|
onPlay={() => {
|
||||||
|
setVideoPlaying(true);
|
||||||
|
resetPlayback();
|
||||||
|
}}
|
||||||
onLoadedData={() => setVideoLoaded(true)}
|
onLoadedData={() => setVideoLoaded(true)}
|
||||||
onError={() => {
|
onError={() => {
|
||||||
setVideoLoaded(true);
|
setVideoLoaded(true);
|
||||||
setVideoPlaying(true);
|
setVideoPlaying(true);
|
||||||
}}
|
}}
|
||||||
onTimeUpdate={() => {
|
onTimeUpdate={() => {
|
||||||
if (!videoRef.current || !preview || !isVisible) {
|
if (!videoRef.current || !preview) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -519,12 +522,10 @@ function MotionPreviewClip({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isVisible && (
|
<source
|
||||||
<source
|
src={`${baseUrl}${preview.src.substring(1)}`}
|
||||||
src={`${baseUrl}${preview.src.substring(1)}`}
|
type={preview.type}
|
||||||
type={preview.type}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</video>
|
</video>
|
||||||
{motionHeatmap && (
|
{motionHeatmap && (
|
||||||
<canvas
|
<canvas
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user