mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-17 01:50:47 -04:00
turns stuff testings
This commit is contained in:
@@ -9,10 +9,24 @@ import { useRoverSnapshots } from '../../hooks/useRoverSnapshots.js';
|
||||
import { useControlSystem } from '../../controls/index.js';
|
||||
import VideoTile from '../VideoTile/index.jsx';
|
||||
|
||||
export default function DriverVideoPanel({layoutFormat = 'desktop'}) {
|
||||
function countEligibleDrivers(users = []) {
|
||||
const unique = new Set();
|
||||
users.forEach((entry) => {
|
||||
const role = String(entry?.role || '');
|
||||
if (role === 'spectator') return;
|
||||
const roverId = String(entry?.roverId || '').trim();
|
||||
const socketId = String(entry?.socketId || '').trim();
|
||||
if (!roverId || !socketId) return;
|
||||
unique.add(socketId);
|
||||
});
|
||||
return unique.size;
|
||||
}
|
||||
|
||||
export default function DriverVideoPanel({ layoutFormat = 'desktop' }) {
|
||||
const mode = useSessionSelector((state) => state.session?.mode || null);
|
||||
const roverId = useSessionSelector((state) => state.session?.assignment?.roverId ?? null);
|
||||
const roster = useSessionSelector((state) => state.session?.roster ?? []);
|
||||
const users = useSessionSelector((state) => state.session?.users ?? []);
|
||||
const turnQueues = useSessionSelector((state) => state.session?.turnQueues ?? {});
|
||||
const socketId = useSessionSelector((state) => state.session?.socketId || null);
|
||||
const activeDrivers = useSessionSelector((state) => state.session?.activeDrivers ?? {});
|
||||
@@ -23,7 +37,9 @@ export default function DriverVideoPanel({layoutFormat = 'desktop'}) {
|
||||
const [now, setNow] = useState(() => Date.now());
|
||||
const [turnCueVisible, setTurnCueVisible] = useState(false);
|
||||
const [turnCueStartAt, setTurnCueStartAt] = useState(null);
|
||||
const [notTurnFlashAt, setNotTurnFlashAt] = useState(0);
|
||||
const lastTurnRef = useRef({ active: false, roverId: null });
|
||||
const lastIntentRef = useRef(lastControlIntentAt || 0);
|
||||
useEffect(() => {
|
||||
if (mode !== 'turns') {
|
||||
return undefined;
|
||||
@@ -51,22 +67,34 @@ export default function DriverVideoPanel({layoutFormat = 'desktop'}) {
|
||||
const idleDeadline = turnInfo?.idleDeadline || null;
|
||||
const msUntilTurn = deadline ? deadline - now : null;
|
||||
const msUntilIdleSkip = idleDeadline ? idleDeadline - now : null;
|
||||
const isPreSwitchWindow =
|
||||
mode === 'turns' && isNextDriver && msUntilTurn != null && msUntilTurn <= 5000 && msUntilTurn > 0;
|
||||
const shouldShowVideo = mode !== 'turns' || isActiveDriver || isPreSwitchWindow;
|
||||
const isTurnsMode = mode === 'turns';
|
||||
const totalRovers = roster.length;
|
||||
const totalDrivers = useMemo(() => countEligibleDrivers(users), [users]);
|
||||
const shouldUsePreviewByLoad = isTurnsMode && totalDrivers > totalRovers;
|
||||
const isPreSwitchWindow = isTurnsMode && isNextDriver && msUntilTurn != null && msUntilTurn <= 5000 && msUntilTurn > 0;
|
||||
const isNotYourTurn = isTurnsMode && !isActiveDriver;
|
||||
const shouldUsePreview = isNotYourTurn && !isPreSwitchWindow && shouldUsePreviewByLoad;
|
||||
const shouldShowVideo = !shouldUsePreview;
|
||||
const turnSeconds =
|
||||
msUntilTurn != null && Number.isFinite(msUntilTurn) ? Math.max(0, Math.ceil(msUntilTurn / 1000)) : null;
|
||||
const idleSkipSeconds =
|
||||
msUntilIdleSkip != null && Number.isFinite(msUntilIdleSkip)
|
||||
? Math.max(0, Math.ceil(msUntilIdleSkip / 1000))
|
||||
: null;
|
||||
const turnTimerText = isActiveDriver
|
||||
? turnSeconds != null
|
||||
? `${turnSeconds}s left`
|
||||
: null
|
||||
: isNextDriver && turnSeconds != null
|
||||
? `Your turn in ${turnSeconds}s`
|
||||
: null;
|
||||
const turnTimerText = useMemo(() => {
|
||||
if (!isTurnsMode) return null;
|
||||
if (isActiveDriver) {
|
||||
return turnSeconds != null ? `${turnSeconds}s left` : 'Your turn';
|
||||
}
|
||||
const parts = ['Not your turn'];
|
||||
if (isNextDriver && turnSeconds != null) {
|
||||
parts.push(`your turn in ${turnSeconds}s`);
|
||||
}
|
||||
if (shouldUsePreview) {
|
||||
parts.push('preview to save upload bandwidth');
|
||||
}
|
||||
return parts.join(' • ');
|
||||
}, [isTurnsMode, isActiveDriver, isNextDriver, turnSeconds, shouldUsePreview]);
|
||||
const entries = roverId
|
||||
? [
|
||||
...(shouldShowVideo ? [{ type: 'rover', id: roverId, key: roverId }] : []),
|
||||
@@ -117,6 +145,16 @@ export default function DriverVideoPanel({layoutFormat = 'desktop'}) {
|
||||
}
|
||||
}, [lastControlIntentAt, turnCueStartAt, turnCueVisible]);
|
||||
|
||||
useEffect(() => {
|
||||
const lastIntent = Number(lastIntentRef.current) || 0;
|
||||
const nextIntent = Number(lastControlIntentAt) || 0;
|
||||
const advanced = nextIntent > lastIntent;
|
||||
if (advanced && isNotYourTurn) {
|
||||
setNotTurnFlashAt(Date.now());
|
||||
}
|
||||
lastIntentRef.current = nextIntent;
|
||||
}, [isNotYourTurn, lastControlIntentAt]);
|
||||
|
||||
return (
|
||||
<section className="panel">
|
||||
{roverId ? (
|
||||
@@ -132,12 +170,14 @@ export default function DriverVideoPanel({layoutFormat = 'desktop'}) {
|
||||
layoutFormat={layoutFormat}
|
||||
overcurrentLimiter={overcurrentLimiter}
|
||||
songNote={song?.note}
|
||||
qualityNotice={!shouldShowVideo ? 'Preview feed (low FPS) until your turn.' : null}
|
||||
qualityNotice={null}
|
||||
showTurnCue={turnCueVisible}
|
||||
turnTimerText={turnTimerText}
|
||||
turnSeconds={turnSeconds}
|
||||
isActiveDriver={isActiveDriver}
|
||||
idleSkipSeconds={idleSkipSeconds}
|
||||
showNotTurnNotice={isNotYourTurn}
|
||||
notTurnFlashAt={notTurnFlashAt}
|
||||
/>
|
||||
) : (
|
||||
<div className="panel-muted content-center text-center text-sm text-slate-400 aspect-[4/3]">
|
||||
|
||||
@@ -19,6 +19,7 @@ function HudOverlay({
|
||||
mobileHud = false,
|
||||
mapPosition = 'top-center',
|
||||
turnTimerText = null,
|
||||
turnTimerFlashActive = false,
|
||||
labelScale = 1,
|
||||
}) {
|
||||
const isMobile = mobileHud;
|
||||
@@ -147,7 +148,9 @@ function HudOverlay({
|
||||
</div>
|
||||
{turnTimerText ? (
|
||||
<div
|
||||
className={`absolute left-1/2 top-0.5 -translate-x-1/2 rounded bg-black/70 text-slate-100 ${timerPadClass} ${timerTextClass}`}
|
||||
className={`absolute left-1/2 top-0.5 -translate-x-1/2 rounded transition-colors ${
|
||||
turnTimerFlashActive ? 'bg-red-900/80 text-red-100' : 'bg-black/70 text-slate-100'
|
||||
} ${timerPadClass} ${timerTextClass}`}
|
||||
>
|
||||
{turnTimerText}
|
||||
</div>
|
||||
|
||||
@@ -47,6 +47,8 @@ export default function VideoTile({
|
||||
turnTimerText = null,
|
||||
isActiveDriver = false,
|
||||
idleSkipSeconds = null,
|
||||
showNotTurnNotice = false,
|
||||
notTurnFlashAt = 0,
|
||||
}) {
|
||||
const discordUrl = useSessionSelector((state) => {
|
||||
const socials = state.session?.socials || [];
|
||||
@@ -72,6 +74,7 @@ export default function VideoTile({
|
||||
const [restartToken, setRestartToken] = useState(0);
|
||||
const [audioRestartToken, setAudioRestartToken] = useState(0);
|
||||
const [muted, setMuted] = useState(true);
|
||||
const [noticeFlashActive, setNoticeFlashActive] = useState(false);
|
||||
const hasDedicatedAudio = Boolean(audioSessionInfo?.url);
|
||||
const usingSnapshot = videoMode === 'snapshot';
|
||||
const sensors = telemetryFrame?.sensors;
|
||||
@@ -298,6 +301,13 @@ export default function VideoTile({
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!showNotTurnNotice || !notTurnFlashAt) return undefined;
|
||||
setNoticeFlashActive(true);
|
||||
const timer = setTimeout(() => setNoticeFlashActive(false), 650);
|
||||
return () => clearTimeout(timer);
|
||||
}, [showNotTurnNotice, notTurnFlashAt]);
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'playing') {
|
||||
attemptUnmute(0);
|
||||
@@ -634,6 +644,7 @@ export default function VideoTile({
|
||||
mobileHud={mobileHud}
|
||||
mapPosition={effectiveHudMapPosition}
|
||||
turnTimerText={turnTimerText}
|
||||
turnTimerFlashActive={noticeFlashActive}
|
||||
labelScale={hudLabelScale}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
Reference in New Issue
Block a user