mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-09 04:35:25 +03:00
Allow hovering to control preview on desktop
This commit is contained in:
parent
b1470682ba
commit
79a1b000e5
@ -292,10 +292,12 @@ function VideoPreview({
|
|||||||
onTimeUpdate,
|
onTimeUpdate,
|
||||||
}: VideoPreviewProps) {
|
}: VideoPreviewProps) {
|
||||||
const playerRef = useRef<HTMLVideoElement | null>(null);
|
const playerRef = useRef<HTMLVideoElement | null>(null);
|
||||||
|
const sliderRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
// keep track of playback state
|
// keep track of playback state
|
||||||
|
|
||||||
const [progress, setProgress] = useState(0);
|
const [progress, setProgress] = useState(0);
|
||||||
|
const [hoverTimeout, setHoverTimeout] = useState<NodeJS.Timeout>();
|
||||||
const playerStartTime = useMemo(() => {
|
const playerStartTime = useMemo(() => {
|
||||||
if (!relevantPreview) {
|
if (!relevantPreview) {
|
||||||
return 0;
|
return 0;
|
||||||
@ -456,6 +458,26 @@ function VideoPreview({
|
|||||||
}, 500);
|
}, 500);
|
||||||
}, [playerRef, setIgnoreClick]);
|
}, [playerRef, setIgnoreClick]);
|
||||||
|
|
||||||
|
const onProgressHover = useCallback(
|
||||||
|
(event: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
if (!sliderRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rect = sliderRef.current.getBoundingClientRect();
|
||||||
|
const positionX = event.clientX - rect.left;
|
||||||
|
const width = sliderRef.current.clientWidth;
|
||||||
|
onManualSeek([Math.round((positionX / width) * 100)]);
|
||||||
|
|
||||||
|
if (hoverTimeout) {
|
||||||
|
clearTimeout(hoverTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
setHoverTimeout(setTimeout(() => onStopManualSeek(), 500));
|
||||||
|
},
|
||||||
|
[sliderRef, hoverTimeout, onManualSeek, onStopManualSeek, setHoverTimeout],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative size-full aspect-video bg-black">
|
<div className="relative size-full aspect-video bg-black">
|
||||||
<video
|
<video
|
||||||
@ -470,6 +492,7 @@ function VideoPreview({
|
|||||||
<source src={relevantPreview.src} type={relevantPreview.type} />
|
<source src={relevantPreview.src} type={relevantPreview.type} />
|
||||||
</video>
|
</video>
|
||||||
<Slider
|
<Slider
|
||||||
|
ref={sliderRef}
|
||||||
className="absolute inset-x-0 bottom-0 z-30"
|
className="absolute inset-x-0 bottom-0 z-30"
|
||||||
value={[progress]}
|
value={[progress]}
|
||||||
onValueChange={onManualSeek}
|
onValueChange={onManualSeek}
|
||||||
@ -477,6 +500,7 @@ function VideoPreview({
|
|||||||
min={0}
|
min={0}
|
||||||
step={1}
|
step={1}
|
||||||
max={100}
|
max={100}
|
||||||
|
onMouseMove={isMobile ? undefined : onProgressHover}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -498,12 +522,14 @@ function InProgressPreview({
|
|||||||
onTimeUpdate,
|
onTimeUpdate,
|
||||||
}: InProgressPreviewProps) {
|
}: InProgressPreviewProps) {
|
||||||
const apiHost = useApiHost();
|
const apiHost = useApiHost();
|
||||||
|
const sliderRef = useRef<HTMLDivElement | null>(null);
|
||||||
const { data: previewFrames } = useSWR<string[]>(
|
const { data: previewFrames } = useSWR<string[]>(
|
||||||
`preview/${review.camera}/start/${Math.floor(review.start_time) - PREVIEW_PADDING}/end/${
|
`preview/${review.camera}/start/${Math.floor(review.start_time) - PREVIEW_PADDING}/end/${
|
||||||
Math.ceil(review.end_time) + PREVIEW_PADDING
|
Math.ceil(review.end_time) + PREVIEW_PADDING
|
||||||
}/frames`,
|
}/frames`,
|
||||||
);
|
);
|
||||||
const [manualFrame, setManualFrame] = useState(false);
|
const [manualFrame, setManualFrame] = useState(false);
|
||||||
|
const [hoverTimeout, setHoverTimeout] = useState<NodeJS.Timeout>();
|
||||||
const [key, setKey] = useState(0);
|
const [key, setKey] = useState(0);
|
||||||
|
|
||||||
const handleLoad = useCallback(() => {
|
const handleLoad = useCallback(() => {
|
||||||
@ -575,6 +601,34 @@ function InProgressPreview({
|
|||||||
[setManualFrame, setIgnoreClick],
|
[setManualFrame, setIgnoreClick],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onProgressHover = useCallback(
|
||||||
|
(event: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
if (!sliderRef.current || !previewFrames) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rect = sliderRef.current.getBoundingClientRect();
|
||||||
|
const positionX = event.clientX - rect.left;
|
||||||
|
const width = sliderRef.current.clientWidth;
|
||||||
|
const progress = [Math.round((positionX / width) * previewFrames.length)];
|
||||||
|
onManualSeek(progress);
|
||||||
|
|
||||||
|
if (hoverTimeout) {
|
||||||
|
clearTimeout(hoverTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
setHoverTimeout(setTimeout(() => onStopManualSeek(progress), 500));
|
||||||
|
},
|
||||||
|
[
|
||||||
|
sliderRef,
|
||||||
|
hoverTimeout,
|
||||||
|
previewFrames,
|
||||||
|
onManualSeek,
|
||||||
|
onStopManualSeek,
|
||||||
|
setHoverTimeout,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
if (!previewFrames || previewFrames.length == 0) {
|
if (!previewFrames || previewFrames.length == 0) {
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
@ -592,6 +646,7 @@ function InProgressPreview({
|
|||||||
onLoad={handleLoad}
|
onLoad={handleLoad}
|
||||||
/>
|
/>
|
||||||
<Slider
|
<Slider
|
||||||
|
ref={sliderRef}
|
||||||
className="absolute inset-x-0 bottom-0 z-30"
|
className="absolute inset-x-0 bottom-0 z-30"
|
||||||
value={[key]}
|
value={[key]}
|
||||||
onValueChange={onManualSeek}
|
onValueChange={onManualSeek}
|
||||||
@ -599,6 +654,7 @@ function InProgressPreview({
|
|||||||
min={0}
|
min={0}
|
||||||
step={1}
|
step={1}
|
||||||
max={previewFrames.length - 1}
|
max={previewFrames.length - 1}
|
||||||
|
onMouseMove={isMobile ? undefined : onProgressHover}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user