mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-08-18 13:47:20 +02: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