mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
bandwidth savings configs
This commit is contained in:
@@ -540,13 +540,23 @@ function buildPtzTurnModel(ptz, selfId) {
|
||||
|
||||
function PtzMediaPane({ ptz, open, framed = true }) {
|
||||
const isOperator = Boolean(ptz?.isOperator);
|
||||
const nonTurnVideoPolicy = useSessionSelector(
|
||||
(state) => state.session?.bandwidthSavings?.nonTurnVideo || 'snapshots',
|
||||
);
|
||||
const selfId = useSessionSelector((state) => state.session?.socketId || null);
|
||||
const snapshotFeeds = usePtzCameraSnapshots([PTZ_CAMERA_ID], { enabled: open && !isOperator });
|
||||
/*
|
||||
PTZ has its own turn queue, so non-operators are the camera equivalent of a
|
||||
non-active rover driver. The server enforces the same policy in
|
||||
canRequestLiveVideo(); this branch only chooses the expected browser render
|
||||
path and never unlocks movement controls.
|
||||
*/
|
||||
const shouldUseLiveVideo = isOperator || nonTurnVideoPolicy === 'live';
|
||||
const snapshotFeeds = usePtzCameraSnapshots([PTZ_CAMERA_ID], { enabled: open && !shouldUseLiveVideo });
|
||||
const snapshot = snapshotFeeds[PTZ_CAMERA_ID] || null;
|
||||
const turnModel = useMemo(() => buildPtzTurnModel(ptz, selfId), [ptz, selfId]);
|
||||
const media = (
|
||||
<>
|
||||
{isOperator ? (
|
||||
{shouldUseLiveVideo ? (
|
||||
<PtzLiveVideo enabled={open} startMuted={false} />
|
||||
) : (
|
||||
<PtzSnapshotPreview feed={snapshot} label={ptz?.name || 'PTZ Camera'} />
|
||||
|
||||
@@ -4,6 +4,7 @@ import RoverDescriptionOverlay from '../HudOverlays/RoverDescriptionOverlay/inde
|
||||
import OvercurrentOverlay from '../HudOverlays/OvercurrentOverlay/index.jsx';
|
||||
import LowBatteryOverlay from '../HudOverlays/LowBatteryOverlay/index.jsx';
|
||||
import VerticalBatteryOverlay from '../HudOverlays/VerticalBatteryOverlay/index.jsx';
|
||||
import { useSessionSelector } from '../../context/SessionContext.jsx';
|
||||
|
||||
export default function SpectateVideo({
|
||||
roverId = null,
|
||||
@@ -11,12 +12,24 @@ export default function SpectateVideo({
|
||||
fitParent = false,
|
||||
layoutFormat = 'desktop',
|
||||
}) {
|
||||
const isExternalSpectatorSnapshotOnly = useSessionSelector((state) =>
|
||||
state.session?.role === 'spectator' &&
|
||||
state.session?.isLocalNetwork === false &&
|
||||
state.session?.bandwidthSavings?.externalSpectatorVideo === 'snapshots',
|
||||
);
|
||||
/*
|
||||
Server auth already denies external spectator WHEP when this policy is set,
|
||||
but explicitly selecting snapshot mode avoids expected authorization denials
|
||||
and keeps analytics focused on actual stream failures.
|
||||
*/
|
||||
const videoMode = isExternalSpectatorSnapshotOnly ? 'snapshot' : null;
|
||||
return (
|
||||
<div className={`flex flex-col gap-0.5 ${fitParent ? 'h-full' : ''}`}>
|
||||
<div className={`relative w-full overflow-hidden bg-black ${fitParent ? 'h-full flex-1' : 'aspect-[4/3]'}`}>
|
||||
<RoverMediaPlayer
|
||||
roverId={roverId}
|
||||
label={label}
|
||||
videoMode={videoMode}
|
||||
/>
|
||||
<RoverDescriptionOverlay
|
||||
roverId={roverId}
|
||||
|
||||
@@ -326,9 +326,18 @@ function PtzControlReference() {
|
||||
function PtzController({ open, onClose, layout = 'desktop' }) {
|
||||
const ptz = useSessionSelector((state) => state.session?.ptzCamera || null);
|
||||
const isOperator = Boolean(ptz?.isOperator);
|
||||
const nonTurnVideoPolicy = useSessionSelector(
|
||||
(state) => state.session?.bandwidthSavings?.nonTurnVideo || 'snapshots',
|
||||
);
|
||||
const isMobile = layout === 'mobile-portrait' || layout === 'mobile-landscape';
|
||||
const { ptzRelease } = useSessionActions();
|
||||
const snapshotFeeds = usePtzCameraSnapshots([PTZ_CAMERA_ID], { enabled: open && !isOperator });
|
||||
/*
|
||||
The VIP fullscreen surface is available to queued PTZ users too. Let queued
|
||||
users see live video only when the central non-turn video policy allows it;
|
||||
all movement and light controls still remain guarded by isOperator.
|
||||
*/
|
||||
const shouldUseLiveVideo = isOperator || nonTurnVideoPolicy === 'live';
|
||||
const snapshotFeeds = usePtzCameraSnapshots([PTZ_CAMERA_ID], { enabled: open && !shouldUseLiveVideo });
|
||||
const snapshot = snapshotFeeds[PTZ_CAMERA_ID] || null;
|
||||
const [releasePending, setReleasePending] = useState(false);
|
||||
|
||||
@@ -431,7 +440,7 @@ function PtzController({ open, onClose, layout = 'desktop' }) {
|
||||
bodyClassName={`grid h-full min-h-0 overflow-hidden ${sidebarWidthClass}`}
|
||||
>
|
||||
<main className="relative min-h-0 min-w-0 bg-black">
|
||||
{isOperator ? (
|
||||
{shouldUseLiveVideo ? (
|
||||
<PtzLiveVideo enabled startMuted={false} />
|
||||
) : (
|
||||
<PtzSnapshotPreview feed={snapshot} label={ptz?.name || 'PTZ Camera'} />
|
||||
|
||||
@@ -9,6 +9,9 @@ export function useDriverVideoModePolicy(roverId) {
|
||||
const turnQueues = useSessionSelector((state) => state.session?.turnQueues ?? {});
|
||||
const socketId = useSessionSelector((state) => state.session?.socketId || null);
|
||||
const activeDrivers = useSessionSelector((state) => state.session?.activeDrivers ?? {});
|
||||
const nonTurnVideoPolicy = useSessionSelector(
|
||||
(state) => state.session?.bandwidthSavings?.nonTurnVideo || 'snapshots',
|
||||
);
|
||||
const isTurnsMode = mode === 'turns';
|
||||
/*
|
||||
This policy only switches preview/full video around a multi-second turn
|
||||
@@ -43,7 +46,13 @@ export function useDriverVideoModePolicy(roverId) {
|
||||
});
|
||||
return unique.size;
|
||||
}, [users]);
|
||||
const shouldUsePreviewByLoad = isTurnsMode && totalDrivers > totalRovers;
|
||||
/*
|
||||
The server sends the bandwidth policy because the same rule is enforced in
|
||||
video authorization. The hook only mirrors that policy so the UI avoids
|
||||
requesting live video when snapshots are the intended non-turn experience.
|
||||
*/
|
||||
const shouldUsePreviewByLoad =
|
||||
nonTurnVideoPolicy === 'snapshots' && isTurnsMode && totalDrivers > totalRovers;
|
||||
const isPreSwitchWindow =
|
||||
isTurnsMode && isNextDriver && msUntilTurn != null && msUntilTurn <= 5000 && msUntilTurn > 0;
|
||||
const showNotTurnNotice = isTurnsMode && !isActiveDriver;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useSession } from '../context/SessionContext.jsx';
|
||||
export function useSpectatorMode() {
|
||||
const { session, setRole, subscribeAll, connected } = useSession();
|
||||
const [ready, setReady] = useState(false);
|
||||
const canUseSpectatorAccess = session?.bandwidthSavings?.canUseExternalSpectatorAccess !== false;
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
@@ -14,6 +15,15 @@ export function useSpectatorMode() {
|
||||
setReady(false);
|
||||
return;
|
||||
}
|
||||
if (!canUseSpectatorAccess) {
|
||||
/*
|
||||
The server will reject the role change too, but stopping here prevents
|
||||
a blocked external spectator from retrying on every session sync while
|
||||
the page is intentionally waiting for an admin grant or config change.
|
||||
*/
|
||||
setReady(false);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (session?.role !== 'spectator') {
|
||||
await setRole('spectator');
|
||||
@@ -33,7 +43,7 @@ export function useSpectatorMode() {
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [connected, session?.mode, session?.role, setRole, subscribeAll]);
|
||||
}, [canUseSpectatorAccess, connected, session?.mode, session?.role, setRole, subscribeAll]);
|
||||
|
||||
return ready;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ export default function SpectatorContent() {
|
||||
const latestReplay = useSessionSelector((state) => state.latestReplay);
|
||||
const { clearLatestReplay } = useSessionActions();
|
||||
const inLockdown = session?.mode === 'lockdown';
|
||||
const canUseSpectatorAccess = session?.bandwidthSavings?.canUseExternalSpectatorAccess !== false;
|
||||
const spectatorAccessMode = session?.bandwidthSavings?.externalSpectatorAccess || 'on';
|
||||
useDefaultNickname();
|
||||
// The spectator route is not rendered through App.jsx, so it must opt into
|
||||
// the same persisted identity heartbeat here. That keeps the existing
|
||||
@@ -44,6 +46,26 @@ export default function SpectatorContent() {
|
||||
);
|
||||
}
|
||||
|
||||
if (session && !canUseSpectatorAccess) {
|
||||
/*
|
||||
The server keeps this socket in the normal user role when external
|
||||
spectating is blocked. Rendering a full-page gate makes that intentional
|
||||
state obvious instead of showing an empty spectator shell that repeatedly
|
||||
fails to subscribe.
|
||||
*/
|
||||
const message = spectatorAccessMode === 'admin'
|
||||
? 'This external spectator identity needs admin approval before it can view the spectator page.'
|
||||
: 'External spectator access is disabled on this server.';
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-black text-slate-200">
|
||||
<div className="surface max-w-md space-y-0.5 p-1 text-center text-sm">
|
||||
<p className="text-lg font-semibold text-white">Spectate access required.</p>
|
||||
<p className="text-slate-300">{message}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const mainClass = isPortraitLayout
|
||||
? 'flex min-h-screen flex-col bg-black text-slate-100 md:h-screen md:overflow-hidden'
|
||||
: 'grid min-h-screen grid-cols-1 gap-0.5 bg-black text-slate-100 md:h-full md:min-h-0 md:grid-cols-[minmax(0,1fr)_18rem] lg:grid-cols-[minmax(0,1fr)_20rem]';
|
||||
|
||||
@@ -81,6 +81,19 @@ function PtzSnapshotFallback({ label, source }) {
|
||||
}
|
||||
|
||||
function PtzLiveOrSnapshot({ label }) {
|
||||
const isExternalSpectatorSnapshotOnly = useSessionSelector((state) =>
|
||||
state.session?.role === 'spectator' &&
|
||||
state.session?.isLocalNetwork === false &&
|
||||
state.session?.bandwidthSavings?.externalSpectatorVideo === 'snapshots',
|
||||
);
|
||||
/*
|
||||
PTZ live authorization is server-owned, but the spectator page knows when
|
||||
the configured outcome is snapshot-only. Rendering the fallback directly
|
||||
avoids a guaranteed denied WHEP request for every external spectator card.
|
||||
*/
|
||||
if (isExternalSpectatorSnapshotOnly) {
|
||||
return <PtzSnapshotFallback label={label} source={null} />;
|
||||
}
|
||||
return (
|
||||
<PtzLiveVideo
|
||||
enabled
|
||||
|
||||
Reference in New Issue
Block a user