mirror of
https://github.com/smittix/intercept.git
synced 2026-06-07 21:51:55 -07:00
fix: Resolve listening post audio stuttering introduced in v2.15.0
Throttle audio waterfall rendering (50ms→200ms), eliminate per-frame Array.from() allocation, drain stale pipe buffer before streaming, increase chunk size to 8192, and remove debug logging from animation hot paths. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1456,13 +1456,30 @@ def stream_audio() -> Response:
|
|||||||
if not proc or not proc.stdout:
|
if not proc or not proc.stdout:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
# First byte timeout to avoid hanging clients forever
|
# Drain stale audio that accumulated in the pipe buffer
|
||||||
|
# between pipeline start and stream connection. Keep the
|
||||||
|
# first chunk (contains WAV header) and discard the rest
|
||||||
|
# so the browser starts close to real-time.
|
||||||
|
header_chunk = None
|
||||||
|
while True:
|
||||||
|
ready, _, _ = select.select([proc.stdout], [], [], 0)
|
||||||
|
if not ready:
|
||||||
|
break
|
||||||
|
chunk = proc.stdout.read(8192)
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
if header_chunk is None:
|
||||||
|
header_chunk = chunk
|
||||||
|
if header_chunk:
|
||||||
|
yield header_chunk
|
||||||
|
|
||||||
|
# Stream real-time audio
|
||||||
first_chunk_deadline = time.time() + 3.0
|
first_chunk_deadline = time.time() + 3.0
|
||||||
while audio_running and proc.poll() is None:
|
while audio_running and proc.poll() is None:
|
||||||
# Use select to avoid blocking forever
|
# Use select to avoid blocking forever
|
||||||
ready, _, _ = select.select([proc.stdout], [], [], 2.0)
|
ready, _, _ = select.select([proc.stdout], [], [], 2.0)
|
||||||
if ready:
|
if ready:
|
||||||
chunk = proc.stdout.read(4096)
|
chunk = proc.stdout.read(8192)
|
||||||
if chunk:
|
if chunk:
|
||||||
yield chunk
|
yield chunk
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1742,9 +1742,6 @@ function initSynthesizer() {
|
|||||||
drawSynthesizer();
|
drawSynthesizer();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug: log signal level periodically
|
|
||||||
let lastSynthDebugLog = 0;
|
|
||||||
|
|
||||||
function drawSynthesizer() {
|
function drawSynthesizer() {
|
||||||
if (!synthCtx || !synthCanvas) return;
|
if (!synthCtx || !synthCanvas) return;
|
||||||
|
|
||||||
@@ -1760,19 +1757,6 @@ function drawSynthesizer() {
|
|||||||
let activityLevel = 0;
|
let activityLevel = 0;
|
||||||
let signalIntensity = 0;
|
let signalIntensity = 0;
|
||||||
|
|
||||||
// Debug logging every 2 seconds
|
|
||||||
const now = Date.now();
|
|
||||||
if (now - lastSynthDebugLog > 2000) {
|
|
||||||
console.log('[SYNTH] State:', {
|
|
||||||
isScannerRunning,
|
|
||||||
isDirectListening,
|
|
||||||
scannerSignalActive,
|
|
||||||
currentSignalLevel,
|
|
||||||
visualizerAnalyser: !!visualizerAnalyser
|
|
||||||
});
|
|
||||||
lastSynthDebugLog = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isScannerRunning && !isScannerPaused) {
|
if (isScannerRunning && !isScannerPaused) {
|
||||||
// Use actual signal level data (0-5000 range, normalize to 0-1)
|
// Use actual signal level data (0-5000 range, normalize to 0-1)
|
||||||
signalIntensity = Math.min(1, currentSignalLevel / 3000);
|
signalIntensity = Math.min(1, currentSignalLevel / 3000);
|
||||||
@@ -1864,13 +1848,6 @@ function drawSynthesizer() {
|
|||||||
synthCtx.lineTo(width, height / 2);
|
synthCtx.lineTo(width, height / 2);
|
||||||
synthCtx.stroke();
|
synthCtx.stroke();
|
||||||
|
|
||||||
// Debug: show signal level value
|
|
||||||
if (isScannerRunning || isDirectListening) {
|
|
||||||
synthCtx.fillStyle = 'rgba(255, 255, 255, 0.5)';
|
|
||||||
synthCtx.font = '9px monospace';
|
|
||||||
synthCtx.fillText(`lvl:${Math.round(currentSignalLevel)}`, 4, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
synthAnimationId = requestAnimationFrame(drawSynthesizer);
|
synthAnimationId = requestAnimationFrame(drawSynthesizer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3109,7 +3086,7 @@ let waterfallEndFreq = 108;
|
|||||||
let waterfallRowImage = null;
|
let waterfallRowImage = null;
|
||||||
let waterfallPalette = null;
|
let waterfallPalette = null;
|
||||||
let lastWaterfallDraw = 0;
|
let lastWaterfallDraw = 0;
|
||||||
const WATERFALL_MIN_INTERVAL_MS = 50;
|
const WATERFALL_MIN_INTERVAL_MS = 200;
|
||||||
let waterfallInteractionBound = false;
|
let waterfallInteractionBound = false;
|
||||||
let waterfallResizeObserver = null;
|
let waterfallResizeObserver = null;
|
||||||
let waterfallMode = 'rf';
|
let waterfallMode = 'rf';
|
||||||
@@ -3436,9 +3413,8 @@ function startAudioWaterfall() {
|
|||||||
if (ts - lastAudioWaterfallDraw >= WATERFALL_MIN_INTERVAL_MS) {
|
if (ts - lastAudioWaterfallDraw >= WATERFALL_MIN_INTERVAL_MS) {
|
||||||
lastAudioWaterfallDraw = ts;
|
lastAudioWaterfallDraw = ts;
|
||||||
visualizerAnalyser.getByteFrequencyData(dataArray);
|
visualizerAnalyser.getByteFrequencyData(dataArray);
|
||||||
const bins = Array.from(dataArray, v => v);
|
drawWaterfallRow(dataArray);
|
||||||
drawWaterfallRow(bins);
|
drawSpectrumLine(dataArray, 0, maxFreqKhz, 'kHz');
|
||||||
drawSpectrumLine(bins, 0, maxFreqKhz, 'kHz');
|
|
||||||
}
|
}
|
||||||
audioWaterfallAnimId = requestAnimationFrame(drawFrame);
|
audioWaterfallAnimId = requestAnimationFrame(drawFrame);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user