Compare commits

..

4 Commits

Author SHA1 Message Date
Smittix 367048e853 chore: bump version to 2.22.3 and update changelog
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 21:03:27 +00:00
Smittix 406ca28304 fix: suppress stale WebSocket close message after stopping waterfall
stop() sets _ws = null before the async onclose fires, so the handler
now early-returns when _ws is null instead of showing the misleading
"WebSocket closed before ready" retry message.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 21:01:59 +00:00
Smittix f889c53d92 fix: waterfall monitor audio delay and unresponsive stop button
- _waitForPlayback now only succeeds on playing/timeupdate events, not
  loadeddata/canplay which fire from just the WAV header before real
  audio arrives
- stopMonitor() pauses audio and updates UI immediately instead of
  blocking on the backend stop request (1+ second delay)
- Reduced backend audio stop sleep from 1.0s to 0.15s; the start
  retry loop already handles USB contention

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 20:59:40 +00:00
Smittix b0af1d16d2 chore: bump pyproject.toml version to 2.22.2
Was missed during previous 2.22.x release bumps.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-23 20:27:35 +00:00
6 changed files with 522 additions and 499 deletions
+4 -1
View File
@@ -2,11 +2,14 @@
All notable changes to iNTERCEPT will be documented in this file.
## [2.22.2] - 2026-02-23
## [2.22.3] - 2026-02-23
### Fixed
- Waterfall control panel rendered as unstyled text for up to 20 seconds on first visit — CSS is now loaded eagerly with the rest of the page assets
- WebSDR globe failed to render on first page load — initialization now waits for a layout frame before mounting the WebGL renderer, ensuring the container has non-zero dimensions
- Waterfall monitor audio took minutes to start — `_waitForPlayback` now only reports success on actual audio playback (`playing`/`timeupdate`), not from the WAV header alone (`loadeddata`/`canplay`)
- Waterfall monitor could not be stopped — `stopMonitor()` now pauses audio and updates the UI immediately instead of waiting for the backend stop request (which blocked for 1+ seconds during SDR process cleanup)
- Stopping the waterfall no longer shows a stale "WebSocket closed before ready" message — the `onclose` handler now detects intentional closes
---
+5 -2
View File
@@ -7,16 +7,19 @@ import os
import sys
# Application version
VERSION = "2.22.2"
VERSION = "2.22.3"
# Changelog - latest release notes (shown on welcome screen)
CHANGELOG = [
{
"version": "2.22.2",
"version": "2.22.3",
"date": "February 2026",
"highlights": [
"Waterfall control panel no longer shows as unstyled text on first visit",
"WebSDR globe renders correctly on first page load without requiring a refresh",
"Waterfall monitor audio no longer takes minutes to start — playback detection now waits for real audio data instead of just the WAV header",
"Waterfall monitor stop is now instant — audio pauses and UI updates immediately instead of waiting for backend cleanup",
"Stopping the waterfall no longer shows a stale 'WebSocket closed before ready' message",
]
},
{
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "intercept"
version = "2.21.1"
version = "2.22.3"
description = "Signal Intelligence Platform - Pager/433MHz/ADS-B/Satellite/WiFi/Bluetooth"
readme = "README.md"
requires-python = ">=3.9"
+486 -484
View File
File diff suppressed because it is too large Load Diff
+25 -10
View File
@@ -900,19 +900,27 @@ const Waterfall = (function () {
resolve(ok);
};
const onReady = () => finish(true);
// Only treat actual playback as success. `loadeddata` and
// `canplay` fire when just the WAV header arrives — before any
// real audio samples have been decoded — which caused the
// monitor to report "started" while the stream was still silent.
const onReady = () => {
if (player.currentTime > 0 || (!player.paused && player.readyState >= 4)) {
finish(true);
}
};
const onFail = () => finish(false);
const events = ['playing', 'timeupdate', 'canplay', 'loadeddata'];
const events = ['playing', 'timeupdate'];
const failEvents = ['error', 'abort', 'stalled', 'ended'];
events.forEach((evt) => player.addEventListener(evt, onReady));
failEvents.forEach((evt) => player.addEventListener(evt, onFail));
timer = setTimeout(() => {
finish(!player.paused && (player.currentTime > 0 || player.readyState >= 2));
finish(!player.paused && player.currentTime > 0);
}, timeoutMs);
if (!player.paused && (player.currentTime > 0 || player.readyState >= 2)) {
if (!player.paused && player.currentTime > 0) {
finish(true);
}
});
@@ -2571,6 +2579,7 @@ const Waterfall = (function () {
}
if (attempt < maxAttempts) {
_setMonitorState(`Waiting for audio stream (attempt ${attempt}/${maxAttempts})...`);
await _wait(220 * attempt);
continue;
}
@@ -2813,12 +2822,9 @@ const Waterfall = (function () {
clearTimeout(_monitorRetuneTimer);
_audioConnectNonce += 1;
try {
await fetch('/receiver/audio/stop', { method: 'POST' });
} catch (_) {
// Ignore backend stop errors
}
// Immediately pause audio and update the UI so the user gets instant
// feedback. The backend cleanup (which can block for 1-2 s while the
// SDR process group is reaped) happens afterwards.
_stopSmeter();
_setUnlockVisible(false);
_audioUnlockRequired = false;
@@ -2836,6 +2842,13 @@ const Waterfall = (function () {
_setVisualStatus('READY');
}
// Backend stop is fire-and-forget; UI is already updated above.
try {
await fetch('/receiver/audio/stop', { method: 'POST' });
} catch (_) {
// Ignore backend stop errors
}
if (resumeWaterfall && _active) {
_resumeWaterfallAfterMonitor = false;
await start();
@@ -2983,6 +2996,8 @@ const Waterfall = (function () {
};
_ws.onclose = () => {
// stop() sets _ws = null before the async onclose fires.
if (!_ws) return;
if (!_wsOpened && _active) {
// Wait for timeout-based fallback; avoid flapping to SSE on brief close/retry.
_setStatus('WebSocket closed before ready. Waiting to retry/fallback...');
+1 -1
View File
@@ -2936,7 +2936,7 @@
<script src="{{ url_for('static', filename='js/core/voice-alerts.js') }}?v={{ version }}&r=voicefix2"></script>
<script src="{{ url_for('static', filename='js/core/keyboard-shortcuts.js') }}"></script>
<script src="{{ url_for('static', filename='js/core/cheat-sheets.js') }}"></script>
<script src="{{ url_for('static', filename='js/modes/waterfall.js') }}?v={{ version }}&r=wfdeck20"></script>
<script src="{{ url_for('static', filename='js/modes/waterfall.js') }}?v={{ version }}&r=wfdeck21"></script>
<script>
// ============================================