Enhanced Dashboard
This commit is contained in:
+143
-114
@@ -1,6 +1,10 @@
|
||||
console.log("App.js caricato - VU Meter & Click-to-Tune attivi!");
|
||||
|
||||
let ws = null;
|
||||
let audioCtx = null;
|
||||
let gainNode = null; // Il controller del volume
|
||||
let gainNode = null;
|
||||
let analyser = null;
|
||||
let dataArray = null;
|
||||
let nextStartTime = 0;
|
||||
|
||||
const connectBtn = document.getElementById('connectBtn');
|
||||
@@ -8,135 +12,160 @@ const tgInput = document.getElementById('tgInput');
|
||||
const setTgBtn = document.getElementById('setTgBtn');
|
||||
const displayScreen = document.getElementById('displayScreen');
|
||||
const statusText = document.getElementById('statusText');
|
||||
|
||||
// --- CONTROLLI VOLUME ---
|
||||
const volumeSlider = document.getElementById('volumeSlider');
|
||||
const volumeValue = document.getElementById('volumeValue');
|
||||
const liveTrafficList = document.getElementById('liveTrafficList');
|
||||
const vuMeterBar = document.getElementById('vuMeterBar');
|
||||
|
||||
volumeSlider.addEventListener('input', (e) => {
|
||||
const val = parseFloat(e.target.value);
|
||||
volumeValue.innerText = Math.round(val * 100) + '%';
|
||||
|
||||
// Modifica il volume in tempo reale
|
||||
if (gainNode) {
|
||||
gainNode.gain.value = val;
|
||||
}
|
||||
});
|
||||
if(statusText) statusText.innerText = "✅ Sistema pronto. Clicca Avvia Ascolto.";
|
||||
|
||||
// --- RIPRODUZIONE AUDIO PCM ---
|
||||
function playPCM320(arrayBuffer) {
|
||||
if (!audioCtx || !gainNode) return;
|
||||
|
||||
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;
|
||||
|
||||
// Colleghiamo l'audio al controller del volume invece che direttamente all'uscita
|
||||
source.connect(gainNode);
|
||||
|
||||
const currentTime = audioCtx.currentTime;
|
||||
if (nextStartTime < currentTime) {
|
||||
nextStartTime = currentTime + 0.02;
|
||||
}
|
||||
source.start(nextStartTime);
|
||||
nextStartTime += buffer.duration;
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
// --- CONNESSIONE WEBSOCKET ---
|
||||
function startAudio() {
|
||||
if (!audioCtx) {
|
||||
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
|
||||
// Creazione del nodo Volume
|
||||
gainNode = audioCtx.createGain();
|
||||
gainNode.connect(audioCtx.destination);
|
||||
gainNode.gain.value = parseFloat(volumeSlider.value);
|
||||
}
|
||||
if (audioCtx.state === 'suspended') {
|
||||
audioCtx.resume();
|
||||
}
|
||||
window.setQuickTg = function(valore) {
|
||||
if (tgInput) tgInput.value = valore;
|
||||
inviaCambioTG(valore);
|
||||
};
|
||||
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
ws = new WebSocket(`${protocol}//${window.location.host}/ws`);
|
||||
ws.binaryType = 'arraybuffer';
|
||||
|
||||
ws.onopen = () => {
|
||||
connectBtn.style.display = 'none';
|
||||
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);
|
||||
|
||||
// --- GESTIONE ERRORE SERVER PIENO ---
|
||||
if (data.action === "error") {
|
||||
displayScreen.innerText = "SERVER PIENO";
|
||||
displayScreen.style.background = "#e74c3c"; // Rosso[cite: 5]
|
||||
statusText.innerText = "❌ " + data.text;
|
||||
statusText.style.color = "#e74c3c";
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.action === "display") {
|
||||
displayScreen.innerText = data.text;
|
||||
statusText.style.color = "#bdc3c7"; // Ripristina il colore di default del testo[cite: 5]
|
||||
if (data.active) {
|
||||
displayScreen.style.background = "#e67e22"; // Arancione durante RX[cite: 5]
|
||||
} else {
|
||||
displayScreen.style.background = "#1abc9c"; // Verde in Standby[cite: 5]
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
// Se il display non è in stato di errore "SERVER PIENO", mostra la riconnessione standard[cite: 5]
|
||||
if (displayScreen.innerText !== "SERVER PIENO") {
|
||||
statusText.innerText = "Connessione persa. Riconnessione...";
|
||||
statusText.style.color = "#bdc3c7";
|
||||
connectBtn.style.display = 'block';
|
||||
connectBtn.innerText = "Riconnetti Ricevitore";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// --- FUNZIONE PER CAMBIARE TG ---
|
||||
function inviaCambioTG(nuovoTG) {
|
||||
if (!nuovoTG || nuovoTG <= 0) return;
|
||||
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({ action: "cambia_tg", tg: nuovoTG }));
|
||||
displayScreen.innerText = "SINTONIA...";
|
||||
statusText.innerText = "Sintonizzato su TG " + nuovoTG;
|
||||
if(displayScreen) displayScreen.innerText = "SINTONIA...";
|
||||
if(statusText) statusText.innerText = "Sintonizzato su TG " + nuovoTG;
|
||||
} else {
|
||||
statusText.innerText = "Pronto a sintonizzare TG " + nuovoTG;
|
||||
if(statusText) statusText.innerText = "Pronto a sintonizzare TG " + nuovoTG + " (Avvia prima)";
|
||||
}
|
||||
}
|
||||
|
||||
// Scelta Rapida dai tasti prefissati
|
||||
function setQuickTg(valore) {
|
||||
tgInput.value = valore;
|
||||
inviaCambioTG(valore);
|
||||
// --- VU METER LOOP ---
|
||||
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); // Moltiplicatore di sensibilità grafica
|
||||
|
||||
vuMeterBar.style.width = percent + '%';
|
||||
if (percent > 85) vuMeterBar.style.background = '#e74c3c'; // Rosso saturo
|
||||
else if (percent > 50) vuMeterBar.style.background = '#f1c40f'; // Giallo
|
||||
else vuMeterBar.style.background = '#2ecc71'; // Verde standard
|
||||
}
|
||||
|
||||
// --- EVENTI ---
|
||||
connectBtn.addEventListener('click', startAudio);
|
||||
setTgBtn.addEventListener('click', () => inviaCambioTG(tgInput.value));
|
||||
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;
|
||||
}
|
||||
|
||||
// Invio anche premendo "Invio" sulla tastiera dentro la casella di testo
|
||||
tgInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
inviaCambioTG(tgInput.value);
|
||||
const buffer = audioCtx.createBuffer(1, float32Array.length, 8000);
|
||||
buffer.getChannelData(0).set(float32Array);
|
||||
|
||||
const source = audioCtx.createBufferSource();
|
||||
source.buffer = buffer;
|
||||
|
||||
// Collega al VU meter, che a sua volta è collegato al volume
|
||||
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 startAudio() {
|
||||
try {
|
||||
if (!audioCtx) {
|
||||
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
|
||||
analyser = audioCtx.createAnalyser();
|
||||
analyser.fftSize = 256;
|
||||
dataArray = new Uint8Array(analyser.frequencyBinCount);
|
||||
|
||||
gainNode = audioCtx.createGain();
|
||||
|
||||
// Catena: Source -> Analyser(VU) -> Gain(Volume) -> Casse
|
||||
analyser.connect(gainNode);
|
||||
gainNode.connect(audioCtx.destination);
|
||||
|
||||
if(volumeSlider) gainNode.gain.value = parseFloat(volumeSlider.value);
|
||||
|
||||
renderVUMeter(); // Avvia l'animazione grafica
|
||||
}
|
||||
if (audioCtx.state === 'suspended') audioCtx.resume();
|
||||
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(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" : "#1abc9c";
|
||||
}
|
||||
if(statusText) statusText.style.color = "#bdc3c7";
|
||||
}
|
||||
|
||||
// CLICK-TO-TUNE IMPLEMENTATO QUI
|
||||
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 =>
|
||||
`<div class="traffic-item traffic-clickable" onclick="setQuickTg(${item.tg})" title="Clicca per sintonizzare TG ${item.tg}">
|
||||
<b>TG ${item.tg}</b>: ${item.src}
|
||||
</div>`
|
||||
).join("");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
if(statusText) statusText.innerText = "❌ Connessione persa.";
|
||||
if(connectBtn) connectBtn.style.display = 'block';
|
||||
};
|
||||
|
||||
} catch (err) {
|
||||
console.error("Errore in avvio:", err);
|
||||
}
|
||||
}
|
||||
|
||||
if(connectBtn) connectBtn.addEventListener('click', startAudio);
|
||||
if(setTgBtn) setTgBtn.addEventListener('click', () => { if(tgInput) inviaCambioTG(tgInput.value); });
|
||||
if(tgInput) {
|
||||
tgInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') inviaCambioTG(tgInput.value);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user