Live player fixes (#13143)

* Jump to live when exceeding buffer time threshold in MSE player

* clean up

* Try adjusting playback rate instead of jumping to live

* clean up

* fallback to webrtc if enabled before jsmpeg

* baseline

* clean up

* remove comments

* adaptive playback rate and intelligent switching improvements

* increase logging and reset live mode after camera is no longer active on dashboard only

* jump to live on safari/iOS

* clean up

* clean up

* refactor camera live mode hook

* remove key listener

* resolve conflicts
This commit is contained in:
Josh Hawkins
2024-08-17 13:16:48 -05:00
committed by GitHub
parent 4133e454c4
commit 8c23ede683
7 changed files with 228 additions and 120 deletions

View File

@@ -41,6 +41,7 @@ import {
TooltipContent,
} from "@/components/ui/tooltip";
import { Toaster } from "@/components/ui/sonner";
import useCameraLiveMode from "@/hooks/use-camera-live-mode";
type DraggableGridLayoutProps = {
cameras: CameraConfig[];
@@ -75,36 +76,8 @@ export default function DraggableGridLayout({
// preferred live modes per camera
const [preferredLiveModes, setPreferredLiveModes] = useState<{
[key: string]: LivePlayerMode;
}>({});
useEffect(() => {
if (!cameras) return;
const mseSupported =
"MediaSource" in window || "ManagedMediaSource" in window;
const newPreferredLiveModes = cameras.reduce(
(acc, camera) => {
const isRestreamed =
config &&
Object.keys(config.go2rtc.streams || {}).includes(
camera.live.stream_name,
);
if (!mseSupported) {
acc[camera.name] = isRestreamed ? "webrtc" : "jsmpeg";
} else {
acc[camera.name] = isRestreamed ? "mse" : "jsmpeg";
}
return acc;
},
{} as { [key: string]: LivePlayerMode },
);
setPreferredLiveModes(newPreferredLiveModes);
}, [cameras, config, windowVisible]);
const { preferredLiveModes, setPreferredLiveModes, resetPreferredLiveMode } =
useCameraLiveMode(cameras, windowVisible);
const ResponsiveGridLayout = useMemo(() => WidthProvider(Responsive), []);
@@ -477,6 +450,7 @@ export default function DraggableGridLayout({
return newModes;
});
}}
onResetLiveMode={() => resetPreferredLiveMode(camera.name)}
>
{isEditMode && showCircles && <CornerCircles />}
</LivePlayerGridItem>
@@ -635,6 +609,7 @@ type LivePlayerGridItemProps = {
preferredLiveMode: LivePlayerMode;
onClick: () => void;
onError: (e: LivePlayerError) => void;
onResetLiveMode: () => void;
};
const LivePlayerGridItem = React.forwardRef<
@@ -655,6 +630,7 @@ const LivePlayerGridItem = React.forwardRef<
preferredLiveMode,
onClick,
onError,
onResetLiveMode,
...props
},
ref,
@@ -676,6 +652,7 @@ const LivePlayerGridItem = React.forwardRef<
preferredLiveMode={preferredLiveMode}
onClick={onClick}
onError={onError}
onResetLiveMode={onResetLiveMode}
containerRef={ref as React.RefObject<HTMLDivElement>}
/>
{children}

View File

@@ -227,6 +227,10 @@ export default function LiveCameraView({
return "webrtc";
}
if (!isRestreamed) {
return "jsmpeg";
}
return "mse";
}, [lowBandwidth, mic, webRTC, isRestreamed]);
@@ -286,14 +290,23 @@ export default function LiveCameraView({
}
}, [fullscreen, isPortrait, cameraAspectRatio, containerAspectRatio]);
const handleError = useCallback((e: LivePlayerError) => {
if (e == "mse-decode") {
setWebRTC(true);
} else {
setWebRTC(false);
setLowBandwidth(true);
}
}, []);
const handleError = useCallback(
(e: LivePlayerError) => {
if (e) {
if (
!webRTC &&
config &&
config.go2rtc?.webrtc?.candidates?.length > 0
) {
setWebRTC(true);
} else {
setWebRTC(false);
setLowBandwidth(true);
}
}
},
[config, webRTC],
);
return (
<TransformWrapper minScale={1.0} wheel={{ smoothStep: 0.005 }}>

View File

@@ -28,8 +28,9 @@ import DraggableGridLayout from "./DraggableGridLayout";
import { IoClose } from "react-icons/io5";
import { LuLayoutDashboard } from "react-icons/lu";
import { cn } from "@/lib/utils";
import { LivePlayerError, LivePlayerMode } from "@/types/live";
import { LivePlayerError } from "@/types/live";
import { FaCompress, FaExpand } from "react-icons/fa";
import useCameraLiveMode from "@/hooks/use-camera-live-mode";
import { useResizeObserver } from "@/hooks/resize-observer";
type LiveDashboardViewProps = {
@@ -129,9 +130,6 @@ export default function LiveDashboardView({
// camera live views
const [autoLiveView] = usePersistence("autoLiveView", true);
const [preferredLiveModes, setPreferredLiveModes] = useState<{
[key: string]: LivePlayerMode;
}>({});
const [{ height: containerHeight }] = useResizeObserver(containerRef);
@@ -186,32 +184,8 @@ export default function LiveDashboardView({
};
}, []);
useEffect(() => {
if (!cameras) return;
const mseSupported =
"MediaSource" in window || "ManagedMediaSource" in window;
const newPreferredLiveModes = cameras.reduce(
(acc, camera) => {
const isRestreamed =
config &&
Object.keys(config.go2rtc.streams || {}).includes(
camera.live.stream_name,
);
if (!mseSupported) {
acc[camera.name] = isRestreamed ? "webrtc" : "jsmpeg";
} else {
acc[camera.name] = isRestreamed ? "mse" : "jsmpeg";
}
return acc;
},
{} as { [key: string]: LivePlayerMode },
);
setPreferredLiveModes(newPreferredLiveModes);
}, [cameras, config, windowVisible]);
const { preferredLiveModes, setPreferredLiveModes, resetPreferredLiveMode } =
useCameraLiveMode(cameras, windowVisible);
const cameraRef = useCallback(
(node: HTMLElement | null) => {
@@ -381,6 +355,7 @@ export default function LiveDashboardView({
autoLive={autoLiveView}
onClick={() => onSelectCamera(camera.name)}
onError={(e) => handleError(camera.name, e)}
onResetLiveMode={() => resetPreferredLiveMode(camera.name)}
/>
);
})}