mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	Add ability to zoom in to live and recordings views (#10475)
* Make live view zoomable * Add zooming to full recordings
This commit is contained in:
		
							parent
							
								
									c66f552280
								
							
						
					
					
						commit
						f5a26c5962
					
				
							
								
								
									
										14
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										14
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							@ -52,6 +52,7 @@
 | 
				
			|||||||
        "react-tracked": "^1.7.11",
 | 
					        "react-tracked": "^1.7.11",
 | 
				
			||||||
        "react-transition-group": "^4.4.5",
 | 
					        "react-transition-group": "^4.4.5",
 | 
				
			||||||
        "react-use-websocket": "^4.7.0",
 | 
					        "react-use-websocket": "^4.7.0",
 | 
				
			||||||
 | 
					        "react-zoom-pan-pinch": "^3.4.3",
 | 
				
			||||||
        "recoil": "^0.7.7",
 | 
					        "recoil": "^0.7.7",
 | 
				
			||||||
        "scroll-into-view-if-needed": "^3.1.0",
 | 
					        "scroll-into-view-if-needed": "^3.1.0",
 | 
				
			||||||
        "sonner": "^1.4.0",
 | 
					        "sonner": "^1.4.0",
 | 
				
			||||||
@ -6694,6 +6695,19 @@
 | 
				
			|||||||
        "react-dom": ">= 18.0.0"
 | 
					        "react-dom": ">= 18.0.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/react-zoom-pan-pinch": {
 | 
				
			||||||
 | 
					      "version": "3.4.3",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-3.4.3.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-x5MFlfAx2D6NTpZu8OISqc2nYn4p+YEaM1p21w7S/VE1wbVzK8vRzTo9Bj1ydufa649MuP7JBRM3vvj1RftFZw==",
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=8",
 | 
				
			||||||
 | 
					        "npm": ">=5"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "peerDependencies": {
 | 
				
			||||||
 | 
					        "react": "*",
 | 
				
			||||||
 | 
					        "react-dom": "*"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/read-cache": {
 | 
					    "node_modules/read-cache": {
 | 
				
			||||||
      "version": "1.0.0",
 | 
					      "version": "1.0.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
 | 
				
			||||||
 | 
				
			|||||||
@ -57,6 +57,7 @@
 | 
				
			|||||||
    "react-tracked": "^1.7.11",
 | 
					    "react-tracked": "^1.7.11",
 | 
				
			||||||
    "react-transition-group": "^4.4.5",
 | 
					    "react-transition-group": "^4.4.5",
 | 
				
			||||||
    "react-use-websocket": "^4.7.0",
 | 
					    "react-use-websocket": "^4.7.0",
 | 
				
			||||||
 | 
					    "react-zoom-pan-pinch": "^3.4.3",
 | 
				
			||||||
    "recoil": "^0.7.7",
 | 
					    "recoil": "^0.7.7",
 | 
				
			||||||
    "scroll-into-view-if-needed": "^3.1.0",
 | 
					    "scroll-into-view-if-needed": "^3.1.0",
 | 
				
			||||||
    "sonner": "^1.4.0",
 | 
					    "sonner": "^1.4.0",
 | 
				
			||||||
 | 
				
			|||||||
@ -27,6 +27,7 @@ import {
 | 
				
			|||||||
} from "react-icons/md";
 | 
					} from "react-icons/md";
 | 
				
			||||||
import useKeyboardListener from "@/hooks/use-keyboard-listener";
 | 
					import useKeyboardListener from "@/hooks/use-keyboard-listener";
 | 
				
			||||||
import { Slider } from "../ui/slider-volume";
 | 
					import { Slider } from "../ui/slider-volume";
 | 
				
			||||||
 | 
					import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const HLS_MIME_TYPE = "application/vnd.apple.mpegurl" as const;
 | 
					const HLS_MIME_TYPE = "application/vnd.apple.mpegurl" as const;
 | 
				
			||||||
const unsupportedErrorCodes = [
 | 
					const unsupportedErrorCodes = [
 | 
				
			||||||
@ -169,48 +170,54 @@ export default function HlsVideoPlayer({
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
      onClick={isDesktop ? undefined : () => setControls(!controls)}
 | 
					      onClick={isDesktop ? undefined : () => setControls(!controls)}
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <video
 | 
					      <TransformWrapper minScale={1.0}>
 | 
				
			||||||
        ref={videoRef}
 | 
					        <TransformComponent>
 | 
				
			||||||
        className="size-full rounded-2xl"
 | 
					          <video
 | 
				
			||||||
        preload="auto"
 | 
					            ref={videoRef}
 | 
				
			||||||
        autoPlay
 | 
					            className="size-full rounded-2xl"
 | 
				
			||||||
        controls={false}
 | 
					            preload="auto"
 | 
				
			||||||
        playsInline
 | 
					            autoPlay
 | 
				
			||||||
        muted
 | 
					            controls={false}
 | 
				
			||||||
        onPlay={() => {
 | 
					            playsInline
 | 
				
			||||||
          setIsPlaying(true);
 | 
					            muted
 | 
				
			||||||
 | 
					            onPlay={() => {
 | 
				
			||||||
 | 
					              setIsPlaying(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          if (isMobile) {
 | 
					              if (isMobile) {
 | 
				
			||||||
            setControls(true);
 | 
					                setControls(true);
 | 
				
			||||||
            setMobileCtrlTimeout(setTimeout(() => setControls(false), 4000));
 | 
					                setMobileCtrlTimeout(
 | 
				
			||||||
          }
 | 
					                  setTimeout(() => setControls(false), 4000),
 | 
				
			||||||
        }}
 | 
					                );
 | 
				
			||||||
        onPlaying={onPlaying}
 | 
					              }
 | 
				
			||||||
        onPause={() => {
 | 
					            }}
 | 
				
			||||||
          setIsPlaying(false);
 | 
					            onPlaying={onPlaying}
 | 
				
			||||||
 | 
					            onPause={() => {
 | 
				
			||||||
 | 
					              setIsPlaying(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          if (isMobile && mobileCtrlTimeout) {
 | 
					              if (isMobile && mobileCtrlTimeout) {
 | 
				
			||||||
            clearTimeout(mobileCtrlTimeout);
 | 
					                clearTimeout(mobileCtrlTimeout);
 | 
				
			||||||
          }
 | 
					              }
 | 
				
			||||||
        }}
 | 
					            }}
 | 
				
			||||||
        onTimeUpdate={() =>
 | 
					            onTimeUpdate={() =>
 | 
				
			||||||
          onTimeUpdate && videoRef.current
 | 
					              onTimeUpdate && videoRef.current
 | 
				
			||||||
            ? onTimeUpdate(videoRef.current.currentTime)
 | 
					                ? onTimeUpdate(videoRef.current.currentTime)
 | 
				
			||||||
            : undefined
 | 
					                : undefined
 | 
				
			||||||
        }
 | 
					            }
 | 
				
			||||||
        onLoadedData={onPlayerLoaded}
 | 
					            onLoadedData={onPlayerLoaded}
 | 
				
			||||||
        onEnded={onClipEnded}
 | 
					            onEnded={onClipEnded}
 | 
				
			||||||
        onError={(e) => {
 | 
					            onError={(e) => {
 | 
				
			||||||
          if (
 | 
					              if (
 | 
				
			||||||
            !hlsRef.current &&
 | 
					                !hlsRef.current &&
 | 
				
			||||||
            // @ts-expect-error code does exist
 | 
					                // @ts-expect-error code does exist
 | 
				
			||||||
            unsupportedErrorCodes.includes(e.target.error.code) &&
 | 
					                unsupportedErrorCodes.includes(e.target.error.code) &&
 | 
				
			||||||
            videoRef.current
 | 
					                videoRef.current
 | 
				
			||||||
          ) {
 | 
					              ) {
 | 
				
			||||||
            setUseHlsCompat(true);
 | 
					                setUseHlsCompat(true);
 | 
				
			||||||
          }
 | 
					              }
 | 
				
			||||||
        }}
 | 
					            }}
 | 
				
			||||||
      />
 | 
					          />
 | 
				
			||||||
 | 
					        </TransformComponent>
 | 
				
			||||||
 | 
					      </TransformWrapper>
 | 
				
			||||||
      <VideoControls
 | 
					      <VideoControls
 | 
				
			||||||
        video={videoRef.current}
 | 
					        video={videoRef.current}
 | 
				
			||||||
        isPlaying={isPlaying}
 | 
					        isPlaying={isPlaying}
 | 
				
			||||||
 | 
				
			|||||||
@ -55,6 +55,7 @@ import {
 | 
				
			|||||||
  MdZoomOut,
 | 
					  MdZoomOut,
 | 
				
			||||||
} from "react-icons/md";
 | 
					} from "react-icons/md";
 | 
				
			||||||
import { useNavigate } from "react-router-dom";
 | 
					import { useNavigate } from "react-router-dom";
 | 
				
			||||||
 | 
					import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
 | 
				
			||||||
import useSWR from "swr";
 | 
					import useSWR from "swr";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type LiveCameraViewProps = {
 | 
					type LiveCameraViewProps = {
 | 
				
			||||||
@ -263,16 +264,20 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
 | 
				
			|||||||
            aspectRatio: aspectRatio,
 | 
					            aspectRatio: aspectRatio,
 | 
				
			||||||
          }}
 | 
					          }}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          <LivePlayer
 | 
					          <TransformWrapper minScale={1.0}>
 | 
				
			||||||
            key={camera.name}
 | 
					            <TransformComponent>
 | 
				
			||||||
            className={`${fullscreen ? "*:rounded-none" : ""}`}
 | 
					              <LivePlayer
 | 
				
			||||||
            windowVisible
 | 
					                key={camera.name}
 | 
				
			||||||
            showStillWithoutActivity={false}
 | 
					                className={`m-1 ${fullscreen ? "*:rounded-none" : ""}`}
 | 
				
			||||||
            cameraConfig={camera}
 | 
					                windowVisible
 | 
				
			||||||
            playAudio={audio}
 | 
					                showStillWithoutActivity={false}
 | 
				
			||||||
            micEnabled={mic}
 | 
					                cameraConfig={camera}
 | 
				
			||||||
            preferredLiveMode={preferredLiveMode}
 | 
					                playAudio={audio}
 | 
				
			||||||
          />
 | 
					                micEnabled={mic}
 | 
				
			||||||
 | 
					                preferredLiveMode={preferredLiveMode}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            </TransformComponent>
 | 
				
			||||||
 | 
					          </TransformWrapper>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        {camera.onvif.host != "" && <PtzControlPanel camera={camera.name} />}
 | 
					        {camera.onvif.host != "" && <PtzControlPanel camera={camera.name} />}
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user