console.log("App.js caricato - Soft Stop e Auto-Wakeup attivi!"); let ws = null; let audioCtx = null; let gainNode = null; let analyser = null; let dataArray = null; let nextStartTime = 0; const connectBtn = document.getElementById('connectBtn'); const stopBtn = document.getElementById('stopBtn'); const tgInput = document.getElementById('tgInput'); const setTgBtn = document.getElementById('setTgBtn'); const displayScreen = document.getElementById('displayScreen'); const statusText = document.getElementById('statusText'); const volumeSlider = document.getElementById('volumeSlider'); const volumeValue = document.getElementById('volumeValue'); const liveTrafficList = document.getElementById('liveTrafficList'); const vuMeterBar = document.getElementById('vuMeterBar'); if(statusText) statusText.innerText = "✅ Sistema pronto. Clicca Avvia Ricevitore."; if(volumeSlider) { volumeSlider.addEventListener('input', (e) => { const val = parseFloat(e.target.value); if(volumeValue) volumeValue.innerText = Math.round(val * 100) + '%'; if (gainNode) gainNode.gain.value = val; }); } window.setQuickTg = function(valore) { if (tgInput) tgInput.value = valore; inviaCambioTG(valore); }; function inviaCambioTG(nuovoTG) { if (!nuovoTG || nuovoTG < 0) return; // Permette lo zero per lo stop interno if (ws && ws.readyState === WebSocket.OPEN) { // Se l'audio era stato fermato, lo riavvia automaticamente (Auto-Wakeup) if (nuovoTG > 0 && audioCtx && audioCtx.state === 'suspended') { audioCtx.resume(); if(connectBtn) connectBtn.style.display = 'none'; if(stopBtn) stopBtn.style.display = 'block'; } ws.send(JSON.stringify({ action: "cambia_tg", tg: nuovoTG })); if (nuovoTG > 0) { if(displayScreen) { displayScreen.innerText = "SINTONIA..."; displayScreen.style.background = ""; } if(statusText) statusText.innerText = "Sintonizzato su TG " + nuovoTG; } } else { if(statusText) statusText.innerText = "Pronto a sintonizzare TG " + nuovoTG + " (Avvia prima)"; } } function renderVUMeter() { requestAnimationFrame(renderVUMeter); if (!analyser || !vuMeterBar) return; analyser.getByteTimeDomainData(dataArray); let sum = 0; for(let i = 0; i < dataArray.length; i++) { let v = (dataArray[i] - 128) / 128.0; sum += v * v; } let rms = Math.sqrt(sum / dataArray.length); let percent = Math.min(100, rms * 500); vuMeterBar.style.width = percent + '%'; if (percent > 85) vuMeterBar.style.background = '#e74c3c'; else if (percent > 50) vuMeterBar.style.background = '#f1c40f'; else vuMeterBar.style.background = '#2ecc71'; } function playPCM320(arrayBuffer) { if (!audioCtx || !analyser) return; try { const int16Array = new Int16Array(arrayBuffer); const float32Array = new Float32Array(int16Array.length); for (let i = 0; i < int16Array.length; i++) { float32Array[i] = int16Array[i] / 32768.0; } const buffer = audioCtx.createBuffer(1, float32Array.length, 8000); buffer.getChannelData(0).set(float32Array); const source = audioCtx.createBufferSource(); source.buffer = buffer; source.connect(analyser); const currentTime = audioCtx.currentTime; if (nextStartTime < currentTime) nextStartTime = currentTime + 0.02; source.start(nextStartTime); nextStartTime += buffer.duration; } catch(err) { console.error("Errore audio:", err); } } function stopAudio() { // Invia TG 0 per sganciare il flusso audio dal server mantenendo vivo il WebSocket if (ws && ws.readyState === WebSocket.OPEN) { inviaCambioTG(0); } if (audioCtx && audioCtx.state !== 'suspended') { audioCtx.suspend(); } if(connectBtn) connectBtn.style.display = 'block'; if(stopBtn) stopBtn.style.display = 'none'; if(displayScreen) { displayScreen.innerText = "STANDBY"; displayScreen.style.background = ""; } if(statusText) { statusText.innerText = "Audio disattivato. Monitor di rete attivo."; statusText.style.color = "#bdc3c7"; } } function startAudio() { try { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); analyser = audioCtx.createAnalyser(); analyser.fftSize = 256; dataArray = new Uint8Array(analyser.frequencyBinCount); gainNode = audioCtx.createGain(); analyser.connect(gainNode); gainNode.connect(audioCtx.destination); if(volumeSlider) gainNode.gain.value = parseFloat(volumeSlider.value); renderVUMeter(); } if (audioCtx.state === 'suspended') audioCtx.resume(); // Se il WebSocket è già aperto (uscita dallo stato di Stop) if (ws && ws.readyState === WebSocket.OPEN) { if(connectBtn) connectBtn.style.display = 'none'; if(stopBtn) stopBtn.style.display = 'block'; if(tgInput) inviaCambioTG(tgInput.value); return; } if(statusText) statusText.innerText = "🔄 Connessione in corso..."; const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; ws = new WebSocket(`${protocol}//${window.location.host}/ws`); ws.binaryType = 'arraybuffer'; ws.onopen = () => { if(connectBtn) connectBtn.style.display = 'none'; if(stopBtn) stopBtn.style.display = 'block'; if(tgInput) inviaCambioTG(tgInput.value); }; ws.onmessage = (event) => { if (event.data instanceof ArrayBuffer) { playPCM320(event.data); } else if (typeof event.data === 'string') { const data = JSON.parse(event.data); if (data.action === "error") return; if (data.action === "display") { if(displayScreen) { displayScreen.innerText = data.text; displayScreen.style.background = data.active ? "#e67e22" : ""; } if(statusText) statusText.style.color = "#bdc3c7"; } if (data.action === "global_traffic" && liveTrafficList) { if (!data.data || data.data.length === 0) { liveTrafficList.innerHTML = "Nessun traffico in corso..."; } else { liveTrafficList.innerHTML = data.data.map(item => `
TG ${item.tg}: ${item.src}
` ).join(""); } } } }; ws.onclose = () => { if(statusText) statusText.innerText = "❌ Connessione persa."; if(connectBtn) connectBtn.style.display = 'block'; if(stopBtn) stopBtn.style.display = 'none'; }; } catch (err) { console.error("Errore in avvio:", err); } } if(connectBtn) connectBtn.addEventListener('click', startAudio); if(stopBtn) stopBtn.addEventListener('click', stopAudio); if(setTgBtn) setTgBtn.addEventListener('click', () => { if(tgInput) inviaCambioTG(tgInput.value); }); if(tgInput) { tgInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') inviaCambioTG(tgInput.value); }); }