pause ptz turns when no ones on

This commit is contained in:
legop3
2026-09-24 18:06:24 -04:00
parent ffd34de56c
commit 8ca714cf1a
4 changed files with 21 additions and 10 deletions
@@ -121,7 +121,7 @@ function describeParticipation(socket) {
color: cameraConfig.color || DEFAULT_PTZ_COLOR,
fallback: null,
},
enabled: entered && (isOperator || position > 0),
enabled: entered && (isOperator || position > 0) && participation.state.queue.length > 0,
isActive: isOperator,
turnsAhead: isOperator ? 0 : position || null,
queueLength: participation.state.queue.length + (participation.state.operatorSocketId ? 1 : 0),
@@ -22,19 +22,27 @@ function createParticipation({ io, events, emitChange, getTurnDurationMs, canPar
}
function scheduleTurn() {
clearTimeout(timer);
// Uncontested control has no deadline. Starting a new wait gets a full turn,
// while later queue changes must not reset an already running countdown.
if (!state.operatorSocketId || !state.queue.length) {
clearTimeout(timer);
timer = null;
state.deadline = null;
return;
}
if (timer) return;
const duration = getTurnDurationMs();
state.deadline = Date.now() + duration;
timer = setTimeout(() => {
// An expired operator stays when alone, but is not automatically requeued
// when another participant takes over. Preserve the existing PTZ rule.
state.queue = state.queue.filter((id) => eligible(id));
timer = null;
state.queue = state.queue.filter(eligible);
if (state.queue.length) {
// Preserve PTZ's explicit request-to-rejoin rule after a handoff.
stopOperator('turn-expired');
advance('turn-expired');
} else {
scheduleTurn();
emitChange('turn-extended-empty-queue');
emitChange('turn-uncontested');
}
}, duration);
}
@@ -45,14 +53,15 @@ function createParticipation({ io, events, emitChange, getTurnDurationMs, canPar
}
function advance(reason) {
state.queue = state.queue.filter(eligible);
while (!state.operatorSocketId && state.queue.length) {
const next = state.queue.shift();
if (!eligible(next)) continue;
state.operatorSocketId = next;
state.generation += 1;
scheduleTurn();
events.emit('operator', { socketId: next, action: 'active' });
}
scheduleTurn();
emitChange(reason);
}
+3 -3
View File
@@ -37,7 +37,7 @@ function PtzDesktopFullscreen({ ptz, releasePending }) {
</aside>
</div>
<div className="grid min-h-0 grid-cols-[minmax(0,1.6fr)_minmax(16rem,0.7fr)] gap-0.5 overflow-hidden">
<ChatPanel fillHeight title="Chat" allowSpectatorInput inputTarget="overlay" />
<ChatPanel fillHeight title="Chat" allowSpectatorInput />
<PtzPresetPanel ptz={ptz} />
</div>
{releasePending ? (
@@ -105,7 +105,7 @@ function PtzMobileLandscape({ ptz, onClose, releasePending = false }) {
</aside>
</section>
<section className="grid gap-0.5 md:grid-cols-[minmax(0,1fr)_minmax(0,0.7fr)]">
<ChatPanel title="Chat" allowSpectatorInput inputTarget="overlay" />
<ChatPanel title="Chat" allowSpectatorInput />
<PtzPresetPanel ptz={ptz} />
</section>
</div>
@@ -150,7 +150,7 @@ function PtzMobilePortrait({ ptz, onClose, releasePending = false }) {
<PtzPresetPanel ptz={ptz} />
</div>
<PtzQueueSummary ptz={ptz} />
<ChatPanel title="Chat" allowSpectatorInput inputTarget="overlay" />
<ChatPanel title="Chat" allowSpectatorInput />
{/* Portrait keeps room lights immediately after chat as requested. */}
<HomeAssistantControls />
</section>
@@ -1,4 +1,5 @@
import PtzLiveVideo, { PTZ_CAMERA_ID } from '../../components/PtzLiveVideo/index.jsx';
import ChatExpansion from '../../components/HudOverlays/newgen/CornerPods/ChatExpansion.jsx';
import TopLeftPod from '../../components/HudOverlays/newgen/CornerPods/TopLeftPod.jsx';
import { usePtzCameraSnapshots } from '../../hooks/usePtzCameraSnapshot.js';
function PtzSnapshotPreview({ feed, label = 'PTZ Camera', className = 'h-full w-full' }) {
@@ -35,6 +36,7 @@ function PtzMediaPane({ ptz, open, framed = true, compact = false }) {
<PtzSnapshotPreview feed={snapshot} label={ptz?.name || 'PTZ Camera'} />
)}
{ptz?.turn ? <TopLeftPod compact={compact} turns={ptz.turn} /> : null}
<ChatExpansion podOpen={false} />
</>
);