mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	Fix handling of recordings and switching cameras (#10351)
* Fix handling of recordings and switching cameras * mobile switch * Cleanup * Cleanup autoplay * Remove vite
This commit is contained in:
		
							parent
							
								
									9fc1286568
								
							
						
					
					
						commit
						b910db4f05
					
				@ -5,7 +5,7 @@ type TWrapperProps = {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Wrapper = ({ children }: TWrapperProps) => {
 | 
					const Wrapper = ({ children }: TWrapperProps) => {
 | 
				
			||||||
  return <main className="w-screen h-screen overflow-hidden">{children}</main>;
 | 
					  return <main className="w-screen h-dvh overflow-hidden">{children}</main>;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Wrapper;
 | 
					export default Wrapper;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
 | 
					import { useCallback, useEffect, useMemo, useState } from "react";
 | 
				
			||||||
import VideoPlayer from "./VideoPlayer";
 | 
					import VideoPlayer from "./VideoPlayer";
 | 
				
			||||||
import Player from "video.js/dist/types/player";
 | 
					import Player from "video.js/dist/types/player";
 | 
				
			||||||
import TimelineEventOverlay from "../overlay/TimelineDataOverlay";
 | 
					import TimelineEventOverlay from "../overlay/TimelineDataOverlay";
 | 
				
			||||||
@ -24,6 +24,7 @@ type DynamicVideoPlayerProps = {
 | 
				
			|||||||
  timeRange: { start: number; end: number };
 | 
					  timeRange: { start: number; end: number };
 | 
				
			||||||
  cameraPreviews: Preview[];
 | 
					  cameraPreviews: Preview[];
 | 
				
			||||||
  previewOnly?: boolean;
 | 
					  previewOnly?: boolean;
 | 
				
			||||||
 | 
					  startTime?: number;
 | 
				
			||||||
  onControllerReady: (controller: DynamicVideoController) => void;
 | 
					  onControllerReady: (controller: DynamicVideoController) => void;
 | 
				
			||||||
  onClick?: () => void;
 | 
					  onClick?: () => void;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@ -33,6 +34,7 @@ export default function DynamicVideoPlayer({
 | 
				
			|||||||
  timeRange,
 | 
					  timeRange,
 | 
				
			||||||
  cameraPreviews,
 | 
					  cameraPreviews,
 | 
				
			||||||
  previewOnly = false,
 | 
					  previewOnly = false,
 | 
				
			||||||
 | 
					  startTime,
 | 
				
			||||||
  onControllerReady,
 | 
					  onControllerReady,
 | 
				
			||||||
  onClick,
 | 
					  onClick,
 | 
				
			||||||
}: DynamicVideoPlayerProps) {
 | 
					}: DynamicVideoPlayerProps) {
 | 
				
			||||||
@ -59,7 +61,7 @@ export default function DynamicVideoPlayer({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  // controlling playback
 | 
					  // controlling playback
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const playerRef = useRef<Player | null>(null);
 | 
					  const [playerRef, setPlayerRef] = useState<Player | null>(null);
 | 
				
			||||||
  const [previewController, setPreviewController] =
 | 
					  const [previewController, setPreviewController] =
 | 
				
			||||||
    useState<PreviewVideoController | null>(null);
 | 
					    useState<PreviewVideoController | null>(null);
 | 
				
			||||||
  const [isScrubbing, setIsScrubbing] = useState(previewOnly);
 | 
					  const [isScrubbing, setIsScrubbing] = useState(previewOnly);
 | 
				
			||||||
@ -67,13 +69,13 @@ export default function DynamicVideoPlayer({
 | 
				
			|||||||
    undefined,
 | 
					    undefined,
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
  const controller = useMemo(() => {
 | 
					  const controller = useMemo(() => {
 | 
				
			||||||
    if (!config || !playerRef.current || !previewController) {
 | 
					    if (!config || !playerRef || !previewController) {
 | 
				
			||||||
      return undefined;
 | 
					      return undefined;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return new DynamicVideoController(
 | 
					    return new DynamicVideoController(
 | 
				
			||||||
      camera,
 | 
					      camera,
 | 
				
			||||||
      playerRef.current,
 | 
					      playerRef,
 | 
				
			||||||
      previewController,
 | 
					      previewController,
 | 
				
			||||||
      (config.cameras[camera]?.detect?.annotation_offset || 0) / 1000,
 | 
					      (config.cameras[camera]?.detect?.annotation_offset || 0) / 1000,
 | 
				
			||||||
      previewOnly ? "scrubbing" : "playback",
 | 
					      previewOnly ? "scrubbing" : "playback",
 | 
				
			||||||
@ -82,7 +84,7 @@ export default function DynamicVideoPlayer({
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
    // we only want to fire once when players are ready
 | 
					    // we only want to fire once when players are ready
 | 
				
			||||||
    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
					    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
				
			||||||
  }, [camera, config, playerRef.current, previewController]);
 | 
					  }, [camera, config, playerRef, previewController]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    if (!controller) {
 | 
					    if (!controller) {
 | 
				
			||||||
@ -97,64 +99,44 @@ export default function DynamicVideoPlayer({
 | 
				
			|||||||
    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
					    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
				
			||||||
  }, [controller]);
 | 
					  }, [controller]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const [initPreviewOnly, setInitPreviewOnly] = useState(previewOnly);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    if (!controller || !playerRef.current) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (previewOnly == initPreviewOnly) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!previewOnly) {
 | 
					 | 
				
			||||||
      controller.seekToTimestamp(playerRef.current.currentTime() || 0, true);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    setInitPreviewOnly(previewOnly);
 | 
					 | 
				
			||||||
    // we only want to fire once when players are ready
 | 
					 | 
				
			||||||
    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
					 | 
				
			||||||
  }, [controller, previewOnly]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // keyboard control
 | 
					  // keyboard control
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const onKeyboardShortcut = useCallback(
 | 
					  const onKeyboardShortcut = useCallback(
 | 
				
			||||||
    (key: string, down: boolean, repeat: boolean) => {
 | 
					    (key: string, down: boolean, repeat: boolean) => {
 | 
				
			||||||
      if (!playerRef.current || previewOnly) {
 | 
					      if (!playerRef || previewOnly) {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      switch (key) {
 | 
					      switch (key) {
 | 
				
			||||||
        case "ArrowLeft":
 | 
					        case "ArrowLeft":
 | 
				
			||||||
          if (down) {
 | 
					          if (down) {
 | 
				
			||||||
            const currentTime = playerRef.current.currentTime();
 | 
					            const currentTime = playerRef.currentTime();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (currentTime) {
 | 
					            if (currentTime) {
 | 
				
			||||||
              playerRef.current.currentTime(Math.max(0, currentTime - 5));
 | 
					              playerRef.currentTime(Math.max(0, currentTime - 5));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          break;
 | 
					          break;
 | 
				
			||||||
        case "ArrowRight":
 | 
					        case "ArrowRight":
 | 
				
			||||||
          if (down) {
 | 
					          if (down) {
 | 
				
			||||||
            const currentTime = playerRef.current.currentTime();
 | 
					            const currentTime = playerRef.currentTime();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (currentTime) {
 | 
					            if (currentTime) {
 | 
				
			||||||
              playerRef.current.currentTime(currentTime + 5);
 | 
					              playerRef.currentTime(currentTime + 5);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          break;
 | 
					          break;
 | 
				
			||||||
        case "m":
 | 
					        case "m":
 | 
				
			||||||
          if (down && !repeat && playerRef) {
 | 
					          if (down && !repeat && playerRef) {
 | 
				
			||||||
            playerRef.current.muted(!playerRef.current.muted());
 | 
					            playerRef.muted(!playerRef.muted());
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          break;
 | 
					          break;
 | 
				
			||||||
        case " ":
 | 
					        case " ":
 | 
				
			||||||
          if (down && playerRef) {
 | 
					          if (down && playerRef) {
 | 
				
			||||||
            if (playerRef.current.paused()) {
 | 
					            if (playerRef.paused()) {
 | 
				
			||||||
              playerRef.current.play();
 | 
					              playerRef.play();
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
              playerRef.current.pause();
 | 
					              playerRef.pause();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          break;
 | 
					          break;
 | 
				
			||||||
@ -162,7 +144,7 @@ export default function DynamicVideoPlayer({
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    // only update when preview only changes
 | 
					    // only update when preview only changes
 | 
				
			||||||
    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
					    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
				
			||||||
    [playerRef.current, previewOnly],
 | 
					    [playerRef, previewOnly],
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
  useKeyboardListener(
 | 
					  useKeyboardListener(
 | 
				
			||||||
    ["ArrowLeft", "ArrowRight", "m", " "],
 | 
					    ["ArrowLeft", "ArrowRight", "m", " "],
 | 
				
			||||||
@ -186,6 +168,42 @@ export default function DynamicVideoPlayer({
 | 
				
			|||||||
    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
					    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
				
			||||||
  }, []);
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // start at correct time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    const player = playerRef;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!player) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (previewOnly) {
 | 
				
			||||||
 | 
					      player.autoplay(false);
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    player.autoplay(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!startTime) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (player.isReady_) {
 | 
				
			||||||
 | 
					      controller?.seekToTimestamp(startTime, true);
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const callback = () => {
 | 
				
			||||||
 | 
					      controller?.seekToTimestamp(startTime, true);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    player.on("loadeddata", callback);
 | 
				
			||||||
 | 
					    return () => {
 | 
				
			||||||
 | 
					      player.off("loadeddata", callback);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    // we only want to calculate this once
 | 
				
			||||||
 | 
					    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
				
			||||||
 | 
					  }, [previewOnly, startTime, controller]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // state of playback player
 | 
					  // state of playback player
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const recordingParams = useMemo(() => {
 | 
					  const recordingParams = useMemo(() => {
 | 
				
			||||||
@ -236,7 +254,7 @@ export default function DynamicVideoPlayer({
 | 
				
			|||||||
        <VideoPlayer
 | 
					        <VideoPlayer
 | 
				
			||||||
          options={{
 | 
					          options={{
 | 
				
			||||||
            preload: "auto",
 | 
					            preload: "auto",
 | 
				
			||||||
            autoplay: !previewOnly,
 | 
					            autoplay: false,
 | 
				
			||||||
            sources: [initialPlaybackSource],
 | 
					            sources: [initialPlaybackSource],
 | 
				
			||||||
            aspectRatio: wideVideo ? undefined : "16:9",
 | 
					            aspectRatio: wideVideo ? undefined : "16:9",
 | 
				
			||||||
            controlBar: {
 | 
					            controlBar: {
 | 
				
			||||||
@ -248,10 +266,10 @@ export default function DynamicVideoPlayer({
 | 
				
			|||||||
          }}
 | 
					          }}
 | 
				
			||||||
          seekOptions={{ forward: 10, backward: 5 }}
 | 
					          seekOptions={{ forward: 10, backward: 5 }}
 | 
				
			||||||
          onReady={(player) => {
 | 
					          onReady={(player) => {
 | 
				
			||||||
            playerRef.current = player;
 | 
					            setPlayerRef(player);
 | 
				
			||||||
          }}
 | 
					          }}
 | 
				
			||||||
          onDispose={() => {
 | 
					          onDispose={() => {
 | 
				
			||||||
            playerRef.current = null;
 | 
					            setPlayerRef(null);
 | 
				
			||||||
          }}
 | 
					          }}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          {config && focusedItem && (
 | 
					          {config && focusedItem && (
 | 
				
			||||||
@ -289,12 +307,10 @@ export class DynamicVideoController {
 | 
				
			|||||||
  private recordings: Recording[] = [];
 | 
					  private recordings: Recording[] = [];
 | 
				
			||||||
  private annotationOffset: number;
 | 
					  private annotationOffset: number;
 | 
				
			||||||
  private timeToStart: number | undefined = undefined;
 | 
					  private timeToStart: number | undefined = undefined;
 | 
				
			||||||
  private canPlay: boolean = false;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // listeners
 | 
					  // listeners
 | 
				
			||||||
  private playerProgressListener: (() => void) | null = null;
 | 
					  private playerProgressListener: (() => void) | null = null;
 | 
				
			||||||
  private playerEndedListener: (() => void) | null = null;
 | 
					  private playerEndedListener: (() => void) | null = null;
 | 
				
			||||||
  private canPlayListener: (() => void) | null = null;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    camera: string,
 | 
					    camera: string,
 | 
				
			||||||
@ -320,7 +336,6 @@ export class DynamicVideoController {
 | 
				
			|||||||
      src: newPlayback.playbackUri,
 | 
					      src: newPlayback.playbackUri,
 | 
				
			||||||
      type: "application/vnd.apple.mpegurl",
 | 
					      type: "application/vnd.apple.mpegurl",
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    this.canPlay = false;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (this.timeToStart) {
 | 
					    if (this.timeToStart) {
 | 
				
			||||||
      this.seekToTimestamp(this.timeToStart);
 | 
					      this.seekToTimestamp(this.timeToStart);
 | 
				
			||||||
@ -328,10 +343,6 @@ export class DynamicVideoController {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  autoPlay(play: boolean) {
 | 
					 | 
				
			||||||
    this.playerController.autoplay(play);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  seekToTimestamp(time: number, play: boolean = false) {
 | 
					  seekToTimestamp(time: number, play: boolean = false) {
 | 
				
			||||||
    if (this.playerMode != "playback") {
 | 
					    if (this.playerMode != "playback") {
 | 
				
			||||||
      this.playerMode = "playback";
 | 
					      this.playerMode = "playback";
 | 
				
			||||||
@ -391,21 +402,6 @@ export class DynamicVideoController {
 | 
				
			|||||||
    return timestamp;
 | 
					    return timestamp;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  onCanPlay(listener: (() => void) | null) {
 | 
					 | 
				
			||||||
    if (this.canPlayListener) {
 | 
					 | 
				
			||||||
      this.playerController.off("canplay", this.canPlayListener);
 | 
					 | 
				
			||||||
      this.canPlayListener = null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (listener) {
 | 
					 | 
				
			||||||
      this.canPlayListener = () => {
 | 
					 | 
				
			||||||
        this.canPlay = true;
 | 
					 | 
				
			||||||
        listener();
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      this.playerController.on("canplay", this.canPlayListener);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  onPlayerTimeUpdate(listener: ((timestamp: number) => void) | null) {
 | 
					  onPlayerTimeUpdate(listener: ((timestamp: number) => void) | null) {
 | 
				
			||||||
    if (this.playerProgressListener) {
 | 
					    if (this.playerProgressListener) {
 | 
				
			||||||
      this.playerController.off("timeupdate", this.playerProgressListener);
 | 
					      this.playerController.off("timeupdate", this.playerProgressListener);
 | 
				
			||||||
@ -414,9 +410,13 @@ export class DynamicVideoController {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if (listener) {
 | 
					    if (listener) {
 | 
				
			||||||
      this.playerProgressListener = () => {
 | 
					      this.playerProgressListener = () => {
 | 
				
			||||||
        if (this.canPlay) {
 | 
					        const progress = this.playerController.currentTime() || 0;
 | 
				
			||||||
          listener(this.getProgress(this.playerController.currentTime() || 0));
 | 
					
 | 
				
			||||||
 | 
					        if (progress == 0) {
 | 
				
			||||||
 | 
					          return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        listener(this.getProgress(progress));
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
      this.playerController.on("timeupdate", this.playerProgressListener);
 | 
					      this.playerController.on("timeupdate", this.playerProgressListener);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -270,6 +270,7 @@ export default function Events() {
 | 
				
			|||||||
          reviewItems={selectedReviewData.cameraSegments}
 | 
					          reviewItems={selectedReviewData.cameraSegments}
 | 
				
			||||||
          startCamera={selectedReviewData.camera}
 | 
					          startCamera={selectedReviewData.camera}
 | 
				
			||||||
          startTime={selectedReviewData.start_time}
 | 
					          startTime={selectedReviewData.start_time}
 | 
				
			||||||
 | 
					          allCameras={selectedReviewData.allCameras}
 | 
				
			||||||
          severity={selectedReviewData.severity}
 | 
					          severity={selectedReviewData.severity}
 | 
				
			||||||
          relevantPreviews={allPreviews}
 | 
					          relevantPreviews={allPreviews}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,13 @@ import DynamicVideoPlayer, {
 | 
				
			|||||||
import EventReviewTimeline from "@/components/timeline/EventReviewTimeline";
 | 
					import EventReviewTimeline from "@/components/timeline/EventReviewTimeline";
 | 
				
			||||||
import MotionReviewTimeline from "@/components/timeline/MotionReviewTimeline";
 | 
					import MotionReviewTimeline from "@/components/timeline/MotionReviewTimeline";
 | 
				
			||||||
import { Button } from "@/components/ui/button";
 | 
					import { Button } from "@/components/ui/button";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  DropdownMenu,
 | 
				
			||||||
 | 
					  DropdownMenuContent,
 | 
				
			||||||
 | 
					  DropdownMenuRadioGroup,
 | 
				
			||||||
 | 
					  DropdownMenuRadioItem,
 | 
				
			||||||
 | 
					  DropdownMenuTrigger,
 | 
				
			||||||
 | 
					} from "@/components/ui/dropdown-menu";
 | 
				
			||||||
import { Preview } from "@/types/preview";
 | 
					import { Preview } from "@/types/preview";
 | 
				
			||||||
import { MotionData, ReviewSegment, ReviewSeverity } from "@/types/review";
 | 
					import { MotionData, ReviewSegment, ReviewSeverity } from "@/types/review";
 | 
				
			||||||
import { getChunkedTimeDay } from "@/utils/timelineUtil";
 | 
					import { getChunkedTimeDay } from "@/utils/timelineUtil";
 | 
				
			||||||
@ -40,6 +47,8 @@ export function DesktopRecordingView({
 | 
				
			|||||||
    {},
 | 
					    {},
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [playbackStart, setPlaybackStart] = useState(startTime);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // timeline time
 | 
					  // timeline time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const timeRange = useMemo(() => getChunkedTimeDay(startTime), [startTime]);
 | 
					  const timeRange = useMemo(() => getChunkedTimeDay(startTime), [startTime]);
 | 
				
			||||||
@ -119,13 +128,7 @@ export function DesktopRecordingView({
 | 
				
			|||||||
      const newController = videoPlayersRef.current[newCam];
 | 
					      const newController = videoPlayersRef.current[newCam];
 | 
				
			||||||
      lastController.onPlayerTimeUpdate(null);
 | 
					      lastController.onPlayerTimeUpdate(null);
 | 
				
			||||||
      lastController.onClipChangedEvent(null);
 | 
					      lastController.onClipChangedEvent(null);
 | 
				
			||||||
      lastController.autoPlay(false);
 | 
					 | 
				
			||||||
      lastController.scrubToTimestamp(currentTime);
 | 
					      lastController.scrubToTimestamp(currentTime);
 | 
				
			||||||
      newController.autoPlay(true);
 | 
					 | 
				
			||||||
      newController.onCanPlay(() => {
 | 
					 | 
				
			||||||
        newController.seekToTimestamp(currentTime, true);
 | 
					 | 
				
			||||||
        newController.onCanPlay(null);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      newController.onPlayerTimeUpdate((timestamp: number) => {
 | 
					      newController.onPlayerTimeUpdate((timestamp: number) => {
 | 
				
			||||||
        setCurrentTime(timestamp);
 | 
					        setCurrentTime(timestamp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -137,6 +140,8 @@ export function DesktopRecordingView({
 | 
				
			|||||||
          }
 | 
					          }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					      newController.seekToTimestamp(currentTime, true);
 | 
				
			||||||
 | 
					      setPlaybackStart(currentTime);
 | 
				
			||||||
      setMainCamera(newCam);
 | 
					      setMainCamera(newCam);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    [allCameras, currentTime, mainCamera],
 | 
					    [allCameras, currentTime, mainCamera],
 | 
				
			||||||
@ -179,6 +184,7 @@ export function DesktopRecordingView({
 | 
				
			|||||||
                  camera={cam}
 | 
					                  camera={cam}
 | 
				
			||||||
                  timeRange={currentTimeRange}
 | 
					                  timeRange={currentTimeRange}
 | 
				
			||||||
                  cameraPreviews={allPreviews ?? []}
 | 
					                  cameraPreviews={allPreviews ?? []}
 | 
				
			||||||
 | 
					                  startTime={playbackStart}
 | 
				
			||||||
                  onControllerReady={(controller) => {
 | 
					                  onControllerReady={(controller) => {
 | 
				
			||||||
                    videoPlayersRef.current[cam] = controller;
 | 
					                    videoPlayersRef.current[cam] = controller;
 | 
				
			||||||
                    controller.onPlayerTimeUpdate((timestamp: number) => {
 | 
					                    controller.onPlayerTimeUpdate((timestamp: number) => {
 | 
				
			||||||
@ -192,11 +198,6 @@ export function DesktopRecordingView({
 | 
				
			|||||||
                        }
 | 
					                        }
 | 
				
			||||||
                      });
 | 
					                      });
 | 
				
			||||||
                    });
 | 
					                    });
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    controller.onCanPlay(() => {
 | 
					 | 
				
			||||||
                      controller.seekToTimestamp(startTime, true);
 | 
					 | 
				
			||||||
                      controller.onCanPlay(null);
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
                  }}
 | 
					                  }}
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
@ -264,6 +265,7 @@ type MobileRecordingViewProps = {
 | 
				
			|||||||
  severity: ReviewSeverity;
 | 
					  severity: ReviewSeverity;
 | 
				
			||||||
  reviewItems: ReviewSegment[];
 | 
					  reviewItems: ReviewSegment[];
 | 
				
			||||||
  relevantPreviews?: Preview[];
 | 
					  relevantPreviews?: Preview[];
 | 
				
			||||||
 | 
					  allCameras: string[];
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
export function MobileRecordingView({
 | 
					export function MobileRecordingView({
 | 
				
			||||||
  startCamera,
 | 
					  startCamera,
 | 
				
			||||||
@ -271,6 +273,7 @@ export function MobileRecordingView({
 | 
				
			|||||||
  severity,
 | 
					  severity,
 | 
				
			||||||
  reviewItems,
 | 
					  reviewItems,
 | 
				
			||||||
  relevantPreviews,
 | 
					  relevantPreviews,
 | 
				
			||||||
 | 
					  allCameras,
 | 
				
			||||||
}: MobileRecordingViewProps) {
 | 
					}: MobileRecordingViewProps) {
 | 
				
			||||||
  const navigate = useNavigate();
 | 
					  const navigate = useNavigate();
 | 
				
			||||||
  const contentRef = useRef<HTMLDivElement | null>(null);
 | 
					  const contentRef = useRef<HTMLDivElement | null>(null);
 | 
				
			||||||
@ -279,6 +282,8 @@ export function MobileRecordingView({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  const [playerReady, setPlayerReady] = useState(false);
 | 
					  const [playerReady, setPlayerReady] = useState(false);
 | 
				
			||||||
  const controllerRef = useRef<DynamicVideoController | undefined>(undefined);
 | 
					  const controllerRef = useRef<DynamicVideoController | undefined>(undefined);
 | 
				
			||||||
 | 
					  const [playbackCamera, setPlaybackCamera] = useState(startCamera);
 | 
				
			||||||
 | 
					  const [playbackStart, setPlaybackStart] = useState(startTime);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // timeline time
 | 
					  // timeline time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -361,16 +366,45 @@ export function MobileRecordingView({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div ref={contentRef} className="flex flex-col relative w-full h-full">
 | 
					    <div ref={contentRef} className="flex flex-col relative w-full h-full">
 | 
				
			||||||
      <Button className="rounded-lg" onClick={() => navigate(-1)}>
 | 
					      <div className="flex justify-evenly items-center">
 | 
				
			||||||
        <IoMdArrowRoundBack className="size-5 mr-[10px]" />
 | 
					        <Button className="rounded-lg" onClick={() => navigate(-1)}>
 | 
				
			||||||
        Back
 | 
					          <IoMdArrowRoundBack className="size-5 mr-[10px]" />
 | 
				
			||||||
      </Button>
 | 
					          Back
 | 
				
			||||||
 | 
					        </Button>
 | 
				
			||||||
 | 
					        <DropdownMenu>
 | 
				
			||||||
 | 
					          <DropdownMenuTrigger asChild>
 | 
				
			||||||
 | 
					            <Button className="capitalize">
 | 
				
			||||||
 | 
					              {playbackCamera.replaceAll("_", " ")}
 | 
				
			||||||
 | 
					            </Button>
 | 
				
			||||||
 | 
					          </DropdownMenuTrigger>
 | 
				
			||||||
 | 
					          <DropdownMenuContent>
 | 
				
			||||||
 | 
					            <DropdownMenuRadioGroup
 | 
				
			||||||
 | 
					              value={playbackCamera}
 | 
				
			||||||
 | 
					              onValueChange={(cam) => {
 | 
				
			||||||
 | 
					                setPlaybackStart(currentTime);
 | 
				
			||||||
 | 
					                setPlaybackCamera(cam);
 | 
				
			||||||
 | 
					              }}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              {allCameras.map((cam) => (
 | 
				
			||||||
 | 
					                <DropdownMenuRadioItem
 | 
				
			||||||
 | 
					                  key={cam}
 | 
				
			||||||
 | 
					                  className="capitalize"
 | 
				
			||||||
 | 
					                  value={cam}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                  {cam.replaceAll("_", " ")}
 | 
				
			||||||
 | 
					                </DropdownMenuRadioItem>
 | 
				
			||||||
 | 
					              ))}
 | 
				
			||||||
 | 
					            </DropdownMenuRadioGroup>
 | 
				
			||||||
 | 
					          </DropdownMenuContent>
 | 
				
			||||||
 | 
					        </DropdownMenu>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <div>
 | 
					      <div>
 | 
				
			||||||
        <DynamicVideoPlayer
 | 
					        <DynamicVideoPlayer
 | 
				
			||||||
          camera={startCamera}
 | 
					          camera={playbackCamera}
 | 
				
			||||||
          timeRange={currentTimeRange}
 | 
					          timeRange={currentTimeRange}
 | 
				
			||||||
          cameraPreviews={relevantPreviews || []}
 | 
					          cameraPreviews={relevantPreviews || []}
 | 
				
			||||||
 | 
					          startTime={playbackStart}
 | 
				
			||||||
          onControllerReady={(controller) => {
 | 
					          onControllerReady={(controller) => {
 | 
				
			||||||
            controllerRef.current = controller;
 | 
					            controllerRef.current = controller;
 | 
				
			||||||
            setPlayerReady(true);
 | 
					            setPlayerReady(true);
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user