From 2e64870daaa04729b01745dd09f9d4dae766c0d7 Mon Sep 17 00:00:00 2001 From: ComputerTech Date: Wed, 11 Mar 2026 19:34:32 +0000 Subject: [PATCH] UI improvements: glow effects, deck colors, listener count, remove black bar, mobile header fix --- index.html | 11 ++++---- listener.css | 43 ++++++++++++++++--------------- listener.html | 2 +- listener.js | 9 +++++++ script.js | 19 +++++++++++--- server.py | 70 ++++++++++++++++++++++++++++++++++++--------------- style.css | 27 +++++++++++++------- 7 files changed, 121 insertions(+), 60 deletions(-) diff --git a/index.html b/index.html index ba96277..b6fe9e2 100644 --- a/index.html +++ b/index.html @@ -424,10 +424,6 @@
-
- SETTINGS - -
@@ -446,10 +442,15 @@
- +
+
+ + +
{f"
{error}
" if error else ""} -
Set/disable this in config.json (dj_panel_password).
@@ -736,6 +735,23 @@ def dj_start(data=None): def dj_get_listener_count(): emit('listener_count', {'count': len(listener_sids)}) +@dj_socketio.on('listener_glow') +def dj_listener_glow(data): + """DJ sets the glow intensity on the listener page.""" + intensity = int(data.get('intensity', 30)) if isinstance(data, dict) else 30 + intensity = max(0, min(100, intensity)) + listener_socketio.emit('listener_glow', {'intensity': intensity}, namespace='/') + +@dj_socketio.on('deck_glow') +def dj_deck_glow(data): + """Relay which decks are playing so the listener page can mirror the glow colour.""" + if not isinstance(data, dict): + return + listener_socketio.emit('deck_glow', { + 'A': bool(data.get('A', False)), + 'B': bool(data.get('B', False)), + }, namespace='/') + @dj_socketio.on('stop_broadcast') def dj_stop(): broadcast_state['active'] = False @@ -784,34 +800,49 @@ listener_socketio = SocketIO( cors_allowed_origins=CONFIG_CORS, async_mode='eventlet', max_http_buffer_size=CONFIG_MAX_UPLOAD_MB * 1024 * 1024, - ping_timeout=60, - ping_interval=25, + # Lower timeouts: stale connections detected in ~25s instead of ~85s + # ping_interval: how often to probe (seconds) + # ping_timeout: how long to wait for pong before declaring dead + ping_timeout=15, + ping_interval=10, logger=CONFIG_DEBUG, engineio_logger=CONFIG_DEBUG ) +def _broadcast_listener_count(): + """Compute the most accurate listener count and broadcast to both panels. + + Uses the larger of: + - listener_sids: Socket.IO connections (people with the page open) + - _mp3_clients: active /stream.mp3 HTTP connections (people actually hearing audio) + + Taking the max avoids undercounting when someone hasn't clicked Enable Audio + yet, and also avoids undercounting direct stream URL listeners (e.g. VLC). + """ + with _mp3_lock: + stream_count = len(_mp3_clients) + count = max(len(listener_sids), stream_count) + listener_socketio.emit('listener_count', {'count': count}, namespace='/') + dj_socketio.emit('listener_count', {'count': count}, namespace='/') + return count + + @listener_socketio.on('connect') def listener_connect(): - print(f"LISTENER: Listener Socket Connected: {request.sid}") + # Count immediately on connect — don't wait for join_listener + listener_sids.add(request.sid) + count = _broadcast_listener_count() + print(f"LISTENER: Connected {request.sid}. Total: {count}") @listener_socketio.on('disconnect') def listener_disconnect(): listener_sids.discard(request.sid) - count = len(listener_sids) - print(f"REMOVED: Listener left. Total: {count}") - # Notify BOTH namespaces - listener_socketio.emit('listener_count', {'count': count}, namespace='/') - dj_socketio.emit('listener_count', {'count': count}, namespace='/') + count = _broadcast_listener_count() + print(f"REMOVED: Listener left {request.sid}. Total: {count}") @listener_socketio.on('join_listener') def listener_join(): - if request.sid not in listener_sids: - listener_sids.add(request.sid) - count = len(listener_sids) - print(f"LISTENER: New listener joined. Total: {count}") - listener_socketio.emit('listener_count', {'count': count}, namespace='/') - dj_socketio.emit('listener_count', {'count': count}, namespace='/') - + # SID already added in listener_connect(); just send stream status back emit('stream_status', {'active': broadcast_state['active']}) @listener_socketio.on('get_listener_count') @@ -833,12 +864,11 @@ def _transcoder_watchdog(): def _listener_count_sync_loop(): - """Periodic background sync to ensure listener count is always accurate.""" + """Periodic reconciliation — catches any edge cases where connect/disconnect + events were missed (e.g. server under load, eventlet greenlet delays).""" while True: - count = len(listener_sids) - listener_socketio.emit('listener_count', {'count': count}, namespace='/') - dj_socketio.emit('listener_count', {'count': count}, namespace='/') eventlet.sleep(5) + _broadcast_listener_count() if __name__ == '__main__': diff --git a/style.css b/style.css index 46265f0..9e62b2a 100644 --- a/style.css +++ b/style.css @@ -24,6 +24,12 @@ html { scroll-behavior: smooth; + overflow: hidden; + scrollbar-width: none; /* Firefox */ +} + +html::-webkit-scrollbar { + display: none; /* Chrome / Edge / Safari */ } body { @@ -218,7 +224,7 @@ header h1 { grid-template-rows: 1fr 80px; gap: 10px; padding: 10px; - height: calc(100vh - 60px); + height: 100vh; /* Adjust based on header height */ min-height: 0; overflow: hidden; @@ -1706,6 +1712,9 @@ input[type=range] { /* Less intense on mobile */ } + header { + display: none; + } .mobile-top-bar { display: flex; @@ -1916,8 +1925,8 @@ input[type=range] { /* Streaming Button */ .streaming-btn { position: fixed; - bottom: 25px; - right: 175px; + bottom: 175px; + right: 25px; width: 60px; height: 60px; border-radius: 50%; @@ -1947,8 +1956,8 @@ input[type=range] { .upload-btn { position: fixed; - bottom: 25px; - right: 100px; + bottom: 100px; + right: 25px; width: 60px; height: 60px; border-radius: 50%; @@ -1980,7 +1989,7 @@ input[type=range] { .streaming-panel { position: fixed; top: 0; - right: -400px; + right: -460px; height: 100vh; width: 380px; background: rgba(10, 10, 20, 0.98); @@ -2587,8 +2596,8 @@ body.listening-active .landscape-prompt { /* Base Settings Button Fix */ .keyboard-btn { position: fixed; - bottom: 25px; - right: 250px; + bottom: 250px; + right: 25px; width: 60px; height: 60px; border-radius: 50%; @@ -2637,7 +2646,7 @@ body.listening-active .landscape-prompt { .settings-panel { position: fixed; top: 0; - right: -350px; + right: -400px; height: 100vh; width: 320px; background: rgba(10, 10, 20, 0.98);