Upload files to "/"

This commit is contained in:
2026-01-02 18:31:55 +00:00
commit ec1735b9e3
5 changed files with 7379 additions and 0 deletions

470
index.html Normal file
View File

@@ -0,0 +1,470 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>TechDJ Pro</title>
<link rel="stylesheet" href="style.css?v=7.0">
</head>
<body>
<div id="start-overlay">
<h1 class="overlay-title">TECHDJ PROTOCOL</h1>
<button id="start-btn" onclick="initSystem()">INITIALIZE SYSTEM</button>
<p style="color:#666; margin-top:20px; font-family:'Rajdhani'">v2.0 // NEON CORE</p>
</div>
<!-- Landscape Orientation Prompt -->
<div class="landscape-prompt" id="landscape-prompt">
<div class="rotate-icon">📱→🔄</div>
<h2>OPTIMAL ORIENTATION</h2>
<p>For the best DJ experience, please rotate your device to <strong>landscape mode</strong> (sideways).</p>
<p style="font-size: 0.9rem; color: #888;">Both decks and the crossfader will be visible simultaneously.</p>
<button onclick="dismissLandscapePrompt()">GOT IT</button>
</div>
<!-- MAIN APP CONTAINER -->
<div class="app-container">
<!-- Mobile Tabs -->
<nav class="mobile-tabs">
<button class="tab-btn active" onclick="switchTab('library')">
<span class="tab-icon">📁</span>
<span>LIBRARY</span>
</button>
<button class="tab-btn" onclick="switchTab('deck-A')">
<span class="tab-icon">💿</span>
<span>DECK A</span>
</button>
<button class="tab-btn" onclick="switchTab('deck-B')">
<span class="tab-icon">💿</span>
<span>DECK B</span>
</button>
</nav>
<!-- 1. LEFT: LIBRARY -->
<section class="library-section">
<div class="lib-header">
<input type="text" id="lib-search" placeholder="🔍 FILTER LIBRARY..." onkeyup="filterLibrary()">
<button class="refresh-btn" onclick="refreshLibrary()" title="Refresh Library">🔄</button>
</div>
<!-- YouTube Search -->
<div class="youtube-search-container">
<input type="text" id="youtube-search" placeholder="🎵 SEARCH YOUTUBE..."
onkeyup="handleYouTubeSearch(this.value)">
<div id="youtube-results" class="youtube-results"></div>
</div>
<div id="library-list" class="library-list">
<!-- Tracks go here -->
</div>
</section>
<!-- 2. MIDDLE: DECK A -->
<div class="deck" id="deck-A">
<div class="deck-header">
<span class="deck-title title-a">DECK A</span>
<span class="track-display" id="display-A">NO TRACK LOADED</span>
</div>
<!-- Waveform Display -->
<div class="waveform-container">
<canvas id="waveform-A" class="waveform-canvas"></canvas>
<div class="playhead" id="playhead-A"></div>
<div class="time-display">
<span id="time-current-A">0:00</span> / <span id="time-total-A">0:00</span>
<span class="bpm-display" id="bpm-A"></span>
</div>
</div>
<div class="disk-container">
<div class="dj-disk" id="disk-A">
<div class="disk-label">A</div>
</div>
</div>
<canvas id="viz-A" width="400" height="100"></canvas>
<!-- Hot Cues -->
<div class="hot-cues">
<button class="cue-btn cue-1" onclick="handleCue('A', 1)"
oncontextmenu="clearCue('A', 1); return false;">CUE 1</button>
<button class="cue-btn cue-2" onclick="handleCue('A', 2)"
oncontextmenu="clearCue('A', 2); return false;">CUE 2</button>
<button class="cue-btn cue-3" onclick="handleCue('A', 3)"
oncontextmenu="clearCue('A', 3); return false;">CUE 3</button>
<button class="cue-btn cue-4" onclick="handleCue('A', 4)"
oncontextmenu="clearCue('A', 4); return false;">CUE 4</button>
</div>
<!-- Loop Controls -->
<div class="loop-controls">
<button class="loop-btn" onclick="setLoop('A', 'in')">LOOP IN</button>
<button class="loop-btn" onclick="setLoop('A', 'out')">LOOP OUT</button>
<button class="loop-btn" onclick="setLoop('A', 'exit')">EXIT</button>
</div>
<!-- Auto-Loop Beat Controls -->
<div class="auto-loop-controls">
<label class="auto-loop-label">AUTO-LOOP</label>
<div class="auto-loop-buttons">
<button class="auto-loop-btn" data-beats="8" onclick="setAutoLoop('A', 8)">8</button>
<button class="auto-loop-btn" data-beats="4" onclick="setAutoLoop('A', 4)">4</button>
<button class="auto-loop-btn" data-beats="2" onclick="setAutoLoop('A', 2)">2</button>
<button class="auto-loop-btn" data-beats="1" onclick="setAutoLoop('A', 1)">1</button>
<button class="auto-loop-btn" data-beats="0.5" onclick="setAutoLoop('A', 0.5)">1/2</button>
<button class="auto-loop-btn" data-beats="0.25" onclick="setAutoLoop('A', 0.25)">1/4</button>
<button class="auto-loop-btn" data-beats="0.125" onclick="setAutoLoop('A', 0.125)">1/8</button>
</div>
</div>
<div class="start-group">
<input type="text" class="search-input" id="search-input-A" placeholder="PASTE YOUTUBE URL...">
<div class="download-controls">
<select id="quality-A" class="quality-selector">
<option value="128">128kbps</option>
<option value="192">192kbps</option>
<option value="320" selected>320kbps</option>
</select>
<button class="download-btn" onclick="downloadFromPanel('A')">⬇ DOWNLOAD</button>
</div>
<div id="download-status-A" class="download-status"></div>
</div>
<div class="controls-grid">
<!-- Volume Fader -->
<div class="fader-group">
<label>VOLUME</label>
<input type="range" orient="vertical" class="volume-fader" min="0" max="100" value="80"
oninput="changeVolume('A', this.value)">
</div>
<!-- EQ -->
<div class="eq-container">
<div class="eq-band"><input type="range" orient="vertical" min="-20" max="20" value="0"
oninput="changeEQ('A', 'high', this.value)"><label>HI</label></div>
<div class="eq-band"><input type="range" orient="vertical" min="-20" max="20" value="0"
oninput="changeEQ('A', 'mid', this.value)"><label>MID</label></div>
<div class="eq-band"><input type="range" orient="vertical" min="-20" max="20" value="0"
oninput="changeEQ('A', 'low', this.value)"><label>LO</label></div>
</div>
<!-- Filters -->
<div class="filter-knobs">
<div class="filter-knob">
<label>LOW-PASS</label>
<input type="range" class="filter-slider filter-lp" min="0" max="100" value="100"
oninput="changeFilter('A', 'lowpass', this.value)">
</div>
<div class="filter-knob">
<label>HIGH-PASS</label>
<input type="range" class="filter-slider filter-hp" min="0" max="100" value="0"
oninput="changeFilter('A', 'highpass', this.value)">
</div>
</div>
<!-- Pitch/Tempo -->
<div class="fader-group">
<label>PITCH / TEMPO</label>
<input type="range" class="speed-slider" min="0.5" max="1.5" step="0.01" value="1"
oninput="changeSpeed('A', this.value)" ondblclick="this.value=1; changeSpeed('A', 1);">
<div class="pitch-bend-buttons">
<button class="pitch-bend" onmousedown="pitchBend('A', -0.05)" onmouseup="pitchBend('A', 0)"
onmouseleave="pitchBend('A', 0)">-</button>
<button class="pitch-bend" onmousedown="pitchBend('A', 0.05)" onmouseup="pitchBend('A', 0)"
onmouseleave="pitchBend('A', 0)">+</button>
</div>
</div>
</div>
<div class="transport">
<button class="big-btn play-btn" onclick="playDeck('A')">PLAY</button>
<button class="big-btn pause-btn" onclick="pauseDeck('A')">PAUSE</button>
<button class="big-btn sync-btn" onclick="syncDecks('A')">SYNC</button>
<button class="big-btn reset-btn" onclick="resetDeck('A')" title="Reset all settings to default">🔄
RESET</button>
</div>
<!-- QUEUE for Deck A -->
<div class="queue-panel" id="queue-panel-A">
<div class="queue-header">
<span class="queue-title">📋 QUEUE A</span>
<button class="queue-clear-btn" onclick="clearQueue('A')" title="Clear queue">🗑️</button>
</div>
<div class="queue-list" id="queue-list-A">
<div class="queue-empty">Drop tracks here or click "Queue to A" in library</div>
</div>
</div>
</div>
<!-- 3. RIGHT: DECK B -->
<div class="deck" id="deck-B">
<div class="deck-header">
<span class="deck-title title-b">DECK B</span>
<span class="track-display" id="display-B">NO TRACK LOADED</span>
</div>
<!-- Waveform Display -->
<div class="waveform-container">
<canvas id="waveform-B" class="waveform-canvas"></canvas>
<div class="playhead" id="playhead-B"></div>
<div class="time-display">
<span id="time-current-B">0:00</span> / <span id="time-total-B">0:00</span>
<span class="bpm-display" id="bpm-B"></span>
</div>
</div>
<div class="disk-container">
<div class="dj-disk" id="disk-B">
<div class="disk-label">B</div>
</div>
</div>
<canvas id="viz-B" width="400" height="100"></canvas>
<!-- Hot Cues -->
<div class="hot-cues">
<button class="cue-btn cue-1" onclick="handleCue('B', 1)"
oncontextmenu="clearCue('B', 1); return false;">CUE 1</button>
<button class="cue-btn cue-2" onclick="handleCue('B', 2)"
oncontextmenu="clearCue('B', 2); return false;">CUE 2</button>
<button class="cue-btn cue-3" onclick="handleCue('B', 3)"
oncontextmenu="clearCue('B', 3); return false;">CUE 3</button>
<button class="cue-btn cue-4" onclick="handleCue('B', 4)"
oncontextmenu="clearCue('B', 4); return false;">CUE 4</button>
</div>
<!-- Loop Controls -->
<div class="loop-controls">
<button class="loop-btn" onclick="setLoop('B', 'in')">LOOP IN</button>
<button class="loop-btn" onclick="setLoop('B', 'out')">LOOP OUT</button>
<button class="loop-btn" onclick="setLoop('B', 'exit')">EXIT</button>
</div>
<!-- Auto-Loop Beat Controls -->
<div class="auto-loop-controls">
<label class="auto-loop-label">AUTO-LOOP</label>
<div class="auto-loop-buttons">
<button class="auto-loop-btn" data-beats="8" onclick="setAutoLoop('B', 8)">8</button>
<button class="auto-loop-btn" data-beats="4" onclick="setAutoLoop('B', 4)">4</button>
<button class="auto-loop-btn" data-beats="2" onclick="setAutoLoop('B', 2)">2</button>
<button class="auto-loop-btn" data-beats="1" onclick="setAutoLoop('B', 1)">1</button>
<button class="auto-loop-btn" data-beats="0.5" onclick="setAutoLoop('B', 0.5)">1/2</button>
<button class="auto-loop-btn" data-beats="0.25" onclick="setAutoLoop('B', 0.25)">1/4</button>
<button class="auto-loop-btn" data-beats="0.125" onclick="setAutoLoop('B', 0.125)">1/8</button>
</div>
</div>
<div class="start-group">
<input type="text" class="search-input" id="search-input-B" placeholder="PASTE YOUTUBE URL...">
<div class="download-controls">
<select id="quality-B" class="quality-selector">
<option value="128">128kbps</option>
<option value="192">192kbps</option>
<option value="320" selected>320kbps</option>
</select>
<button class="download-btn" onclick="downloadFromPanel('B')">⬇ DOWNLOAD</button>
</div>
<div id="download-status-B" class="download-status"></div>
</div>
<div class="controls-grid">
<!-- Volume Fader -->
<div class="fader-group">
<label>VOLUME</label>
<input type="range" orient="vertical" class="volume-fader" min="0" max="100" value="80"
oninput="changeVolume('B', this.value)">
</div>
<!-- EQ -->
<div class="eq-container">
<div class="eq-band"><input type="range" orient="vertical" min="-20" max="20" value="0"
oninput="changeEQ('B', 'high', this.value)"><label>HI</label></div>
<div class="eq-band"><input type="range" orient="vertical" min="-20" max="20" value="0"
oninput="changeEQ('B', 'mid', this.value)"><label>MID</label></div>
<div class="eq-band"><input type="range" orient="vertical" min="-20" max="20" value="0"
oninput="changeEQ('B', 'low', this.value)"><label>LO</label></div>
</div>
<!-- Filters -->
<div class="filter-knobs">
<div class="filter-knob">
<label>LOW-PASS</label>
<input type="range" class="filter-slider filter-lp" min="0" max="100" value="100"
oninput="changeFilter('B', 'lowpass', this.value)">
</div>
<div class="filter-knob">
<label>HIGH-PASS</label>
<input type="range" class="filter-slider filter-hp" min="0" max="100" value="0"
oninput="changeFilter('B', 'highpass', this.value)">
</div>
</div>
<!-- Pitch/Tempo -->
<div class="fader-group">
<label>PITCH / TEMPO</label>
<input type="range" class="speed-slider" min="0.5" max="1.5" step="0.01" value="1"
oninput="changeSpeed('B', this.value)" ondblclick="this.value=1; changeSpeed('B', 1);">
<div class="pitch-bend-buttons">
<button class="pitch-bend" onmousedown="pitchBend('B', -0.05)" onmouseup="pitchBend('B', 0)"
onmouseleave="pitchBend('B', 0)">-</button>
<button class="pitch-bend" onmousedown="pitchBend('B', 0.05)" onmouseup="pitchBend('B', 0)"
onmouseleave="pitchBend('B', 0)">+</button>
</div>
</div>
</div>
<div class="transport">
<button class="big-btn play-btn" onclick="playDeck('B')">PLAY</button>
<button class="big-btn pause-btn" onclick="pauseDeck('B')">PAUSE</button>
<button class="big-btn sync-btn" onclick="syncDecks('B')">SYNC</button>
<button class="big-btn reset-btn" onclick="resetDeck('B')" title="Reset all settings to default">🔄
RESET</button>
</div>
<!-- QUEUE for Deck B -->
<div class="queue-panel" id="queue-panel-B">
<div class="queue-header">
<span class="queue-title">📋 QUEUE B</span>
<button class="queue-clear-btn" onclick="clearQueue('B')" title="Clear queue">🗑️</button>
</div>
<div class="queue-list" id="queue-list-B">
<div class="queue-empty">Drop tracks here or click "Queue to B" in library</div>
</div>
</div>
</div>
<!-- 4. BOTTOM: CROSSFADER -->
<div class="mixer-section">
<input type="range" class="xfader" id="crossfader" min="0" max="100" value="50"
oninput="updateCrossfader(this.value)">
</div>
</div>
<!-- Live Streaming Panel -->
<div class="streaming-panel" id="streaming-panel">
<div class="streaming-header">
<span>📡 LIVE STREAM</span>
<button class="close-streaming" onclick="toggleStreamingPanel()"></button>
</div>
<div class="streaming-content">
<div class="broadcast-controls">
<button class="broadcast-btn" id="broadcast-btn" onclick="toggleBroadcast()">
<span class="broadcast-icon">🔴</span>
<span id="broadcast-text">START BROADCAST</span>
</button>
<div class="broadcast-status" id="broadcast-status">Offline</div>
</div>
<div class="listener-info">
<div class="listener-count">
<span class="count-icon">👂</span>
<span id="listener-count">0</span>
<span class="count-label">Listeners</span>
</div>
</div>
<div class="stream-url-section">
<label>Share this URL:</label>
<div class="url-copy-group">
<input type="text" id="stream-url" readonly value="http://localhost:5000?listen=true">
<button onclick="copyStreamUrl()" class="copy-btn">📋</button>
</div>
</div>
<div class="stream-settings">
<label>
<input type="checkbox" id="auto-start-stream" onchange="toggleAutoStream(this.checked)">
Auto-start on play
</label>
<div class="quality-selector-group">
<label for="stream-quality">Stream Quality:</label>
<select id="stream-quality" class="stream-quality-select">
<option value="128">High (128kbps)</option>
<option value="96" selected>Medium (96kbps)</option>
<option value="64">Low (64kbps)</option>
<option value="48">Very Low (48kbps)</option>
<option value="32">Minimum (32kbps)</option>
</select>
<span class="quality-hint">Lower = more stable on poor connections</span>
</div>
</div>
</div>
</div>
<!-- Listener Mode (Hidden by default) -->
<div class="listener-mode" id="listener-mode" style="display: none;">
<div class="listener-header">
<h1>🎧 TECHDJ LIVE</h1>
<div class="live-indicator">
<span class="pulse-dot"></span>
<span>LIVE</span>
</div>
</div>
<div class="listener-content">
<div class="now-playing" id="listener-now-playing">Waiting for stream...</div>
<!-- Enable Audio Button (shown when autoplay is blocked) -->
<button class="enable-audio-btn" id="enable-audio-btn" style="display: none;"
onclick="enableListenerAudio()">
<span class="audio-icon">🔊</span>
<span class="audio-text">ENABLE AUDIO</span>
<span class="audio-subtitle">Click to start listening</span>
</button>
<div class="volume-control">
<label>🔊 Volume</label>
<input type="range" id="listener-volume" min="0" max="100" value="80"
oninput="setListenerVolume(this.value)">
</div>
<div class="connection-status" id="connection-status">Connecting...</div>
</div>
</div>
<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
<script src="script.js"></script>
<!-- Settings Panel -->
<div class="settings-panel" id="settings-panel">
<div class="settings-header">
<span>⚙️ SETTINGS</span>
<button class="close-settings" onclick="toggleSettings()"></button>
</div>
<div class="settings-content">
<div class="setting-item"><label><input type="checkbox" id="repeat-A"
onchange="toggleRepeat('A', this.checked)">🔁 Repeat Deck A</label></div>
<div class="setting-item"><label><input type="checkbox" id="repeat-B"
onchange="toggleRepeat('B', this.checked)"><EFBFBD><EFBFBD> Repeat Deck B</label></div>
<div class="setting-item"><label><input type="checkbox" id="auto-mix"
onchange="toggleAutoMix(this.checked)">🎛️ Auto-Crossfade</label></div>
<div class="setting-item"><label><input type="checkbox" id="shuffle-mode"
onchange="toggleShuffle(this.checked)">🔀 Shuffle Library</label></div>
<div class="setting-item"><label><input type="checkbox" id="quantize"
onchange="toggleQuantize(this.checked)">📐 Quantize</label></div>
<div class="setting-item"><label><input type="checkbox" id="auto-play" checked
onchange="toggleAutoPlay(this.checked)">▶️ Auto-play next</label></div>
<div class="setting-item"><label><input type="checkbox" id="glow-A"
onchange="updateManualGlow('A', this.checked)">✨ Glow Deck A (Cyan)</label></div>
<div class="setting-item"><label><input type="checkbox" id="glow-B"
onchange="updateManualGlow('B', this.checked)">✨ Glow Deck B (Magenta)</label></div>
<div class="setting-item" style="flex-direction: column; align-items: flex-start;">
<label>✨ Glow Intensity</label>
<input type="range" id="glow-intensity" min="1" max="100" value="30" style="width: 100%;"
oninput="updateGlowIntensity(this.value)">
</div>
</div>
</div>
<button class="keyboard-btn" onclick="openKeyboardSettings()" title="Keyboard Shortcuts (H)">⌨️</button>
<button class="streaming-btn" onclick="toggleStreamingPanel()" title="Live Streaming">📡</button>
<button class="settings-btn" onclick="toggleSettings()">⚙️</button>
</body>
</html>

7
requirements.txt Normal file
View File

@@ -0,0 +1,7 @@
# TechDJ Requirements
flask
flask-socketio
yt-dlp
eventlet
python-dotenv

2895
script.js Normal file

File diff suppressed because it is too large Load Diff

244
server.py Normal file
View File

@@ -0,0 +1,244 @@
# Monkey patch MUST be first - before any other imports!
import eventlet
eventlet.monkey_patch()
import os
from flask import Flask, send_from_directory, jsonify, request, session
from flask_socketio import SocketIO, emit
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
import downloader
# Relay State
broadcast_state = {
'active': False
}
listener_count = 0
MUSIC_FOLDER = "music"
# Ensure music folder exists
if not os.path.exists(MUSIC_FOLDER):
os.makedirs(MUSIC_FOLDER)
# Helper for shared routes
def setup_shared_routes(app):
@app.route('/library.json')
def get_library():
library = []
if os.path.exists(MUSIC_FOLDER):
for filename in sorted(os.listdir(MUSIC_FOLDER)):
if filename.lower().endswith(('.mp3', '.m4a', '.wav', '.flac', '.ogg')):
library.append({
"title": os.path.splitext(filename)[0],
"file": f"music/{filename}"
})
return jsonify(library)
@app.route('/download', methods=['POST'])
def download():
data = request.json
url = data.get('url')
quality = data.get('quality', '320')
if not url:
return jsonify({"success": False, "error": "No URL provided"}), 400
result = downloader.download_mp3(url, quality)
return jsonify(result)
@app.route('/search_youtube', methods=['GET'])
def search_youtube():
query = request.args.get('q', '')
if not query:
return jsonify({"success": False, "error": "No query provided"}), 400
# Get API key from environment variable
api_key = os.environ.get('YOUTUBE_API_KEY', '')
if not api_key:
return jsonify({
"success": False,
"error": "YouTube API key not configured. Set YOUTUBE_API_KEY environment variable."
}), 500
try:
import requests
# Search YouTube using Data API v3
url = 'https://www.googleapis.com/youtube/v3/search'
params = {
'part': 'snippet',
'q': query,
'type': 'video',
'videoCategoryId': '10', # Music category
'maxResults': 20,
'key': api_key
}
response = requests.get(url, params=params)
data = response.json()
if 'error' in data:
return jsonify({
"success": False,
"error": data['error'].get('message', 'YouTube API error')
}), 400
# Format results
results = []
for item in data.get('items', []):
results.append({
'videoId': item['id']['videoId'],
'title': item['snippet']['title'],
'channel': item['snippet']['channelTitle'],
'thumbnail': item['snippet']['thumbnails']['medium']['url'],
'url': f"https://www.youtube.com/watch?v={item['id']['videoId']}"
})
return jsonify({"success": True, "results": results})
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 500
@app.route('/<path:filename>')
def serve_static(filename):
response = send_from_directory('.', filename)
if filename.endswith(('.css', '.js', '.html')):
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
return response
@app.route('/')
def index():
return send_from_directory('.', 'index.html')
# === DJ SERVER (Port 5000) ===
dj_app = Flask(__name__, static_folder='.', static_url_path='')
dj_app.config['SECRET_KEY'] = 'dj_panel_secret'
setup_shared_routes(dj_app)
dj_socketio = SocketIO(
dj_app,
cors_allowed_origins="*",
async_mode='eventlet',
max_http_buffer_size=1e8, # 100MB buffer
ping_timeout=10,
ping_interval=5,
logger=False,
engineio_logger=False
)
@dj_socketio.on('connect')
def dj_connect():
print(f"🎧 DJ connected: {request.sid}")
session['is_dj'] = True
@dj_socketio.on('disconnect')
def dj_disconnect():
if session.get('is_dj'):
print("⚠️ DJ disconnected - broadcast will continue until manually stopped")
session['is_dj'] = False
# Don't stop streaming_active - let it continue
# Broadcast will resume when DJ reconnects
def stop_broadcast_after_timeout():
"""No longer used - broadcasts don't auto-stop"""
pass
@dj_socketio.on('start_broadcast')
def dj_start():
broadcast_state['active'] = True
session['is_dj'] = True
print("🎙️ Broadcast -> ACTIVE")
listener_socketio.emit('broadcast_started', namespace='/')
listener_socketio.emit('stream_status', {'active': True}, namespace='/')
@dj_socketio.on('stop_broadcast')
def dj_stop():
broadcast_state['active'] = False
session['is_dj'] = False
print("🛑 DJ stopped broadcasting")
listener_socketio.emit('broadcast_stopped', namespace='/')
listener_socketio.emit('stream_status', {'active': False}, namespace='/')
@dj_socketio.on('audio_chunk')
def dj_audio(data):
# Relay audio chunk to all listeners immediately
if broadcast_state['active']:
listener_socketio.emit('audio_data', data, namespace='/')
# === LISTENER SERVER (Port 6000) ===
listener_app = Flask(__name__, static_folder='.', static_url_path='')
listener_app.config['SECRET_KEY'] = 'listener_secret'
setup_shared_routes(listener_app)
listener_socketio = SocketIO(
listener_app,
cors_allowed_origins="*",
async_mode='eventlet',
max_http_buffer_size=1e8, # 100MB buffer
ping_timeout=10,
ping_interval=5,
logger=False,
engineio_logger=False
)
@listener_socketio.on('connect')
def listener_connect():
print(f"👂 Listener Socket Connected: {request.sid}")
@listener_socketio.on('disconnect')
def listener_disconnect():
global listener_count
if session.get('is_listener'):
# Clear session flag FIRST to prevent re-entry issues
session['is_listener'] = False
listener_count = max(0, listener_count - 1)
print(f"❌ Listener left. Total: {listener_count}")
# Broadcast to all listeners
listener_socketio.emit('listener_count', {'count': listener_count}, namespace='/')
# Broadcast to all DJs
dj_socketio.emit('listener_count', {'count': listener_count}, namespace='/')
@listener_socketio.on('join_listener')
def listener_join():
global listener_count
if not session.get('is_listener'):
session['is_listener'] = True
listener_count += 1
print(f"👂 New listener joined. Total: {listener_count}")
# Broadcast to all listeners
listener_socketio.emit('listener_count', {'count': listener_count}, namespace='/')
# Broadcast to all DJs
dj_socketio.emit('listener_count', {'count': listener_count}, namespace='/')
emit('stream_status', {'active': broadcast_state['active']})
@listener_socketio.on('request_header')
def handle_request_header():
# Header logic removed for local relay mode
pass
@listener_socketio.on('get_listener_count')
def listener_get_count():
emit('listener_count', {'count': listener_count})
# DJ Panel Routes (No engine commands needed in local mode)
@dj_socketio.on('get_mixer_status')
def get_mixer_status():
pass
@dj_socketio.on('audio_sync_queue')
def audio_sync_queue(data):
pass
if __name__ == '__main__':
print("=" * 50)
print("🎧 TECHDJ PRO - DUAL PORT ARCHITECTURE")
print("=" * 50)
print("👉 DJ PANEL: http://localhost:5000")
print("👉 LISTEN PAGE: http://localhost:5001")
print("=" * 50)
# Audio engine DISABLED
print("✅ Local Radio server ready")
# Run both servers using eventlet's spawn
eventlet.spawn(dj_socketio.run, dj_app, host='0.0.0.0', port=5000, debug=False)
listener_socketio.run(listener_app, host='0.0.0.0', port=5001, debug=False)

3763
style.css Normal file

File diff suppressed because it is too large Load Diff