Previously, start_broadcast immediately emitted broadcast_started and
stream_status: active to listeners, even before the DJ pressed play.
Listeners would connect to /stream.mp3, wait 60s with no data, then
disconnect — so music never reached them from the PyQt client.
The web DJ panel was unaffected because it only sends audio_chunk while
music is actively playing, so start_broadcast and data arrive together.
Fix: in MP3-direct mode (Qt client, format=mp3), hold back broadcast_started
and stream_status: active until the very first audio_chunk arrives. The
first chunk guarantees the preroll buffer has data and listeners will
immediately receive audio when they connect.
- _mp3_broadcast_announced flag tracks whether the deferred announcement
has been sent in the current session
- Resets on stop_broadcast, grace-period auto-stop, and fresh start_broadcast
- Browser (webm/opus) mode is unaffected — announces immediately as before
- server.py: Add _distribute_mp3() to route MP3 chunks directly to listener
queues without a second ffmpeg passthrough (halves pipeline latency, removes
the eventlet/subprocess blocking read that caused the Qt client to fail)
- server.py: dj_start no longer starts ffmpeg for is_mp3_input=True
- server.py: dj_audio routes to _distribute_mp3 vs _feed_transcoder based on format
- server.py: _transcoder_watchdog skips MP3-direct mode
- server.py: stream_mp3 endpoint no longer waits for ffmpeg proc when MP3 direct
- techdj_qt.py: Add -fflags nobuffer + -flush_packets 1 to reduce source latency
- techdj_qt.py: bufsize=0 and read(4096) instead of read(8192) for ~260ms chunks
- listener.js: Reduce broadcast_started connect delay 800ms -> 300ms
- server.py: Increase ping_timeout 10->60, ping_interval 5->25 to prevent
frequent socket disconnects during audio streaming
- server.py: Guard dj_start() against reconnect loops - only clear pre-roll
and emit broadcast_started on a fresh broadcast, not on DJ reconnects
- listener.js: Add polling fallback to transports (websocket-only caused
silent failure on upgrade error), set reconnectionAttempts to Infinity
- script.js: Same transport fallback fix for DJ panel socket
- techdj_qt.py: Add _broadcast_started flag to StreamingWorker.on_connect
so streaming_started signal only fires once; reconnects resume silently.
Reset flag in stop_streaming() for clean next session.
- script.js: Remove ~500 lines of dead listener mode code (initListenerMode, enableListenerAudio, setListenerVolume, startListenerVUMeter, getMp3FallbackUrl, listener variables). Listener page now uses listener.js exclusively.
- script.js: Remove ?listen=true detection from DOMContentLoaded that could activate broken listener UI on DJ panel.
- script.js: Clean up initSocket() to remove dead listener mode detection logic.
- index.html: Remove dead #listener-mode div (now served by listener.html).
- server.py: Move 'abort' import to top-level Flask import instead of per-request import.
- techdj_qt.py: Fix StreamingWorker to create fresh socketio.Client on each streaming session, preventing stale socket state on reconnect.
- techdj_qt.py: Fix time.sleep(0.2) blocking GUI thread in stop_streaming() by removing it and using try/except for clean disconnect.
- Block DJ-only files (index.html, script.js, style.css) on listener server
- Disable Flask built-in static handler on listener (static_folder=None)
to prevent it from serving index.html before custom routes
- Add Cache-Control no-store headers to index route to prevent
nginx/browser from caching stale index.html for listener URL
- Created standalone listener.html, listener.js, listener.css
- Listener server now serves listener.html instead of index.html
- Eliminates flash of DJ panel when loading listener page
- setup_shared_routes() accepts index_file parameter
- Add config.example.json with all options: host, dj_port, listener_port,
dj_panel_password, secret_key, music_folder, stream_bitrate, max_upload_mb,
cors_origins, debug (copy to config.json to use)
- server.py: drive host/ports/secrets/CORS/upload limit from config.json;
fix serve_static to use allowlist only; move re import to top-level;
fix inconsistent indentation in login/logout/before_request handlers
- script.js: fix undefined decks.crossfader in updateUIFromMixerStatus;
declare mediaRecorder as a proper let variable
- techdj_qt.py: replace blocking time.sleep poll in YTDownloadWorker with
non-blocking QTimer; fix fragile or-chained lambda in recording reset
- Server-side: Added remote URL support in ffmpeg transcoder
- UI: Added relay controls in streaming panel with URL input
- Client: Added start/stop relay functions with socket communication
- Listener: Shows remote relay status in stream indicator