mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	Convert preview player to use html5 video (#10016)
* Convert preview player to use html5 * Cleanup * Increase padding and use constant * Firefox doesn't support high fps either * Cleanup * no need to special case firefox
This commit is contained in:
		
							parent
							
								
									7ccc7fb393
								
							
						
					
					
						commit
						4a7c159a44
					
				@ -1,7 +1,5 @@
 | 
				
			|||||||
import VideoPlayer from "./VideoPlayer";
 | 
					 | 
				
			||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
 | 
					import { useCallback, useEffect, useMemo, useRef, useState } from "react";
 | 
				
			||||||
import { useApiHost } from "@/api";
 | 
					import { useApiHost } from "@/api";
 | 
				
			||||||
import Player from "video.js/dist/types/player";
 | 
					 | 
				
			||||||
import { formatUnixTimestampToDateTime, isCurrentHour } from "@/utils/dateUtil";
 | 
					import { formatUnixTimestampToDateTime, isCurrentHour } from "@/utils/dateUtil";
 | 
				
			||||||
import { ReviewSegment } from "@/types/review";
 | 
					import { ReviewSegment } from "@/types/review";
 | 
				
			||||||
import { Slider } from "../ui/slider";
 | 
					import { Slider } from "../ui/slider";
 | 
				
			||||||
@ -169,6 +167,7 @@ export default function PreviewThumbnailPlayer({
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const PREVIEW_PADDING = 16;
 | 
				
			||||||
type PreviewContentProps = {
 | 
					type PreviewContentProps = {
 | 
				
			||||||
  review: ReviewSegment;
 | 
					  review: ReviewSegment;
 | 
				
			||||||
  relevantPreview: Preview | undefined;
 | 
					  relevantPreview: Preview | undefined;
 | 
				
			||||||
@ -181,15 +180,69 @@ function PreviewContent({
 | 
				
			|||||||
  setProgress,
 | 
					  setProgress,
 | 
				
			||||||
  setReviewed,
 | 
					  setReviewed,
 | 
				
			||||||
}: PreviewContentProps) {
 | 
					}: PreviewContentProps) {
 | 
				
			||||||
  const playerRef = useRef<Player | null>(null);
 | 
					  const playerRef = useRef<HTMLVideoElement | null>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // keep track of playback state
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const playerStartTime = useMemo(() => {
 | 
					  const playerStartTime = useMemo(() => {
 | 
				
			||||||
    if (!relevantPreview) {
 | 
					    if (!relevantPreview) {
 | 
				
			||||||
      return 0;
 | 
					      return 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // start with a bit of padding
 | 
					    // start with a bit of padding
 | 
				
			||||||
    return Math.max(0, review.start_time - relevantPreview.start - 8);
 | 
					    return Math.max(0, review.start_time - relevantPreview.start - PREVIEW_PADDING);
 | 
				
			||||||
  }, []);
 | 
					  }, []);
 | 
				
			||||||
 | 
					  const [lastPercent, setLastPercent] = useState(0.0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // initialize player correctly
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    if (!playerRef.current) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (isSafari) {
 | 
				
			||||||
 | 
					      playerRef.current.pause();
 | 
				
			||||||
 | 
					      setManualPlayback(true);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      playerRef.current.currentTime = playerStartTime;
 | 
				
			||||||
 | 
					      playerRef.current.playbackRate = 8;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [playerRef]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // time progress update
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const onProgress = useCallback(() => {
 | 
				
			||||||
 | 
					    if (!setProgress) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const playerProgress =
 | 
				
			||||||
 | 
					      (playerRef.current?.currentTime || 0) - playerStartTime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // end with a bit of padding
 | 
				
			||||||
 | 
					    const playerDuration = review.end_time - review.start_time + PREVIEW_PADDING;
 | 
				
			||||||
 | 
					    const playerPercent = (playerProgress / playerDuration) * 100;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					      setReviewed &&
 | 
				
			||||||
 | 
					      !review.has_been_reviewed &&
 | 
				
			||||||
 | 
					      lastPercent < 50 &&
 | 
				
			||||||
 | 
					      playerPercent > 50
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      setReviewed();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setLastPercent(playerPercent);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (playerPercent > 100) {
 | 
				
			||||||
 | 
					      playerRef.current?.pause();
 | 
				
			||||||
 | 
					      setManualPlayback(false);
 | 
				
			||||||
 | 
					      setProgress(100.0);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      setProgress(playerPercent);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [setProgress, lastPercent]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // manual playback
 | 
					  // manual playback
 | 
				
			||||||
  // safari is incapable of playing at a speed > 2x
 | 
					  // safari is incapable of playing at a speed > 2x
 | 
				
			||||||
@ -204,7 +257,7 @@ function PreviewContent({
 | 
				
			|||||||
    let counter = 0;
 | 
					    let counter = 0;
 | 
				
			||||||
    const intervalId: NodeJS.Timeout = setInterval(() => {
 | 
					    const intervalId: NodeJS.Timeout = setInterval(() => {
 | 
				
			||||||
      if (playerRef.current) {
 | 
					      if (playerRef.current) {
 | 
				
			||||||
        playerRef.current.currentTime(playerStartTime + counter);
 | 
					        playerRef.current.currentTime = playerStartTime + counter;
 | 
				
			||||||
        counter += 1;
 | 
					        counter += 1;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }, 125);
 | 
					    }, 125);
 | 
				
			||||||
@ -215,77 +268,17 @@ function PreviewContent({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  if (relevantPreview) {
 | 
					  if (relevantPreview) {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <VideoPlayer
 | 
					      <video
 | 
				
			||||||
        options={{
 | 
					        ref={playerRef}
 | 
				
			||||||
          preload: "auto",
 | 
					        className="w-full h-full aspect-video bg-black"
 | 
				
			||||||
          autoplay: true,
 | 
					        autoPlay
 | 
				
			||||||
          controls: false,
 | 
					        playsInline
 | 
				
			||||||
          muted: true,
 | 
					        preload="auto"
 | 
				
			||||||
          fluid: true,
 | 
					        muted
 | 
				
			||||||
          aspectRatio: "16:9",
 | 
					        onTimeUpdate={onProgress}
 | 
				
			||||||
          loadingSpinner: false,
 | 
					      >
 | 
				
			||||||
          sources: relevantPreview
 | 
					        <source src={relevantPreview.src} type={relevantPreview.type} />
 | 
				
			||||||
            ? [
 | 
					      </video>
 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                  src: `${relevantPreview.src}`,
 | 
					 | 
				
			||||||
                  type: "video/mp4",
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
              ]
 | 
					 | 
				
			||||||
            : [],
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
        seekOptions={{}}
 | 
					 | 
				
			||||||
        onReady={(player) => {
 | 
					 | 
				
			||||||
          playerRef.current = player;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          if (!relevantPreview) {
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          if (isSafari) {
 | 
					 | 
				
			||||||
            player.pause();
 | 
					 | 
				
			||||||
            setManualPlayback(true);
 | 
					 | 
				
			||||||
          } else {
 | 
					 | 
				
			||||||
            player.currentTime(playerStartTime);
 | 
					 | 
				
			||||||
            player.playbackRate(8);
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          let lastPercent = 0;
 | 
					 | 
				
			||||||
          player.on("timeupdate", () => {
 | 
					 | 
				
			||||||
            if (!setProgress) {
 | 
					 | 
				
			||||||
              return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const playerProgress =
 | 
					 | 
				
			||||||
              (player.currentTime() || 0) - playerStartTime;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // end with a bit of padding
 | 
					 | 
				
			||||||
            const playerDuration = review.end_time - review.start_time + 8;
 | 
					 | 
				
			||||||
            const playerPercent = (playerProgress / playerDuration) * 100;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (
 | 
					 | 
				
			||||||
              setReviewed &&
 | 
					 | 
				
			||||||
              !review.has_been_reviewed &&
 | 
					 | 
				
			||||||
              lastPercent < 50 &&
 | 
					 | 
				
			||||||
              playerPercent > 50
 | 
					 | 
				
			||||||
            ) {
 | 
					 | 
				
			||||||
              setReviewed();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            lastPercent = playerPercent;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (playerPercent > 100) {
 | 
					 | 
				
			||||||
              playerRef.current?.pause();
 | 
					 | 
				
			||||||
              setManualPlayback(false);
 | 
					 | 
				
			||||||
              setProgress(100.0);
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
              setProgress(playerPercent);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
        onDispose={() => {
 | 
					 | 
				
			||||||
          playerRef.current = null;
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
      />
 | 
					 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  } else if (isCurrentHour(review.start_time)) {
 | 
					  } else if (isCurrentHour(review.start_time)) {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
@ -373,10 +366,6 @@ function PreviewContextItems({
 | 
				
			|||||||
  setReviewed,
 | 
					  setReviewed,
 | 
				
			||||||
}: PreviewContextItemsProps) {
 | 
					}: PreviewContextItemsProps) {
 | 
				
			||||||
  const exportReview = useCallback(() => {
 | 
					  const exportReview = useCallback(() => {
 | 
				
			||||||
    console.log(
 | 
					 | 
				
			||||||
      "trying to export to " +
 | 
					 | 
				
			||||||
        `export/${review.camera}/start/${review.start_time}/end/${review.end_time}`
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    axios.post(
 | 
					    axios.post(
 | 
				
			||||||
      `export/${review.camera}/start/${review.start_time}/end/${review.end_time}`,
 | 
					      `export/${review.camera}/start/${review.start_time}/end/${review.end_time}`,
 | 
				
			||||||
      { playback: "realtime" }
 | 
					      { playback: "realtime" }
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user