mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-13 06:35:24 +03:00
Handle recordings buffering
This commit is contained in:
parent
371fd84a64
commit
dd1d1f2c22
@ -17,7 +17,7 @@ import { toast } from "sonner";
|
|||||||
import { useOverlayState } from "@/hooks/use-overlay-state";
|
import { useOverlayState } from "@/hooks/use-overlay-state";
|
||||||
import { usePersistence } from "@/hooks/use-persistence";
|
import { usePersistence } from "@/hooks/use-persistence";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { ASPECT_VERTICAL_LAYOUT } from "@/types/record";
|
import { ASPECT_VERTICAL_LAYOUT, RecordingPlayerError } from "@/types/record";
|
||||||
|
|
||||||
// Android native hls does not seek correctly
|
// Android native hls does not seek correctly
|
||||||
const USE_NATIVE_HLS = !isAndroid;
|
const USE_NATIVE_HLS = !isAndroid;
|
||||||
@ -29,6 +29,7 @@ const unsupportedErrorCodes = [
|
|||||||
|
|
||||||
type HlsVideoPlayerProps = {
|
type HlsVideoPlayerProps = {
|
||||||
videoRef: MutableRefObject<HTMLVideoElement | null>;
|
videoRef: MutableRefObject<HTMLVideoElement | null>;
|
||||||
|
containerRef?: React.MutableRefObject<HTMLDivElement | null>;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
currentSource: string;
|
currentSource: string;
|
||||||
hotKeys: boolean;
|
hotKeys: boolean;
|
||||||
@ -40,10 +41,11 @@ type HlsVideoPlayerProps = {
|
|||||||
setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
|
setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
|
||||||
onUploadFrame?: (playTime: number) => Promise<AxiosResponse> | undefined;
|
onUploadFrame?: (playTime: number) => Promise<AxiosResponse> | undefined;
|
||||||
toggleFullscreen?: () => void;
|
toggleFullscreen?: () => void;
|
||||||
containerRef?: React.MutableRefObject<HTMLDivElement | null>;
|
onError?: (error: RecordingPlayerError) => void;
|
||||||
};
|
};
|
||||||
export default function HlsVideoPlayer({
|
export default function HlsVideoPlayer({
|
||||||
videoRef,
|
videoRef,
|
||||||
|
containerRef,
|
||||||
visible,
|
visible,
|
||||||
currentSource,
|
currentSource,
|
||||||
hotKeys,
|
hotKeys,
|
||||||
@ -55,7 +57,7 @@ export default function HlsVideoPlayer({
|
|||||||
setFullResolution,
|
setFullResolution,
|
||||||
onUploadFrame,
|
onUploadFrame,
|
||||||
toggleFullscreen,
|
toggleFullscreen,
|
||||||
containerRef,
|
onError,
|
||||||
}: HlsVideoPlayerProps) {
|
}: HlsVideoPlayerProps) {
|
||||||
const { data: config } = useSWR<FrigateConfig>("config");
|
const { data: config } = useSWR<FrigateConfig>("config");
|
||||||
|
|
||||||
@ -64,6 +66,7 @@ export default function HlsVideoPlayer({
|
|||||||
const hlsRef = useRef<Hls>();
|
const hlsRef = useRef<Hls>();
|
||||||
const [useHlsCompat, setUseHlsCompat] = useState(false);
|
const [useHlsCompat, setUseHlsCompat] = useState(false);
|
||||||
const [loadedMetadata, setLoadedMetadata] = useState(false);
|
const [loadedMetadata, setLoadedMetadata] = useState(false);
|
||||||
|
const [bufferTimeout, setBufferTimeout] = useState<NodeJS.Timeout>();
|
||||||
|
|
||||||
const handleLoadedMetadata = useCallback(() => {
|
const handleLoadedMetadata = useCallback(() => {
|
||||||
setLoadedMetadata(true);
|
setLoadedMetadata(true);
|
||||||
@ -270,6 +273,29 @@ export default function HlsVideoPlayer({
|
|||||||
clearTimeout(mobileCtrlTimeout);
|
clearTimeout(mobileCtrlTimeout);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
onProgress={() => {
|
||||||
|
if (onError != undefined) {
|
||||||
|
if (videoRef.current?.paused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bufferTimeout) {
|
||||||
|
clearTimeout(bufferTimeout);
|
||||||
|
setBufferTimeout(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
setBufferTimeout(
|
||||||
|
setTimeout(() => {
|
||||||
|
if (
|
||||||
|
document.visibilityState === "visible" &&
|
||||||
|
videoRef.current
|
||||||
|
) {
|
||||||
|
onError("stalled");
|
||||||
|
}
|
||||||
|
}, 3000),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
onTimeUpdate={() =>
|
onTimeUpdate={() =>
|
||||||
onTimeUpdate && videoRef.current
|
onTimeUpdate && videoRef.current
|
||||||
? onTimeUpdate(videoRef.current.currentTime)
|
? onTimeUpdate(videoRef.current.currentTime)
|
||||||
|
|||||||
@ -91,6 +91,7 @@ export default function DynamicVideoPlayer({
|
|||||||
// initial state
|
// initial state
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [isBuffering, setIsBuffering] = useState(false);
|
||||||
const [loadingTimeout, setLoadingTimeout] = useState<NodeJS.Timeout>();
|
const [loadingTimeout, setLoadingTimeout] = useState<NodeJS.Timeout>();
|
||||||
const [source, setSource] = useState(
|
const [source, setSource] = useState(
|
||||||
`${apiHost}vod/${camera}/start/${timeRange.after}/end/${timeRange.before}/master.m3u8`,
|
`${apiHost}vod/${camera}/start/${timeRange.after}/end/${timeRange.before}/master.m3u8`,
|
||||||
@ -130,9 +131,13 @@ export default function DynamicVideoPlayer({
|
|||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isBuffering) {
|
||||||
|
setIsBuffering(false);
|
||||||
|
}
|
||||||
|
|
||||||
onTimestampUpdate(controller.getProgress(time));
|
onTimestampUpdate(controller.getProgress(time));
|
||||||
},
|
},
|
||||||
[controller, onTimestampUpdate, isScrubbing, isLoading],
|
[controller, onTimestampUpdate, isBuffering, isLoading, isScrubbing],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onUploadFrameToPlus = useCallback(
|
const onUploadFrameToPlus = useCallback(
|
||||||
@ -188,6 +193,7 @@ export default function DynamicVideoPlayer({
|
|||||||
<>
|
<>
|
||||||
<HlsVideoPlayer
|
<HlsVideoPlayer
|
||||||
videoRef={playerRef}
|
videoRef={playerRef}
|
||||||
|
containerRef={containerRef}
|
||||||
visible={!(isScrubbing || isLoading)}
|
visible={!(isScrubbing || isLoading)}
|
||||||
currentSource={source}
|
currentSource={source}
|
||||||
hotKeys={hotKeys}
|
hotKeys={hotKeys}
|
||||||
@ -209,7 +215,11 @@ export default function DynamicVideoPlayer({
|
|||||||
setFullResolution={setFullResolution}
|
setFullResolution={setFullResolution}
|
||||||
onUploadFrame={onUploadFrameToPlus}
|
onUploadFrame={onUploadFrameToPlus}
|
||||||
toggleFullscreen={toggleFullscreen}
|
toggleFullscreen={toggleFullscreen}
|
||||||
containerRef={containerRef}
|
onError={(error) => {
|
||||||
|
if (error == "stalled" && !isScrubbing) {
|
||||||
|
setIsBuffering(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<PreviewPlayer
|
<PreviewPlayer
|
||||||
className={cn(
|
className={cn(
|
||||||
@ -221,11 +231,11 @@ export default function DynamicVideoPlayer({
|
|||||||
cameraPreviews={cameraPreviews}
|
cameraPreviews={cameraPreviews}
|
||||||
startTime={startTimestamp}
|
startTime={startTimestamp}
|
||||||
isScrubbing={isScrubbing}
|
isScrubbing={isScrubbing}
|
||||||
onControllerReady={(previewController) => {
|
onControllerReady={(previewController) =>
|
||||||
setPreviewController(previewController);
|
setPreviewController(previewController)
|
||||||
}}
|
}
|
||||||
/>
|
/>
|
||||||
{!isScrubbing && isLoading && !noRecording && (
|
{!isScrubbing && (isLoading || isBuffering) && !noRecording && (
|
||||||
<ActivityIndicator className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />
|
<ActivityIndicator className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />
|
||||||
)}
|
)}
|
||||||
{!isScrubbing && noRecording && (
|
{!isScrubbing && noRecording && (
|
||||||
|
|||||||
@ -39,5 +39,7 @@ export type RecordingStartingPoint = {
|
|||||||
severity: ReviewSeverity;
|
severity: ReviewSeverity;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type RecordingPlayerError = "stalled" | "startup";
|
||||||
|
|
||||||
export const ASPECT_VERTICAL_LAYOUT = 1.5;
|
export const ASPECT_VERTICAL_LAYOUT = 1.5;
|
||||||
export const ASPECT_WIDE_LAYOUT = 2;
|
export const ASPECT_WIDE_LAYOUT = 2;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user