here goes nothin

This commit is contained in:
legop3
2025-11-29 21:24:18 -05:00
parent d00b2cf160
commit f6a57b788a
13 changed files with 247 additions and 42 deletions
+15 -2
View File
@@ -6,8 +6,14 @@ import VideoTile from './VideoTile.jsx';
export default function DriverVideoPanel({layoutFormat = 'desktop'}) {
const { session } = useSession();
const roverId = session?.assignment?.roverId;
const sources = useVideoRequests(roverId ? [roverId] : []);
const rosterEntry =
roverId && session?.roster ? session.roster.find((item) => String(item.id) === String(roverId)) : null;
const hasAudio = Boolean(rosterEntry?.media?.audioPublishUrl);
const sources = useVideoRequests(
roverId ? [{ type: 'rover', id: roverId, audioId: hasAudio ? `${roverId}-audio` : null }] : [],
);
const info = roverId ? sources[roverId] : null;
const audioInfo = roverId && hasAudio ? sources[`${roverId}-audio`] : null;
const frame = useTelemetryFrame(roverId);
const batteryRecord =
roverId && session?.roster
@@ -20,7 +26,14 @@ export default function DriverVideoPanel({layoutFormat = 'desktop'}) {
return (
<section className="panel">
{roverId ? (
<VideoTile sessionInfo={info} label={roverLabel} telemetryFrame={frame} batteryConfig={batteryConfig} layoutFormat={layoutFormat}/>
<VideoTile
sessionInfo={info}
audioSessionInfo={audioInfo}
label={roverLabel}
telemetryFrame={frame}
batteryConfig={batteryConfig}
layoutFormat={layoutFormat}
/>
) : (
<div className="panel-muted content-center text-center text-sm text-slate-400 aspect-video">
<p>You are not assigned to a rover.</p>
+52
View File
@@ -1,11 +1,13 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { WhepPlayer } from '../lib/whepPlayer.js';
import { AudioWhepPlayer } from '../lib/audioWhepPlayer.js';
const RESTART_DELAY_MS = 2000;
const UNMUTE_RETRY_MS = 3000;
export default function VideoTile({
sessionInfo,
audioSessionInfo,
label,
forceMute = false,
telemetryFrame,
@@ -13,11 +15,16 @@ export default function VideoTile({
layoutFormat = 'desktop',
}) {
const videoRef = useRef(null);
const audioRef = useRef(null);
const restartTimer = useRef(null);
const audioRestartTimer = useRef(null);
const unmuteTimer = useRef(null);
const [status, setStatus] = useState('idle');
const [detail, setDetail] = useState(null);
const [audioStatus, setAudioStatus] = useState('idle');
const [audioDetail, setAudioDetail] = useState(null);
const [restartToken, setRestartToken] = useState(0);
const [audioRestartToken, setAudioRestartToken] = useState(0);
const [muted, setMuted] = useState(true);
const sensors = telemetryFrame?.sensors;
const batteryCharge = sensors?.batteryChargeMah ?? null;
@@ -40,6 +47,10 @@ export default function VideoTile({
clearTimeout(restartTimer.current);
restartTimer.current = setTimeout(() => setRestartToken(Date.now()), RESTART_DELAY_MS);
}, []);
const scheduleAudioRestart = useCallback(() => {
clearTimeout(audioRestartTimer.current);
audioRestartTimer.current = setTimeout(() => setAudioRestartToken(Date.now()), RESTART_DELAY_MS);
}, []);
const ensurePlayback = useCallback(async () => {
const video = videoRef.current;
@@ -89,6 +100,7 @@ export default function VideoTile({
useEffect(
() => () => {
clearTimeout(restartTimer.current);
clearTimeout(audioRestartTimer.current);
clearTimeout(unmuteTimer.current);
},
[],
@@ -146,6 +158,45 @@ export default function VideoTile({
}
}, [status, sessionInfo?.url, scheduleRestart]);
// Audio-only WHEP (no pausing/muting; keeps trying to play)
useEffect(() => {
if (!audioSessionInfo?.url || !audioRef.current) {
return undefined;
}
let active = true;
let player;
const handleStatus = (nextStatus, info) => {
if (!active) return;
setAudioStatus(nextStatus);
setAudioDetail(info || null);
if (nextStatus === 'playing') {
audioRef.current?.play().catch(() => {});
}
if (['error', 'failed', 'disconnected', 'closed'].includes(nextStatus)) {
scheduleAudioRestart();
}
};
player = new AudioWhepPlayer({
url: audioSessionInfo.url,
token: audioSessionInfo.token,
audio: audioRef.current,
onStatus: handleStatus,
});
player.start().catch((err) => {
if (!active) return;
setAudioStatus('error');
setAudioDetail(err.message);
scheduleAudioRestart();
});
return () => {
active = false;
player?.stop();
};
}, [audioSessionInfo?.url, audioSessionInfo?.token, audioRestartToken, scheduleAudioRestart]);
const renderedStatus = !sessionInfo?.url
? 'waiting'
: status === 'error'
@@ -165,6 +216,7 @@ export default function VideoTile({
controls={false}
className="h-full w-full object-contain"
/>
<audio ref={audioRef} autoPlay hidden />
<HudOverlay frame={telemetryFrame} label={label} status={renderedStatus} desktopLayout={desktopLayout}/>
<OvercurrentOverlay motors={overcurrentMotors} />
<LowBatteryOverlay charge={batteryCharge} config={batteryConfig} />