Handle recordings buffering

This commit is contained in:
Nicolas Mowen 2024-07-17 06:36:27 -06:00
parent 371fd84a64
commit dd1d1f2c22
3 changed files with 47 additions and 9 deletions

View File

@ -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)

View File

@ -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 && (

View File

@ -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;