node-red-dashboard icon indicating copy to clipboard operation
node-red-dashboard copied to clipboard

Widget: Audio Add TTS Support to match Dashboard 1.0 Functionality

Open kd9lsv opened this issue 1 year ago • 3 comments

Description

Discussed on #52. @bartbutenaers pointed to create issue to not lose track.

Functionality was missing from #1506
Didn't include the Text to Speech functionality in new 2.0 node.

Pictures from Dashboard 1.0 image image

No hurry on additional functionality.

Have you provided an initial effort estimate for this issue?

I am no FlowFuse team member

kd9lsv avatar Dec 24 '24 16:12 kd9lsv

@kd9lsv Thanks for doing the administrative work!

Reminder for myself: the frontend code for TTS in the old dashboard can be found here.

If I remember correctly, there was one issue with that implementation: you have a dropdown in the ui-audio's node config screen, that allows you to select which language needs to be spoken. But that "TTS voice" dropdown contains the languages available on the server running Node-RED, not the device running the frontend dashboard in the browser. So you can select a supported language in the config screen, but might not have speech in that language in your dashboard (if the client device doesn't support that particular language)...

Don't think we can solve that problem. But need to add it at least to the documentation.

bartbutenaers avatar Dec 24 '24 16:12 bartbutenaers

Perhaps it is better to call it "preferred" TTS voice, to lower the expectations...

bartbutenaers avatar Dec 24 '24 16:12 bartbutenaers

@Steve-Mcl Since I developed the ui-audio node, you can assign this one to me (after Chistmas eve whenever you have more time).

bartbutenaers avatar Dec 24 '24 16:12 bartbutenaers

@gstout52 FYI for context and which customer has asked about this. Should be a fairly quick turnaround. @Steve-Mcl can you add a sizing please.

2.23 at the latest would be my recommnendation

joepavitt avatar Sep 09 '25 14:09 joepavitt

For future me: https://github.com/node-red/node-red-dashboard/blob/41ef61145f75a76201a9bf10bc2400af979203b1/nodes/ui_audio.html#L28

Steve-Mcl avatar Sep 09 '25 14:09 Steve-Mcl

Scheduled for 2.22. We can squeeze in a XS. @joepavitt @Steve-Mcl

gstout52 avatar Sep 09 '25 17:09 gstout52

For anyone wanting to use built in browser speech synthsis while they wait for this to be implemented, it can be acheived using a ui-template node.

e.g:

Image
[{"id":"7fc3dbdfb1519dcb","type":"ui-template","z":"640b62bc33fa1726","group":"","page":"","ui":"03482f22997a66ac","name":"TTS Template","order":1,"width":0,"height":0,"head":"","format":"<template>\n  <div></div>\n</template>\n\n<script>\nexport default {\n  data() {\n      return {\n          _msgid: 0\n      }\n  },\n  mounted() {\n    console.log('SpeechSynthesis initializing');\n    this.synth = window.speechSynthesis;\n    this.voice = null;\n\n    const setVoice = () => {\n      const voices = this.synth.getVoices();\n      if (voices.length > 0) {\n        this.voice = voices[0];\n      }\n    };\n\n    if (this.synth.onvoiceschanged !== undefined) {\n      this.synth.onvoiceschanged = setVoice;\n    }\n    setVoice();\n  },\n  watch: {\n    msg: {\n      immediate: true,\n      handler() {\n        const m = this.msg\n\n        if(this._msgid === m._msgid) {\n          return\n        }\n        this._msgid = m._msgid\n\n        console.log('Received msg:', m)\n        if (m?.topic === 'list-voices') {\n          this.send({\n            topic:'voices',\n            payload: {\n              voices: this.synth.getVoices().map(v => ({name: v.name, lang: v.lang})),\n              selectedVoice: this.voice ? {name: this.voice.name, lang: this.voice.lang} : null\n            }\n          })\n          return\n        }\n\n        if (m?.topic === 'set-voice' && m.payload) {\n          if (typeof m.payload === 'string') {\n            const voices = this.synth.getVoices();\n            const selected = voices.find(v => v.name === m.payload || v.lang === m.payload);\n            if (selected) {\n              this.voice = selected;\n            }\n          } else if (typeof m.payload === 'number') {\n            const voices = this.synth.getVoices();\n            const selected = voices[m.payload];\n            if (selected) {\n              this.voice = selected;\n            }\n          }\n          return\n        }\n\n        if (m?.topic === 'speak' && m?.payload) {\n          if (this.synth && this.voice) {\n            if (this.synth.speaking) {\n              this.synth.cancel();\n            }\n            const utterance = new SpeechSynthesisUtterance(m.payload);\n            utterance.voice = this.voice;\n            this.synth.speak(utterance);\n          }\n        }\n      }\n    }\n  }\n}\n</script>\n","storeOutMessages":true,"passthru":false,"resendOnRefresh":true,"templateScope":"widget:ui","className":"d-hide","x":440,"y":760,"wires":[["43f38283a23d6397"]]},{"id":"dcbd1b9bbe606443","type":"inject","z":"640b62bc33fa1726","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"speak","payload":"this is a test","payloadType":"str","x":190,"y":760,"wires":[["7fc3dbdfb1519dcb"]]},{"id":"43f38283a23d6397","type":"debug","z":"640b62bc33fa1726","name":"voice list","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":630,"y":760,"wires":[]},{"id":"2b2d2b01e3e4faed","type":"inject","z":"640b62bc33fa1726","name":"","props":[{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"list-voices","x":220,"y":640,"wires":[["7fc3dbdfb1519dcb"]]},{"id":"94d0203368c46d20","type":"inject","z":"640b62bc33fa1726","name":"Microsoft Hazel - English","props":[{"p":"topic","vt":"str"},{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"set-voice","payload":"Microsoft Hazel - English (United Kingdom)","payloadType":"str","x":170,"y":680,"wires":[["7fc3dbdfb1519dcb"]]},{"id":"1648452e92fcd8ba","type":"inject","z":"640b62bc33fa1726","name":"Google UK English Male","props":[{"p":"topic","vt":"str"},{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"set-voice","payload":"Google UK English Male","payloadType":"str","x":170,"y":720,"wires":[["7fc3dbdfb1519dcb"]]},{"id":"03482f22997a66ac","type":"ui-base","name":"My Dashboard","path":"/dashboard","appIcon":"","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"headerContent":"page","navigationStyle":"default","titleBarStyle":"default","showReconnectNotification":true,"notificationDisplayTime":1,"showDisconnectNotification":true,"allowInstall":true},{"id":"1aa79fbeab39cef7","type":"global-config","env":[],"modules":{"@flowfuse/node-red-dashboard":"1.26.0"}}]

Steve-Mcl avatar Sep 10 '25 17:09 Steve-Mcl

bumping this to a size 2 (now that I have started it - I forgot about docs, i18n and the fact we already have an audio node that will need a bunch of if-else logic slightly complicating matters)

Steve-Mcl avatar Oct 06 '25 10:10 Steve-Mcl