mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-09 04:35:25 +03:00
Allow playback on motion screen
This commit is contained in:
parent
ed5cf4df87
commit
8b89a54e50
@ -10,7 +10,7 @@ import Hls from "hls.js";
|
||||
import { isDesktop, isMobile } from "react-device-detect";
|
||||
import useKeyboardListener from "@/hooks/use-keyboard-listener";
|
||||
import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
|
||||
import VideoControls from "./PlayerControls";
|
||||
import VideoControls from "./VideoControls";
|
||||
|
||||
const HLS_MIME_TYPE = "application/vnd.apple.mpegurl" as const;
|
||||
const unsupportedErrorCodes = [
|
||||
@ -210,6 +210,26 @@ export default function HlsVideoPlayer({
|
||||
show={controls}
|
||||
controlsOpen={controlsOpen}
|
||||
setControlsOpen={setControlsOpen}
|
||||
onPlayPause={(play) => {
|
||||
if (!videoRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (play) {
|
||||
videoRef.current.play();
|
||||
} else {
|
||||
videoRef.current.pause();
|
||||
}
|
||||
}}
|
||||
onSeek={(diff) => {
|
||||
const currentTime = videoRef.current?.currentTime;
|
||||
|
||||
if (!videoRef.current || !currentTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
videoRef.current.currentTime = Math.max(0, currentTime + diff);
|
||||
}}
|
||||
/>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@ -32,12 +32,14 @@ const CONTROLS_DEFAULT: VideoControls = {
|
||||
|
||||
type VideoControlsProps = {
|
||||
className?: string;
|
||||
video: HTMLVideoElement | null;
|
||||
video?: HTMLVideoElement | null;
|
||||
features?: VideoControls;
|
||||
isPlaying: boolean;
|
||||
show: boolean;
|
||||
controlsOpen: boolean;
|
||||
setControlsOpen: (open: boolean) => void;
|
||||
controlsOpen?: boolean;
|
||||
setControlsOpen?: (open: boolean) => void;
|
||||
onPlayPause: (play: boolean) => void;
|
||||
onSeek: (diff: number) => void;
|
||||
};
|
||||
export default function VideoControls({
|
||||
className,
|
||||
@ -47,6 +49,8 @@ export default function VideoControls({
|
||||
show,
|
||||
controlsOpen,
|
||||
setControlsOpen,
|
||||
onPlayPause,
|
||||
onSeek,
|
||||
}: VideoControlsProps) {
|
||||
const playbackRates = useMemo(() => {
|
||||
if (isSafari) {
|
||||
@ -59,48 +63,25 @@ export default function VideoControls({
|
||||
const onReplay = useCallback(
|
||||
(e: React.MouseEvent<SVGElement>) => {
|
||||
e.stopPropagation();
|
||||
|
||||
const currentTime = video?.currentTime;
|
||||
|
||||
if (!video || !currentTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
video.currentTime = Math.max(0, currentTime - 10);
|
||||
onSeek(-10);
|
||||
},
|
||||
[video],
|
||||
[onSeek],
|
||||
);
|
||||
|
||||
const onSkip = useCallback(
|
||||
(e: React.MouseEvent<SVGElement>) => {
|
||||
e.stopPropagation();
|
||||
|
||||
const currentTime = video?.currentTime;
|
||||
|
||||
if (!video || !currentTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
video.currentTime = currentTime + 10;
|
||||
onSeek(10);
|
||||
},
|
||||
[video],
|
||||
[onSeek],
|
||||
);
|
||||
|
||||
const onTogglePlay = useCallback(
|
||||
(e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation();
|
||||
|
||||
if (!video) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPlaying) {
|
||||
video.pause();
|
||||
} else {
|
||||
video.play();
|
||||
}
|
||||
onPlayPause(!isPlaying);
|
||||
},
|
||||
[isPlaying, video],
|
||||
[isPlaying, onPlayPause],
|
||||
);
|
||||
|
||||
// volume control
|
||||
@ -119,7 +100,7 @@ export default function VideoControls({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [video?.volume, video?.muted]);
|
||||
|
||||
if (!video || !show) {
|
||||
if (!show) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -127,7 +108,7 @@ export default function VideoControls({
|
||||
<div
|
||||
className={`px-4 py-2 flex justify-between items-center gap-8 text-white z-50 bg-black bg-opacity-60 rounded-lg ${className ?? ""}`}
|
||||
>
|
||||
{features.volume && (
|
||||
{video && features.volume && (
|
||||
<div className="flex justify-normal items-center gap-2">
|
||||
<VolumeIcon
|
||||
className="size-5"
|
||||
@ -161,17 +142,19 @@ export default function VideoControls({
|
||||
{features.seek && (
|
||||
<MdForward10 className="size-5 cursor-pointer" onClick={onSkip} />
|
||||
)}
|
||||
{features.playbackRate && (
|
||||
{video && features.playbackRate && (
|
||||
<DropdownMenu
|
||||
open={controlsOpen}
|
||||
open={controlsOpen == true}
|
||||
onOpenChange={(open) => {
|
||||
setControlsOpen(open);
|
||||
if (setControlsOpen) {
|
||||
setControlsOpen(open);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DropdownMenuTrigger>{`${video.playbackRate}x`}</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuRadioGroup
|
||||
onValueChange={(rate) => (video.playbackRate = parseInt(rate))}
|
||||
onValueChange={(rate) => (video.playbackRate = parseFloat(rate))}
|
||||
>
|
||||
{playbackRates.map((rate) => (
|
||||
<DropdownMenuRadioItem key={rate} value={rate.toString()}>
|
||||
@ -38,6 +38,7 @@ import PreviewPlayer, {
|
||||
} from "@/components/player/PreviewPlayer";
|
||||
import SummaryTimeline from "@/components/timeline/SummaryTimeline";
|
||||
import { RecordingStartingPoint } from "@/types/record";
|
||||
import VideoControls from "@/components/player/VideoControls";
|
||||
|
||||
type EventViewProps = {
|
||||
reviews?: ReviewSegment[];
|
||||
@ -678,6 +679,7 @@ function MotionReview({
|
||||
);
|
||||
|
||||
const [scrubbing, setScrubbing] = useState(false);
|
||||
const [playing, setPlaying] = useState(false);
|
||||
|
||||
// move to next clip
|
||||
|
||||
@ -704,6 +706,33 @@ function MotionReview({
|
||||
});
|
||||
}, [currentTime, currentTimeRange, timeRangeSegments]);
|
||||
|
||||
// playback
|
||||
|
||||
useEffect(() => {
|
||||
if (!playing) {
|
||||
return;
|
||||
}
|
||||
|
||||
const startTime = currentTime;
|
||||
let counter = 0;
|
||||
const intervalId = setInterval(() => {
|
||||
counter += 0.5;
|
||||
|
||||
if (startTime + counter >= timeRange.before) {
|
||||
setPlaying(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setCurrentTime(startTime + counter);
|
||||
}, 60);
|
||||
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
// do not render when current time changes
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [playing]);
|
||||
|
||||
if (!relevantPreviews) {
|
||||
return <ActivityIndicator />;
|
||||
}
|
||||
@ -762,9 +791,30 @@ function MotionReview({
|
||||
motion_events={motionData ?? []}
|
||||
severityType="significant_motion"
|
||||
contentRef={contentRef}
|
||||
onHandlebarDraggingChange={(scrubbing) => setScrubbing(scrubbing)}
|
||||
onHandlebarDraggingChange={(scrubbing) => {
|
||||
if (playing && scrubbing) {
|
||||
setPlaying(false);
|
||||
}
|
||||
|
||||
setScrubbing(scrubbing);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<VideoControls
|
||||
className="absolute bottom-16 left-1/2 -translate-x-1/2 bg-secondary"
|
||||
features={{
|
||||
volume: false,
|
||||
seek: true,
|
||||
playbackRate: false,
|
||||
}}
|
||||
isPlaying={playing}
|
||||
onPlayPause={setPlaying}
|
||||
onSeek={(diff) => {
|
||||
setCurrentTime(currentTime + diff);
|
||||
}}
|
||||
show={currentTime < timeRange.before - 4}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user