This commit is contained in:
legop3
2026-02-21 18:15:37 -05:00
parent ed93bbf4d6
commit d3a17608af
6 changed files with 178 additions and 160 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -11,8 +11,8 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" />
<title>Multi Roomba Rover</title>
<script type="module" crossorigin src="/assets/index-ChF3_rCg.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BhESptqT.css">
<script type="module" crossorigin src="/assets/index-D8STCiny.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Bz1ixYh7.css">
</head>
<body>
<div id="root"></div>
+50 -32
View File
@@ -68,6 +68,7 @@ export default function VideoTile({
const [audioStatus, setAudioStatus] = useState('idle');
const [audioDetail, setAudioDetail] = useState(null);
const [levelIndicator, setLevelIndicator] = useState(null);
const [compressorActive, setCompressorActive] = useState(false);
const [restartToken, setRestartToken] = useState(0);
const [audioRestartToken, setAudioRestartToken] = useState(0);
const [muted, setMuted] = useState(true);
@@ -415,26 +416,26 @@ export default function VideoTile({
clearInterval(reductionPollRef.current);
reductionPollRef.current = null;
if (!audioEl || !audioSessionInfo?.url) {
setCompressorActive(false);
setLevelIndicator(null);
logAudio('route/no-audio-url');
return;
}
// Phase 1 stabilization: bypass WebAudio compressor path and use plain media element audio.
// This restores the known-good autoplay/retry behavior while we isolate Chrome-specific issues.
const compressorMode = false;
const compressorMode = autoLevelEnabled && autoLevelMode === 'compressor';
const duckMode = autoLevelEnabled && autoLevelMode === 'duck';
const graphReady = compressorMode && ensureAudioGraph();
const graphReady = compressorMode ? ensureAudioGraph() : false;
if (graphReady) {
const compressor = audioCompressorRef.current;
const gainNode = audioGainRef.current;
const contextRunning = audioContextRef.current?.state === 'running';
// Keep direct element audio until WebAudio is actually running in Chrome.
audioEl.volume = contextRunning ? 0 : effectiveRoverGain;
logAudio('route/graph', { contextRunning, elementVolume: audioEl.volume });
const canActivate = Boolean(contextRunning && compressorActive);
// Strict gating: graph-only output only after context is running and compressor is explicitly activated.
audioEl.volume = canActivate ? 0 : effectiveRoverGain;
logAudio('route/graph', { contextRunning, canActivate, elementVolume: audioEl.volume });
if (gainNode) {
const graphGain = contextRunning
const graphGain = canActivate
? Math.max(0, Math.min(4, effectiveRoverGain * COMPRESSOR_MAKEUP_GAIN))
: 0;
gainNode.gain.value = graphGain;
@@ -447,25 +448,29 @@ export default function VideoTile({
compressor.attack.value = COMPRESSOR_SETTINGS.attack;
compressor.release.value = COMPRESSOR_SETTINGS.release;
}
resumeAudioContext();
reductionPollRef.current = setInterval(() => {
if (audioContextRef.current?.state !== 'running') {
setLevelIndicator(null);
return;
}
const reduction = compressor?.reduction;
if (typeof reduction === 'number' && reduction <= COMPRESSOR_REDUCTION_ACTIVE_DB) {
const amount = Math.round(Math.abs(reduction) * 10) / 10;
const formatted = Number.isInteger(amount) ? amount.toFixed(0) : amount.toFixed(1);
setLevelIndicator(`Level: ${formatted}dB`);
logAudio('compressor/reduction', { reduction, formatted });
} else {
setLevelIndicator(null);
}
}, 180);
if (canActivate) {
reductionPollRef.current = setInterval(() => {
if (audioContextRef.current?.state !== 'running') {
setLevelIndicator(null);
return;
}
const reduction = compressor?.reduction;
if (typeof reduction === 'number' && reduction <= COMPRESSOR_REDUCTION_ACTIVE_DB) {
const amount = Math.round(Math.abs(reduction) * 10) / 10;
const formatted = Number.isInteger(amount) ? amount.toFixed(0) : amount.toFixed(1);
setLevelIndicator(`Level: ${formatted}dB`);
logAudio('compressor/reduction', { reduction, formatted });
} else {
setLevelIndicator(null);
}
}, 180);
} else {
setLevelIndicator(null);
}
return;
}
setCompressorActive(false);
audioEl.volume = effectiveRoverGain;
logAudio('route/direct', { elementVolume: audioEl.volume, duckMode, brushOrVacuumActive });
if (audioCompressorRef.current) {
@@ -489,9 +494,9 @@ export default function VideoTile({
autoLevelEnabled,
autoLevelMode,
brushOrVacuumActive,
compressorActive,
effectiveRoverGain,
ensureAudioGraph,
resumeAudioContext,
logAudio,
]);
@@ -517,11 +522,6 @@ export default function VideoTile({
});
if (nextStatus === 'playing') {
resumeAudioContext();
const target = audioRef.current;
if (target) {
target.muted = false;
target.play().then(() => logAudio('audio/play-ok')).catch((err) => logAudio('audio/play-fail', { error: err?.message }));
}
}
if (['error', 'failed'].includes(nextStatus)) {
scheduleAudioRestart();
@@ -573,9 +573,18 @@ export default function VideoTile({
const target = audioRef.current;
if (!target) return;
target.muted = false;
const wantCompressor = autoLevelEnabled && autoLevelMode === 'compressor';
if (wantCompressor) {
ensureAudioGraph();
await resumeAudioContext();
const running = audioContextRef.current?.state === 'running';
if (running && !compressorActive) {
setCompressorActive(true);
logAudio('compressor/activated');
}
}
if (!target.paused && !target.ended) return;
logAudio('retry/play-attempt');
await resumeAudioContext();
target
.play()
.then(() => {
@@ -593,7 +602,16 @@ export default function VideoTile({
audioPlayInterval.current = setInterval(attemptPlay, AUDIO_RETRY_MS);
return () => clearInterval(audioPlayInterval.current);
}, [audioSessionInfo?.url, audioStatus, resumeAudioContext, logAudio]);
}, [
audioSessionInfo?.url,
audioStatus,
autoLevelEnabled,
autoLevelMode,
compressorActive,
ensureAudioGraph,
resumeAudioContext,
logAudio,
]);
// Reflect audio element events back into status/detail so the HUD stays accurate.
useEffect(() => {