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. All notable changes to iNTERCEPT will be documented in this file.
## [2.22.2] - 2026-02-23 ## [2.22.3] - 2026-02-23
### Fixed ### 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 - 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 - 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 import sys
# Application version # Application version
VERSION = "2.22.2" VERSION = "2.22.3"
# Changelog - latest release notes (shown on welcome screen) # Changelog - latest release notes (shown on welcome screen)
CHANGELOG = [ CHANGELOG = [
{ {
"version": "2.22.2", "version": "2.22.3",
"date": "February 2026", "date": "February 2026",
"highlights": [ "highlights": [
"Waterfall control panel no longer shows as unstyled text on first visit", "Waterfall control panel no longer shows as unstyled text on first visit",
"WebSDR globe renders correctly on first page load without requiring a refresh", "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] [project]
name = "intercept" name = "intercept"
version = "2.21.1" version = "2.22.3"
description = "Signal Intelligence Platform - Pager/433MHz/ADS-B/Satellite/WiFi/Bluetooth" description = "Signal Intelligence Platform - Pager/433MHz/ADS-B/Satellite/WiFi/Bluetooth"
readme = "README.md" readme = "README.md"
requires-python = ">=3.9" requires-python = ">=3.9"
+4 -2
View File
@@ -932,9 +932,11 @@ def _stop_audio_stream_internal():
audio_process = None audio_process = None
audio_rtl_process = None audio_rtl_process = None
# Pause for SDR device USB interface to be released by kernel # Brief pause for SDR device USB interface to be released by kernel.
# The _start_audio_stream retry loop handles longer contention windows
# so only a minimal delay is needed here.
if had_processes: if had_processes:
time.sleep(1.0) time.sleep(0.15)
# ============================================ # ============================================
+25 -10
View File
@@ -900,19 +900,27 @@ const Waterfall = (function () {
resolve(ok); 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 onFail = () => finish(false);
const events = ['playing', 'timeupdate', 'canplay', 'loadeddata']; const events = ['playing', 'timeupdate'];
const failEvents = ['error', 'abort', 'stalled', 'ended']; const failEvents = ['error', 'abort', 'stalled', 'ended'];
events.forEach((evt) => player.addEventListener(evt, onReady)); events.forEach((evt) => player.addEventListener(evt, onReady));
failEvents.forEach((evt) => player.addEventListener(evt, onFail)); failEvents.forEach((evt) => player.addEventListener(evt, onFail));
timer = setTimeout(() => { timer = setTimeout(() => {
finish(!player.paused && (player.currentTime > 0 || player.readyState >= 2)); finish(!player.paused && player.currentTime > 0);
}, timeoutMs); }, timeoutMs);
if (!player.paused && (player.currentTime > 0 || player.readyState >= 2)) { if (!player.paused && player.currentTime > 0) {
finish(true); finish(true);
} }
}); });
@@ -2571,6 +2579,7 @@ const Waterfall = (function () {
} }
if (attempt < maxAttempts) { if (attempt < maxAttempts) {
_setMonitorState(`Waiting for audio stream (attempt ${attempt}/${maxAttempts})...`);
await _wait(220 * attempt); await _wait(220 * attempt);
continue; continue;
} }
@@ -2813,12 +2822,9 @@ const Waterfall = (function () {
clearTimeout(_monitorRetuneTimer); clearTimeout(_monitorRetuneTimer);
_audioConnectNonce += 1; _audioConnectNonce += 1;
try { // Immediately pause audio and update the UI so the user gets instant
await fetch('/receiver/audio/stop', { method: 'POST' }); // feedback. The backend cleanup (which can block for 1-2 s while the
} catch (_) { // SDR process group is reaped) happens afterwards.
// Ignore backend stop errors
}
_stopSmeter(); _stopSmeter();
_setUnlockVisible(false); _setUnlockVisible(false);
_audioUnlockRequired = false; _audioUnlockRequired = false;
@@ -2836,6 +2842,13 @@ const Waterfall = (function () {
_setVisualStatus('READY'); _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) { if (resumeWaterfall && _active) {
_resumeWaterfallAfterMonitor = false; _resumeWaterfallAfterMonitor = false;
await start(); await start();
@@ -2983,6 +2996,8 @@ const Waterfall = (function () {
}; };
_ws.onclose = () => { _ws.onclose = () => {
// stop() sets _ws = null before the async onclose fires.
if (!_ws) return;
if (!_wsOpened && _active) { if (!_wsOpened && _active) {
// Wait for timeout-based fallback; avoid flapping to SSE on brief close/retry. // Wait for timeout-based fallback; avoid flapping to SSE on brief close/retry.
_setStatus('WebSocket closed before ready. Waiting to retry/fallback...'); _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/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/keyboard-shortcuts.js') }}"></script>
<script src="{{ url_for('static', filename='js/core/cheat-sheets.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> <script>
// ============================================ // ============================================