mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
Handle error when live view stalls (#11665)
* Handle error when live view stalls * Manually calculate buffer timeout * Formatting
This commit is contained in:
parent
a3d116e70e
commit
758df09da3
@ -8,7 +8,11 @@ import JSMpegPlayer from "./JSMpegPlayer";
|
|||||||
import { MdCircle } from "react-icons/md";
|
import { MdCircle } from "react-icons/md";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
||||||
import { useCameraActivity } from "@/hooks/use-camera-activity";
|
import { useCameraActivity } from "@/hooks/use-camera-activity";
|
||||||
import { LivePlayerMode, VideoResolutionType } from "@/types/live";
|
import {
|
||||||
|
LivePlayerError,
|
||||||
|
LivePlayerMode,
|
||||||
|
VideoResolutionType,
|
||||||
|
} from "@/types/live";
|
||||||
import useCameraLiveMode from "@/hooks/use-camera-live-mode";
|
import useCameraLiveMode from "@/hooks/use-camera-live-mode";
|
||||||
import { getIconForLabel } from "@/utils/iconUtil";
|
import { getIconForLabel } from "@/utils/iconUtil";
|
||||||
import Chip from "../indicators/Chip";
|
import Chip from "../indicators/Chip";
|
||||||
@ -30,6 +34,7 @@ type LivePlayerProps = {
|
|||||||
autoLive?: boolean;
|
autoLive?: boolean;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
|
setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
|
||||||
|
onError?: (error: LivePlayerError) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function LivePlayer({
|
export default function LivePlayer({
|
||||||
@ -47,6 +52,7 @@ export default function LivePlayer({
|
|||||||
autoLive = true,
|
autoLive = true,
|
||||||
onClick,
|
onClick,
|
||||||
setFullResolution,
|
setFullResolution,
|
||||||
|
onError,
|
||||||
}: LivePlayerProps) {
|
}: LivePlayerProps) {
|
||||||
// camera activity
|
// camera activity
|
||||||
|
|
||||||
@ -145,6 +151,7 @@ export default function LivePlayer({
|
|||||||
onPlaying={() => setLiveReady(true)}
|
onPlaying={() => setLiveReady(true)}
|
||||||
pip={pip}
|
pip={pip}
|
||||||
setFullResolution={setFullResolution}
|
setFullResolution={setFullResolution}
|
||||||
|
onError={onError}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { baseUrl } from "@/api/baseUrl";
|
import { baseUrl } from "@/api/baseUrl";
|
||||||
import { VideoResolutionType } from "@/types/live";
|
import { LivePlayerError, VideoResolutionType } from "@/types/live";
|
||||||
import {
|
import {
|
||||||
SetStateAction,
|
SetStateAction,
|
||||||
useCallback,
|
useCallback,
|
||||||
@ -17,6 +17,7 @@ type MSEPlayerProps = {
|
|||||||
pip?: boolean;
|
pip?: boolean;
|
||||||
onPlaying?: () => void;
|
onPlaying?: () => void;
|
||||||
setFullResolution?: React.Dispatch<SetStateAction<VideoResolutionType>>;
|
setFullResolution?: React.Dispatch<SetStateAction<VideoResolutionType>>;
|
||||||
|
onError?: (error: LivePlayerError) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
function MSEPlayer({
|
function MSEPlayer({
|
||||||
@ -27,6 +28,7 @@ function MSEPlayer({
|
|||||||
pip = false,
|
pip = false,
|
||||||
onPlaying,
|
onPlaying,
|
||||||
setFullResolution,
|
setFullResolution,
|
||||||
|
onError,
|
||||||
}: MSEPlayerProps) {
|
}: MSEPlayerProps) {
|
||||||
const RECONNECT_TIMEOUT: number = 30000;
|
const RECONNECT_TIMEOUT: number = 30000;
|
||||||
|
|
||||||
@ -45,6 +47,7 @@ function MSEPlayer({
|
|||||||
|
|
||||||
const [wsState, setWsState] = useState<number>(WebSocket.CLOSED);
|
const [wsState, setWsState] = useState<number>(WebSocket.CLOSED);
|
||||||
const [connectTS, setConnectTS] = useState<number>(0);
|
const [connectTS, setConnectTS] = useState<number>(0);
|
||||||
|
const [bufferTimeout, setBufferTimeout] = useState<NodeJS.Timeout>();
|
||||||
|
|
||||||
const videoRef = useRef<HTMLVideoElement>(null);
|
const videoRef = useRef<HTMLVideoElement>(null);
|
||||||
const wsRef = useRef<WebSocket | null>(null);
|
const wsRef = useRef<WebSocket | null>(null);
|
||||||
@ -308,7 +311,34 @@ function MSEPlayer({
|
|||||||
onPlaying?.();
|
onPlaying?.();
|
||||||
}}
|
}}
|
||||||
muted={!audioEnabled}
|
muted={!audioEnabled}
|
||||||
onError={() => {
|
onProgress={
|
||||||
|
onError != undefined
|
||||||
|
? () => {
|
||||||
|
if (videoRef.current?.paused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bufferTimeout) {
|
||||||
|
clearTimeout(bufferTimeout);
|
||||||
|
setBufferTimeout(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
setBufferTimeout(
|
||||||
|
setTimeout(() => {
|
||||||
|
onError("stalled");
|
||||||
|
}, 3000),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
onError={(e) => {
|
||||||
|
if (
|
||||||
|
// @ts-expect-error code does exist
|
||||||
|
e.target.error.code == MediaError.MEDIA_ERR_NETWORK
|
||||||
|
) {
|
||||||
|
onError?.("startup");
|
||||||
|
}
|
||||||
|
|
||||||
if (wsRef.current) {
|
if (wsRef.current) {
|
||||||
wsRef.current.close();
|
wsRef.current.close();
|
||||||
wsRef.current = null;
|
wsRef.current = null;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { baseUrl } from "@/api/baseUrl";
|
import { baseUrl } from "@/api/baseUrl";
|
||||||
|
import { LivePlayerError } from "@/types/live";
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
|
||||||
type WebRtcPlayerProps = {
|
type WebRtcPlayerProps = {
|
||||||
@ -10,6 +11,7 @@ type WebRtcPlayerProps = {
|
|||||||
iOSCompatFullScreen?: boolean; // ios doesn't support fullscreen divs so we must support the video element
|
iOSCompatFullScreen?: boolean; // ios doesn't support fullscreen divs so we must support the video element
|
||||||
pip?: boolean;
|
pip?: boolean;
|
||||||
onPlaying?: () => void;
|
onPlaying?: () => void;
|
||||||
|
onError?: (error: LivePlayerError) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function WebRtcPlayer({
|
export default function WebRtcPlayer({
|
||||||
@ -21,6 +23,7 @@ export default function WebRtcPlayer({
|
|||||||
iOSCompatFullScreen = false,
|
iOSCompatFullScreen = false,
|
||||||
pip = false,
|
pip = false,
|
||||||
onPlaying,
|
onPlaying,
|
||||||
|
onError,
|
||||||
}: WebRtcPlayerProps) {
|
}: WebRtcPlayerProps) {
|
||||||
// metadata
|
// metadata
|
||||||
|
|
||||||
@ -32,6 +35,7 @@ export default function WebRtcPlayer({
|
|||||||
|
|
||||||
const pcRef = useRef<RTCPeerConnection | undefined>();
|
const pcRef = useRef<RTCPeerConnection | undefined>();
|
||||||
const videoRef = useRef<HTMLVideoElement | null>(null);
|
const videoRef = useRef<HTMLVideoElement | null>(null);
|
||||||
|
const [bufferTimeout, setBufferTimeout] = useState<NodeJS.Timeout>();
|
||||||
|
|
||||||
const PeerConnection = useCallback(
|
const PeerConnection = useCallback(
|
||||||
async (media: string) => {
|
async (media: string) => {
|
||||||
@ -198,11 +202,39 @@ export default function WebRtcPlayer({
|
|||||||
playsInline
|
playsInline
|
||||||
muted={!audioEnabled}
|
muted={!audioEnabled}
|
||||||
onLoadedData={onPlaying}
|
onLoadedData={onPlaying}
|
||||||
|
onProgress={
|
||||||
|
onError != undefined
|
||||||
|
? () => {
|
||||||
|
if (videoRef.current?.paused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bufferTimeout) {
|
||||||
|
clearTimeout(bufferTimeout);
|
||||||
|
setBufferTimeout(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
setBufferTimeout(
|
||||||
|
setTimeout(() => {
|
||||||
|
onError("stalled");
|
||||||
|
}, 3000),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
onClick={
|
onClick={
|
||||||
iOSCompatFullScreen
|
iOSCompatFullScreen
|
||||||
? () => setiOSCompatControls(!iOSCompatControls)
|
? () => setiOSCompatControls(!iOSCompatControls)
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
onError={(e) => {
|
||||||
|
if (
|
||||||
|
// @ts-expect-error code does exist
|
||||||
|
e.target.error.code == MediaError.MEDIA_ERR_NETWORK
|
||||||
|
) {
|
||||||
|
onError?.("startup");
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -30,3 +30,5 @@ export type LiveStreamMetadata = {
|
|||||||
producers: LiveProducerMetadata[];
|
producers: LiveProducerMetadata[];
|
||||||
consumers: LiveConsumerMetadata[];
|
consumers: LiveConsumerMetadata[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type LivePlayerError = "stalled" | "startup";
|
||||||
|
@ -191,6 +191,7 @@ export default function LiveCameraView({
|
|||||||
const [audio, setAudio] = useState(false);
|
const [audio, setAudio] = useState(false);
|
||||||
const [mic, setMic] = useState(false);
|
const [mic, setMic] = useState(false);
|
||||||
const [pip, setPip] = useState(false);
|
const [pip, setPip] = useState(false);
|
||||||
|
const [lowBandwidth, setLowBandwidth] = useState(false);
|
||||||
|
|
||||||
const [fullResolution, setFullResolution] = useState<VideoResolutionType>({
|
const [fullResolution, setFullResolution] = useState<VideoResolutionType>({
|
||||||
width: 0,
|
width: 0,
|
||||||
@ -202,8 +203,14 @@ export default function LiveCameraView({
|
|||||||
return "webrtc";
|
return "webrtc";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lowBandwidth) {
|
||||||
|
return "jsmpeg";
|
||||||
|
}
|
||||||
|
|
||||||
return "mse";
|
return "mse";
|
||||||
}, [mic]);
|
}, [lowBandwidth, mic]);
|
||||||
|
|
||||||
|
// layout state
|
||||||
|
|
||||||
const windowAspectRatio = useMemo(() => {
|
const windowAspectRatio = useMemo(() => {
|
||||||
return windowWidth / windowHeight;
|
return windowWidth / windowHeight;
|
||||||
@ -419,6 +426,7 @@ export default function LiveCameraView({
|
|||||||
pip={pip}
|
pip={pip}
|
||||||
setFullResolution={setFullResolution}
|
setFullResolution={setFullResolution}
|
||||||
containerRef={containerRef}
|
containerRef={containerRef}
|
||||||
|
onError={() => setLowBandwidth(true)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{camera.onvif.host != "" && (
|
{camera.onvif.host != "" && (
|
||||||
|
Loading…
Reference in New Issue
Block a user