diff --git a/DESKTOP-APP.md b/DESKTOP-APP.md index 44ff17b..0ba6666 100644 --- a/DESKTOP-APP.md +++ b/DESKTOP-APP.md @@ -293,7 +293,7 @@ PORT=3001 npm start ## 🚧 TODO / Améliorations ### Priorité haute -- [ ] **WebSocket VU-mètres** : implémenter connexion `/audio-levels` +- [x] **WebSocket VU-mètres** : implémenter connexion `/audio-levels` - [ ] **Vraies icônes** : icns/png pour macOS/Linux - [ ] **Tray icon** : avec menu contextuel fonctionnel diff --git a/electron/ui/app.js b/electron/ui/app.js index 4c39e3d..ebd7e71 100644 --- a/electron/ui/app.js +++ b/electron/ui/app.js @@ -8,6 +8,17 @@ const API_BASE = 'http://localhost:3000'; let serverRunning = false; let statsInterval = null; let logsBuffer = []; +let audioLevelsWS = null; +let audioLevelsData = { + inputs: {}, + groups: {}, + outputs: {}, + routing: { + activeInputs: [], + activeGroups: [], + activeOutputs: [] + } +}; // ========== Initialisation ========== @@ -167,6 +178,9 @@ function updateServerStatus(running) { // Démarrer le polling startStatsPolling(); + // Connecter WebSocket audio levels + connectAudioLevelsWS(); + // Charger les données initiales loadInitialData(); } else { @@ -177,6 +191,9 @@ function updateServerStatus(running) { // Arrêter le polling stopStatsPolling(); + + // Déconnecter WebSocket audio levels + disconnectAudioLevelsWS(); } } @@ -475,7 +492,7 @@ async function loadViewData(view) { await fetchGroups(); break; case 'monitoring': - // TODO: charger VU-mètres WebSocket + renderVUMeters(); break; case 'logs': renderLogs(); @@ -579,6 +596,176 @@ function formatUptime(seconds) { return `${h}h ${m}m ${s}s`; } +// ========== WebSocket Audio Levels ========== + +function connectAudioLevelsWS() { + if (audioLevelsWS && audioLevelsWS.readyState === WebSocket.OPEN) { + console.log('WebSocket audio-levels déjà connecté'); + return; + } + + const wsUrl = 'ws://localhost:3000/audio-levels'; + console.log('Connexion WebSocket audio-levels...', wsUrl); + + try { + audioLevelsWS = new WebSocket(wsUrl); + + audioLevelsWS.onopen = () => { + console.log('WebSocket audio-levels connecté'); + updateVUMetersStatus('Connecté'); + }; + + audioLevelsWS.onmessage = (event) => { + try { + const message = JSON.parse(event.data); + + switch (message.type) { + case 'initial': + case 'levels': + audioLevelsData = message.data; + renderVUMeters(); + break; + + case 'pong': + break; + + default: + console.warn('Message WebSocket inconnu:', message.type); + } + } catch (error) { + console.error('Erreur parsing message WebSocket:', error); + } + }; + + audioLevelsWS.onerror = (error) => { + console.error('Erreur WebSocket audio-levels:', error); + updateVUMetersStatus('Erreur de connexion'); + }; + + audioLevelsWS.onclose = () => { + console.log('WebSocket audio-levels déconnecté'); + audioLevelsWS = null; + updateVUMetersStatus('Déconnecté'); + + // Reconnexion automatique si serveur actif + if (serverRunning) { + setTimeout(() => { + connectAudioLevelsWS(); + }, 3000); + } + }; + + // Ping périodique + const pingInterval = setInterval(() => { + if (audioLevelsWS && audioLevelsWS.readyState === WebSocket.OPEN) { + audioLevelsWS.send(JSON.stringify({ type: 'ping' })); + } else { + clearInterval(pingInterval); + } + }, 10000); + + } catch (error) { + console.error('Erreur création WebSocket:', error); + updateVUMetersStatus('Erreur de connexion'); + } +} + +function disconnectAudioLevelsWS() { + if (audioLevelsWS) { + audioLevelsWS.close(); + audioLevelsWS = null; + } +} + +function updateVUMetersStatus(status) { + const container = document.getElementById('vu-meters'); + if (!container) return; + + const statusEl = container.querySelector('.vu-status'); + if (statusEl) { + statusEl.textContent = `WebSocket: ${status}`; + statusEl.className = `vu-status ${status === 'Connecté' ? 'connected' : 'disconnected'}`; + } +} + +function renderVUMeters() { + const container = document.getElementById('vu-meters'); + if (!container) return; + + const hasData = + Object.keys(audioLevelsData.inputs).length > 0 || + Object.keys(audioLevelsData.groups).length > 0 || + Object.keys(audioLevelsData.outputs).length > 0; + + if (!hasData) { + container.innerHTML = ` +
WebSocket: En attente de connexion...
+Aucune donnée audio disponible
+ `; + return; + } + + let html = '