Compare commits

..

2 Commits

Author SHA1 Message Date
Josh Hawkins
130dc76a01 only redirect to login page once on 401
attempt to fix ios pwa safari redirect storm
2025-12-03 21:59:57 -06:00
Josh Hawkins
87e67d22e2 adjust HLS gap controller params
defaults to false, should help to recover from hangs and stalling in tracking details videos on chrome
2025-12-03 21:44:02 -06:00
3 changed files with 31 additions and 5 deletions

View File

@ -6,6 +6,10 @@ import { ReactNode } from "react";
axios.defaults.baseURL = `${baseUrl}api/`; axios.defaults.baseURL = `${baseUrl}api/`;
// Module-level flag to prevent multiple simultaneous redirects
// (eg, when multiple SWR queries fail with 401 at once)
let isRedirectingToLogin = false;
type ApiProviderType = { type ApiProviderType = {
children?: ReactNode; children?: ReactNode;
options?: Record<string, unknown>; options?: Record<string, unknown>;
@ -31,7 +35,8 @@ export function ApiProvider({ children, options }: ApiProviderType) {
) { ) {
// redirect to the login page if not already there // redirect to the login page if not already there
const loginPage = error.response.headers.get("location") ?? "login"; const loginPage = error.response.headers.get("location") ?? "login";
if (window.location.href !== loginPage) { if (window.location.href !== loginPage && !isRedirectingToLogin) {
isRedirectingToLogin = true;
window.location.href = loginPage; window.location.href = loginPage;
} }
} }

View File

@ -559,6 +559,7 @@ export function TrackingDetails({
isDetailMode={true} isDetailMode={true}
camera={event.camera} camera={event.camera}
currentTimeOverride={currentTime} currentTimeOverride={currentTime}
enableGapControllerRecovery={true}
/> />
{isVideoLoading && ( {isVideoLoading && (
<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" />

View File

@ -5,7 +5,7 @@ import {
useRef, useRef,
useState, useState,
} from "react"; } from "react";
import Hls from "hls.js"; import Hls, { HlsConfig } from "hls.js";
import { isDesktop, isMobile } from "react-device-detect"; import { isDesktop, isMobile } from "react-device-detect";
import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch"; import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
import VideoControls from "./VideoControls"; import VideoControls from "./VideoControls";
@ -57,6 +57,7 @@ type HlsVideoPlayerProps = {
isDetailMode?: boolean; isDetailMode?: boolean;
camera?: string; camera?: string;
currentTimeOverride?: number; currentTimeOverride?: number;
enableGapControllerRecovery?: boolean;
}; };
export default function HlsVideoPlayer({ export default function HlsVideoPlayer({
@ -81,6 +82,7 @@ export default function HlsVideoPlayer({
isDetailMode = false, isDetailMode = false,
camera, camera,
currentTimeOverride, currentTimeOverride,
enableGapControllerRecovery = false,
}: HlsVideoPlayerProps) { }: HlsVideoPlayerProps) {
const { t } = useTranslation("components/player"); const { t } = useTranslation("components/player");
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
@ -170,11 +172,23 @@ export default function HlsVideoPlayer({
return; return;
} }
hlsRef.current = new Hls({ // Base HLS configuration
const baseConfig: Partial<HlsConfig> = {
maxBufferLength: 10, maxBufferLength: 10,
maxBufferSize: 20 * 1000 * 1000, maxBufferSize: 20 * 1000 * 1000,
startPosition: currentSource.startPosition, startPosition: currentSource.startPosition,
}); };
const hlsConfig = { ...baseConfig };
if (enableGapControllerRecovery) {
hlsConfig.highBufferWatchdogPeriod = 1; // Check for stalls every 1 second (default: 3)
hlsConfig.nudgeOffset = 0.2; // Nudge playhead forward 0.2s when stalled (default: 0.1)
hlsConfig.nudgeMaxRetry = 5; // Try up to 5 nudges before giving up (default: 3)
hlsConfig.maxBufferHole = 0.5; // Tolerate up to 0.5s gaps between fragments (default: 0.1)
}
hlsRef.current = new Hls(hlsConfig);
hlsRef.current.attachMedia(videoRef.current); hlsRef.current.attachMedia(videoRef.current);
hlsRef.current.loadSource(currentSource.playlist); hlsRef.current.loadSource(currentSource.playlist);
videoRef.current.playbackRate = currentPlaybackRate; videoRef.current.playbackRate = currentPlaybackRate;
@ -187,7 +201,13 @@ export default function HlsVideoPlayer({
hlsRef.current.destroy(); hlsRef.current.destroy();
} }
}; };
}, [videoRef, hlsRef, useHlsCompat, currentSource]); }, [
videoRef,
hlsRef,
useHlsCompat,
currentSource,
enableGapControllerRecovery,
]);
// state handling // state handling