From 581a567c17de29760194be3160bdea22b724a2ea Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Wed, 11 Feb 2026 19:35:09 +0100 Subject: [PATCH] fix(voce): riproduce la sintesi vocale sul display invece che sul controller Il controller invia un comando 'speak' via WebSocket. Il server inoltra il messaggio solo ai client display, che eseguono speechSynthesis con preferenza per voce italiana. --- src/components/ControllerPage.vue | 24 ++++++++++++++++-------- src/components/DisplayPage.vue | 23 +++++++++++++++++++++++ src/websocket-handler.js | 28 ++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 8 deletions(-) diff --git a/src/components/ControllerPage.vue b/src/components/ControllerPage.vue index e6bc1c6..54622d1 100644 --- a/src/components/ControllerPage.vue +++ b/src/components/ControllerPage.vue @@ -481,21 +481,29 @@ export default { }, speak() { - const msg = new SpeechSynthesisUtterance() + let text = '' if (this.state.sp.punt.home + this.state.sp.punt.guest === 0) { - msg.text = "zero a zero" + text = "zero a zero" } else if (this.state.sp.punt.home === this.state.sp.punt.guest) { - msg.text = this.state.sp.punt.home + " pari" + text = this.state.sp.punt.home + " pari" } else { if (this.state.sp.servHome) { - msg.text = this.state.sp.punt.home + " a " + this.state.sp.punt.guest + text = this.state.sp.punt.home + " a " + this.state.sp.punt.guest } else { - msg.text = this.state.sp.punt.guest + " a " + this.state.sp.punt.home + text = this.state.sp.punt.guest + " a " + this.state.sp.punt.home } } - const voices = window.speechSynthesis.getVoices() - msg.voice = voices.find(v => v.name === 'Google italiano') - window.speechSynthesis.speak(msg) + if (!this.wsConnected || !this.ws || this.ws.readyState !== WebSocket.OPEN) { + this.showErrorFeedback('Non connesso al server') + return + } + + try { + this.ws.send(JSON.stringify({ type: 'speak', text })) + } catch (err) { + console.error('[Controller] Failed to send speak command:', err) + this.showErrorFeedback('Errore invio comando voce') + } } } } diff --git a/src/components/DisplayPage.vue b/src/components/DisplayPage.vue index af90728..fdc6011 100644 --- a/src/components/DisplayPage.vue +++ b/src/components/DisplayPage.vue @@ -266,6 +266,8 @@ export default { if (msg.type === 'state') { this.state = msg.state + } else if (msg.type === 'speak') { + this.speakOnDisplay(msg.text) } else if (msg.type === 'error') { console.error('[Display] Server error:', msg.message) } @@ -314,6 +316,27 @@ export default { this.reconnectTimeout = null this.connectWebSocket() }, delay) + }, + speakOnDisplay(text) { + if (typeof text !== 'string' || !text.trim()) { + return + } + if (!('speechSynthesis' in window)) { + console.warn('[Display] speechSynthesis not supported') + return + } + + const utterance = new SpeechSynthesisUtterance(text.trim()) + const voices = window.speechSynthesis.getVoices() + const preferredVoice = voices.find((v) => v.name === 'Google italiano') + || voices.find((v) => v.lang && v.lang.toLowerCase().startsWith('it')) + if (preferredVoice) { + utterance.voice = preferredVoice + } + utterance.lang = 'it-IT' + + window.speechSynthesis.cancel() + window.speechSynthesis.speak(utterance) } } } diff --git a/src/websocket-handler.js b/src/websocket-handler.js index 5941bb8..ea2474f 100644 --- a/src/websocket-handler.js +++ b/src/websocket-handler.js @@ -28,6 +28,10 @@ export function setupWebSocketHandler(wss) { if (msg.type === 'action') { return handleAction(ws, msg) } + + if (msg.type === 'speak') { + return handleSpeak(ws, msg) + } } catch (err) { console.error('Error processing message:', err, 'data:', data) // Invia l'errore solo se la connessione e ancora aperta. @@ -98,6 +102,30 @@ export function setupWebSocketHandler(wss) { broadcastState() } + /** + * Gestisce una richiesta di sintesi vocale proveniente dal controller. + * Il messaggio viene inoltrato solo ai client registrati come display. + */ + function handleSpeak(ws, msg) { + const client = clients.get(ws) + if (!client || client.role !== 'controller') { + sendError(ws, 'Only controllers can request speech') + return + } + + if (typeof msg.text !== 'string' || msg.text.trim() === '') { + sendError(ws, 'Invalid speak payload') + return + } + + const speakMsg = JSON.stringify({ type: 'speak', text: msg.text.trim() }) + clients.forEach((meta, clientWs) => { + if (meta.role === 'display' && clientWs.readyState === 1) { + clientWs.send(speakMsg) + } + }) + } + /** * Invia un messaggio di errore al client */