node-red-contrib-tts-ultimate 1.0.34 → 1.0.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  [![Donate via PayPal](https://img.shields.io/badge/Donate-PayPal-blue.svg?style=flat-square)](https://www.paypal.me/techtoday)
4
4
 
5
+ <p>
6
+ <b>Version 1.0.38</b> December 2021<br/>
7
+ - Removed some unwanted startup logs.<br/>
8
+ - Fixed ownfile sample code. Thanks to plats98.<br/>
9
+ <p>
10
+ <p>
11
+ <b>Version 1.0.36</b> November 2021<br/>
12
+ - NEW: Autosplit function: you can now set the maximum lenght of the text-parts, in case your spoken text is too long for the allowed TTS Engine limits.<br/>
13
+ <p>
14
+ <p>
15
+ <b>Version 1.0.35</b> October 2021<br/>
16
+ - NEW: You can force unmuting all players, then restore their previous state once finished playing.<br/>
17
+ <p>
5
18
  <p>
6
19
  <b>Version 1.0.34</b> October 2021<br/>
7
20
  - FIX: fixed an issue in retrieving voices if you have more than one TTS engine enabled at the same time.<br/>
package/README.md CHANGED
@@ -125,6 +125,12 @@ On each deploy or node-red restart, delete all tts files in the cache. This is u
125
125
  ***Leave the TTS cache folder untouched*** (suggested only if you have enough disk space)<br/>
126
126
  Don't delete the files cached. Useful if you wish to keep the tts files, even in case of internet outages, node-red restart or reboots.
127
127
  <br/>
128
+
129
+ **Autosplit max length
130
+ <br/>
131
+ Due to some limitations in text lenght, applied by the online TTS Engines, TTS-Ultimate is capable to split your long text to be spoken, in shorter texts in an intelligent way.<br/>
132
+ This field allow you to set the maximum lenght (**in chars**) of each splitted text part. The default is **220** and is OK for most cases. You can change this valut to whatever you want but keep in mind, that your TTS engine can either cut the text or return an error, if the text is too long. As the cached filename is equal to the text being spoken, you could also hit the maximum filename lenght of your filesystem. So keep the default value, unless you absolute need to change it.<br/>
133
+ <br/>
128
134
  <br/>
129
135
 
130
136
  # TTS-ULTIMATE NODE
@@ -151,6 +157,9 @@ In case you select ***No player, only output file name***, you'll get a message
151
157
  **Volume** <br/>
152
158
  Set the preferred TTS volume, from "0" to "100" (can be overridden by passing <code>msg.volume = "40";</code> to the node)
153
159
 
160
+ **Unmute** <br/>
161
+ Unmute the main and the addotional players, then restore the previous mute state once finished. (Can be overridden by passing <code>msg.unmute = true;</code> to the node)
162
+
154
163
  **Main Sonos Player** <br/>
155
164
  Select your Sonos primary player. (It's strongly suggested to set a fixed IP for this player; you can reserve an IP using the DHCP Reservation function of your router/firewall's DHCP Server).<br/>
156
165
  It's possibile to group players, so your announcement can be played on all selected players. For this to happen, you need to select your primary coordinator player. All other players will be then controlled by this coordinator.
@@ -164,6 +173,10 @@ Here you can add all additional players that will be grouped toghether to the *M
164
173
  **msg.volume**<br/>
165
174
  Set the volume (values between "0" and "100" with quotes)</br>
166
175
 
176
+ **msg.unmute**<br/>
177
+ *true*: Unmute all players then mutes it again once finished playing.</br>
178
+ *false*: Leave the player as they are.</br>
179
+
167
180
  **msg.nohailing**<br/>
168
181
  Temporarely doesn't play the Hailing sound prior to the message (values "true" or "1" with quotes)</br>
169
182
 
@@ -259,17 +272,19 @@ The property is a JSON object.
259
272
  <img src='https://github.com/Supergiovane/node-red-contrib-tts-ultimate/raw/master/img/setConfig.png' width="80%">
260
273
 
261
274
  **msg.setConfig**<br/>
262
- This is the property where you can set all the things. It must be an Object.
275
+ This is the property where you can set all the things. It must be a JSON Object with the below specified properties.<br/>
276
+ The setting is retained until the node receives another msg.setConfig or until node-red is restarted.<br/>
263
277
 
264
- **setMainPlayerIP**<br/>
265
- Sets the main player IP. This will also be the coordinator if you have a group of players
278
+ > **property setMainPlayerIP**<br/>
279
+ Sets the main player IP. This will also be the coordinator if you have a group of players.
266
280
 
267
- **setPlayerGroupArray**<br/>
268
- Sets the array of players beloging to the group, if any.<br/>
269
- If you have only one additional player, you need to ever put it into an array. See below.
281
+ > **property setPlayerGroupArray**<br/>
282
+ Sets the array of players beloging to the group, if any.<br/>
283
+ If you have only one additional player, you need to ever put it into an array. See below.
270
284
 
271
285
  ```js
272
- // Set player IP
286
+ // Set main player IP
287
+ // The setting is retained until the node receives another msg.setConfig or until node-red is restarted.
273
288
  var config= {
274
289
  setMainPlayerIP:"192.168.1.109"
275
290
  };
@@ -279,6 +294,7 @@ return msg;
279
294
 
280
295
  ```js
281
296
  // Set player IP and additional players
297
+ // The setting is retained until the node receives another msg.setConfig or until node-red is restarted.
282
298
  var config= {
283
299
  setMainPlayerIP:"192.168.1.109",
284
300
  setPlayerGroupArray:[
@@ -293,6 +309,7 @@ return msg;
293
309
 
294
310
  ```js
295
311
  // If you have only one additional player
312
+ // The setting is retained until the node receives another msg.setConfig or until node-red is restarted.
296
313
  var config= {
297
314
  setMainPlayerIP:"192.168.1.109",
298
315
  setPlayerGroupArray:["192.168.1.110"]
@@ -326,7 +343,7 @@ return msg;
326
343
  > Adjust the nodes according to your setup
327
344
 
328
345
  ```js
329
- [{"id":"db0ea33.f1186e","type":"ttsultimate","z":"c6efd2b6.ab02e8","name":"","voice":"en-AU-Standard-A#en-AU#FEMALE","ssml":false,"sonosipaddress":"192.168.1.109","sonosvolume":"25","sonoshailing":"Hailing_Hailing.mp3","config":"4f941d61.f52c4c","propertyType":{},"rules":[],"x":670,"y":240,"wires":[[]]},{"id":"c7fb2970.271978","type":"ownfile","z":"c6efd2b6.ab02e8","name":"","selectedFile":"OwnFile_Tur geoeffnet.mp3","x":490,"y":220,"wires":[["db0ea33.f1186e"]]},{"id":"fef80c5b.49f9e","type":"inject","z":"c6efd2b6.ab02e8","name":"","topic":"","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":220,"wires":[["c7fb2970.271978"]]},{"id":"807f0f6c.6d59c","type":"comment","z":"c6efd2b6.ab02e8","name":"You can upload your own voice messages and use it with ttsultimate","info":"","x":310,"y":180,"wires":[]},{"id":"536e58b3.bb8468","type":"ownfile","z":"c6efd2b6.ab02e8","name":"","selectedFile":"OwnFile_Tur geoeffnet.mp3","x":490,"y":260,"wires":[["db0ea33.f1186e"]]},{"id":"26c339f9.346fbe","type":"inject","z":"c6efd2b6.ab02e8","name":"","topic":"","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":260,"wires":[["25016441.6447bc"]]},{"id":"25016441.6447bc","type":"function","z":"c6efd2b6.ab02e8","name":"Dynamically Select file","func":"// Override the selected file.\nmsg.selectedFile=\"Porta aperta\"\nreturn msg;","outputs":1,"noerr":0,"x":300,"y":260,"wires":[["536e58b3.bb8468"]]},{"id":"4f941d61.f52c4c","type":"ttsultimate-config","z":"","name":"GoogleTTS","noderedipaddress":"192.168.1.219","noderedport":"1980","purgediratrestart":"leave","ttsservice":"googletts"}]
346
+ [{"id":"db0ea33.f1186e","type":"ttsultimate","z":"c6efd2b6.ab02e8","name":"","voice":"en-AU-Standard-A#en-AU#FEMALE","ssml":false,"sonosipaddress":"192.168.1.109","sonosvolume":"25","sonoshailing":"Hailing_Hailing.mp3","config":"4f941d61.f52c4c","propertyType":{},"rules":[],"x":670,"y":240,"wires":[[]]},{"id":"c7fb2970.271978","type":"ownfileultimate","z":"c6efd2b6.ab02e8","name":"","selectedFile":"OwnFile_Tur geoeffnet.mp3","x":490,"y":220,"wires":[["db0ea33.f1186e"]]},{"id":"fef80c5b.49f9e","type":"inject","z":"c6efd2b6.ab02e8","name":"","topic":"","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":220,"wires":[["c7fb2970.271978"]]},{"id":"807f0f6c.6d59c","type":"comment","z":"c6efd2b6.ab02e8","name":"You can upload your own voice messages and use it with ttsultimate","info":"","x":310,"y":180,"wires":[]},{"id":"536e58b3.bb8468","type":"ownfileultimate","z":"c6efd2b6.ab02e8","name":"","selectedFile":"OwnFile_Tur geoeffnet.mp3","x":490,"y":260,"wires":[["db0ea33.f1186e"]]},{"id":"26c339f9.346fbe","type":"inject","z":"c6efd2b6.ab02e8","name":"","topic":"","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":260,"wires":[["25016441.6447bc"]]},{"id":"25016441.6447bc","type":"function","z":"c6efd2b6.ab02e8","name":"Dynamically Select file","func":"// Override the selected file.\nmsg.selectedFile=\"Porta aperta\"\nreturn msg;","outputs":1,"noerr":0,"x":300,"y":260,"wires":[["536e58b3.bb8468"]]},{"id":"4f941d61.f52c4c","type":"ttsultimate-config","z":"","name":"GoogleTTS","noderedipaddress":"192.168.1.219","noderedport":"1980","purgediratrestart":"leave","ttsservice":"googletts"}]
330
347
  ```
331
348
  </details>
332
349
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-tts-ultimate",
3
- "version": "1.0.34",
3
+ "version": "1.0.38",
4
4
  "description": "Transforms the text in speech and hear it using Sonos player or generate an audio file to be used with third parties nodes. Works with voices from Amazon, Google (without credentials as well), Microsoft TTS Azure, or your own voice. You can also only create a TTS file to be read by third party nodes. Update of the popular SonosPollyTTS node.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -16,6 +16,7 @@
16
16
  "google",
17
17
  "voice",
18
18
  "amazon",
19
+ "azure",
19
20
  "tts",
20
21
  "sonos",
21
22
  "IOT",
@@ -17,7 +17,8 @@
17
17
  type: "text"
18
18
  },
19
19
  purgediratrestart: { value: "leave", required: false },
20
- ttsservice: { value: "googletranslate", required: false }
20
+ ttsservice: { value: "googletranslate", required: false },
21
+ limitTTSFilenameLenght: { value: 220, required: false, validate: RED.validators.number() }
21
22
 
22
23
  },
23
24
  credentials: {
@@ -32,6 +33,10 @@
32
33
  oneditprepare: function () {
33
34
  var node = this;
34
35
 
36
+ // 01/11/2021
37
+ //if (node.limitTTSFilenameLenght === undefined) {
38
+ // $("#node-config-input-limitTTSFilenameLenght").val(220);
39
+ //}
35
40
  // 21/03/2020 Check if the node is the absolute first in the flow. In this case, it has no http server instatiaced
36
41
  $.getJSON('ttsultimateGetEthAddress', (data) => {
37
42
  $("#pleaseDeploy").hide();
@@ -61,16 +66,16 @@
61
66
  $("#node-config-input-ttsservice").change(function (e) {
62
67
  if ($("#node-config-input-ttsservice").val() == "polly") {
63
68
  $("#GoogleForm").hide();
64
- $("#microsoftAzureForm").hide();
69
+ $("#microsoftAzureForm").hide();
65
70
  $("#pollyForm").show();
66
71
  } else if ($("#node-config-input-ttsservice").val() == "googletts") {
67
- $("#microsoftAzureForm").hide();
72
+ $("#microsoftAzureForm").hide();
68
73
  $("#pollyForm").hide();
69
74
  $("#GoogleForm").show();
70
75
  } else if ($("#node-config-input-ttsservice").val() == "googletranslate") {
71
76
  $("#pollyForm").hide();
72
77
  $("#GoogleForm").hide();
73
- $("#microsoftAzureForm").hide();
78
+ $("#microsoftAzureForm").hide();
74
79
  } else if ($("#node-config-input-ttsservice").val() == "microsoftazuretts") {
75
80
  $("#pollyForm").hide();
76
81
  $("#GoogleForm").hide();
@@ -208,6 +213,12 @@
208
213
  <option value="leave">Leave the TTS cache folder untouched (not suggested if you have less disk space)</option>
209
214
  </select>
210
215
  </div>
216
+
217
+ <div class="form-row">
218
+ <label for="node-config-input-limitTTSFilenameLenght"><i class="fa fa-file"></i> Autosplit max length (chars)</label>
219
+ <input type="text" id="node-config-input-limitTTSFilenameLenght">
220
+ </div>
221
+
211
222
  </div>
212
223
 
213
224
  </script>
@@ -23,6 +23,7 @@ module.exports = function (RED) {
23
23
  node.userDir = path.join(RED.settings.userDir, "sonospollyttsstorage"); // 09/03/2020 Storage of ttsultimate (otherwise, at each upgrade to a newer version, the node path is wiped out and recreated, loosing all custom files)
24
24
  node.whoIsUsingTheServer = ""; // Client node.id using the server, because only a ttsultimate node can use the serve at once.
25
25
  node.ttsservice = config.ttsservice || "googletranslate";
26
+ node.limitTTSFilenameLenght = config.limitTTSFilenameLenght === undefined ? 220 : config.limitTTSFilenameLenght;
26
27
  // node.polly = null;
27
28
  // node.googleTTS = null;
28
29
  // node.googleTranslateTTS = null;
@@ -96,7 +97,7 @@ module.exports = function (RED) {
96
97
  RED.log.warn("ttsultimate-config " + node.id + ": Polly service disabled. " + error.message)
97
98
  }
98
99
  } else {
99
- RED.log.warn("ttsultimate-config " + node.id + ": Polly service not used.");
100
+ //RED.log.info("ttsultimate-config " + node.id + ": Polly service not used.");
100
101
  }
101
102
 
102
103
 
@@ -117,7 +118,7 @@ module.exports = function (RED) {
117
118
  }
118
119
 
119
120
  } else {
120
- RED.log.warn("ttsultimate-config " + node.id + ": Google TTS service not used.");
121
+ // RED.log.info("ttsultimate-config " + node.id + ": Google TTS service not used.");
121
122
  }
122
123
 
123
124
 
@@ -127,10 +128,10 @@ module.exports = function (RED) {
127
128
  try {
128
129
  node.googleTranslateTTS = GoogleTranslate;
129
130
  } catch (error) {
130
- RED.log.warn("ttsultimate-config " + node.id + ": Google Translate free service not used.");
131
+ //RED.log.info("ttsultimate-config " + node.id + ": Google Translate free service not used.");
131
132
  }
132
133
  } else {
133
- RED.log.warn("ttsultimate-config " + node.id + ": Google Translate free service not used.");
134
+ //RED.log.info("ttsultimate-config " + node.id + ": Google Translate free service not used.");
134
135
  }
135
136
 
136
137
 
@@ -145,64 +146,63 @@ module.exports = function (RED) {
145
146
  return node.microsoftAzureTTS;
146
147
  }
147
148
  try {
148
-
149
+
149
150
  let speechConfig = microsoftAzureTTS.SpeechConfig.fromSubscription(node.credentials.mssubscriptionKey, node.credentials.mslocation);
150
151
  //speechConfig.speechSynthesisLanguage = "it-IT";
151
152
  //speechConfig.speechSynthesisVoiceName = "it-IT-IsabellaNeural";
152
153
  speechConfig.speechSynthesisOutputFormat = microsoftAzureTTS.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3;
153
154
  node.microsoftAzureTTS = new microsoftAzureTTS.SpeechSynthesizer(speechConfig);
154
155
  node.microsoftAzureTTSVoiceList = [];
155
- if (node.ttsservice === "microsoftazuretts") {
156
- // Get the voices
157
- async function listVoicesAzure() {
158
- const httpsAzure = require('https')
159
- let options = {
160
- hostname: node.credentials.mslocation + '.tts.speech.microsoft.com',
161
- port: 443,
162
- path: '/cognitiveservices/voices/list',
163
- method: 'GET',
164
- headers: {
165
- 'Ocp-Apim-Subscription-Key': node.credentials.mssubscriptionKey
166
- }
156
+ // Get the voices
157
+ async function listVoicesAzure() {
158
+ const httpsAzure = require('https')
159
+ let options = {
160
+ hostname: node.credentials.mslocation + '.tts.speech.microsoft.com',
161
+ port: 443,
162
+ path: '/cognitiveservices/voices/list',
163
+ method: 'GET',
164
+ headers: {
165
+ 'Ocp-Apim-Subscription-Key': node.credentials.mssubscriptionKey
167
166
  }
168
- const reqAzure = httpsAzure.request(options, resVoices => {
169
- var sChunkResponse = "";
170
- resVoices.on('data', d => {
171
- sChunkResponse += d.toString();
172
- })
173
- resVoices.on('end', () => {
174
- try {
175
- let oVoices = JSON.parse(sChunkResponse);
176
- RED.log.info('ttsultimate-config ' + node.id + ': Microsoft Azure voices count: ' + oVoices.length);
177
- for (let index = 0; index < oVoices.length; index++) {
178
- const element = oVoices[index];
179
- node.microsoftAzureTTSVoiceList.push({ name: element.ShortName + " (" + element.Gender + ")", id: element.ShortName })
180
- }
181
- } catch (error) {
182
- RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error parsing Microsoft Azure TTS voices: ' + error.message);
183
- node.microsoftAzureTTSVoiceList.push({ name: "Error parsing Microsoft Azure voices: " + error.message, id: "Ivy" });
184
- }
185
- })
167
+ }
168
+ const reqAzure = httpsAzure.request(options, resVoices => {
169
+ var sChunkResponse = "";
170
+ resVoices.on('data', d => {
171
+ sChunkResponse += d.toString();
186
172
  })
187
- reqAzure.on('error', error => {
188
- RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error contacting Azure for getting the voices list: ' + error.message);
189
- node.microsoftAzureTTSVoiceList.push({ name: "Error getting Microsoft Azure voices: " + error.message, id: "Ivy" })
190
- reqAzure.end();
173
+ resVoices.on('end', () => {
174
+ try {
175
+ let oVoices = JSON.parse(sChunkResponse);
176
+ RED.log.info('ttsultimate-config ' + node.id + ': Microsoft Azure voices count: ' + oVoices.length);
177
+ for (let index = 0; index < oVoices.length; index++) {
178
+ const element = oVoices[index];
179
+ node.microsoftAzureTTSVoiceList.push({ name: element.ShortName + " (" + element.Gender + ")", id: element.ShortName })
180
+ }
181
+ } catch (error) {
182
+ RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error parsing Microsoft Azure TTS voices: ' + error.message);
183
+ node.microsoftAzureTTSVoiceList.push({ name: "Error parsing Microsoft Azure voices: " + error.message, id: "Ivy" });
184
+ }
191
185
  })
186
+ })
187
+ reqAzure.on('error', error => {
188
+ RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error contacting Azure for getting the voices list: ' + error.message);
189
+ node.microsoftAzureTTSVoiceList.push({ name: "Error getting Microsoft Azure voices: " + error.message, id: "Ivy" })
192
190
  reqAzure.end();
193
- };
194
- RED.log.info("ttsultimate-config " + node.id + ": Microsoft AzureTTS service enabled.")
195
- try {
196
- listVoicesAzure();
197
- } catch (error) {
198
- RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error getting Microsoft Azure voices: ' + error.message);
199
- }
191
+ })
192
+ reqAzure.end();
193
+ };
194
+ RED.log.info("ttsultimate-config " + node.id + ": Microsoft AzureTTS service enabled.")
195
+ try {
196
+ listVoicesAzure();
197
+ } catch (error) {
198
+ RED.log.error('ttsultimate-config ' + node.id + ': listVoices: Error getting Microsoft Azure voices: ' + error.message);
200
199
  }
200
+
201
201
  } catch (error) {
202
202
  RED.log.warn("ttsultimate-config " + node.id + ": Microsoft AzureTTS service disabled. " + error.message)
203
203
  }
204
204
  } else {
205
- RED.log.warn("ttsultimate-config " + node.id + ": Microsoft AzureTTS service not used. ");
205
+ //RED.log.info("ttsultimate-config " + node.id + ": Microsoft AzureTTS service not used. ");
206
206
  }
207
207
  // #########################################
208
208
 
@@ -58,6 +58,11 @@
58
58
  <label for="node-input-sonosvolume"><i class="fa fa-volume-up"></i> Volume</label>
59
59
  <input type="text" id="node-input-sonosvolume" style="width:150px">
60
60
  </div>
61
+ <div class="form-row">
62
+ <label for="node-input-unmuteIfMuted"><i class="fa fa-bell-slash-o"></i> Unmute</label>
63
+ <input type="checkbox" id="node-input-unmuteIfMuted" style="margin-left: 0px; vertical-align: top; width: auto !important;"> <label style="width:auto !important;"> Unmute, then restore previous state after play.</label>
64
+ </div>
65
+
61
66
  <div class="form-row">
62
67
  <label><i class="fa fa-upload"></i> Upload hail</label>
63
68
  <input id="ownFileUpload" type="file" multiple="multiple">
@@ -128,7 +133,9 @@
128
133
  rules: { value: [] },
129
134
  playertype: { value: "sonos", required: false },
130
135
  speakingrate: { value: "1", required: false },
131
- speakingpitch: { value: "0", required: false }
136
+ speakingpitch: { value: "0", required: false },
137
+ unmuteIfMuted: { value: true }
138
+
132
139
  },
133
140
  inputs: 1,
134
141
  outputs: 2,
@@ -76,7 +76,9 @@ module.exports = function (RED) {
76
76
  node.playertype = config.playertype === undefined ? "sonos" : config.playertype; // 20/09/2021 Player type
77
77
  node.speakingpitch = config.speakingpitch === undefined ? "0" : config.speakingpitch; // 21/09/2021 AudioConfig speakingpitch
78
78
  node.speakingrate = config.speakingrate === undefined ? "1" : config.speakingrate; // 21/09/2021 AudioConfig speakingrate
79
-
79
+ node.unmuteIfMuted = config.unmuteIfMuted === undefined ? false : config.unmuteIfMuted; // 21/10/2021 Unmute if previiously muted.
80
+ node.sonosCoordinatorIsPreviouslyMuted = false;
81
+
80
82
  if (typeof node.server !== "undefined" && node.server !== null) {
81
83
  node.sNoderedURL = node.server.sNoderedURL || "";
82
84
  }
@@ -243,18 +245,29 @@ module.exports = function (RED) {
243
245
  });
244
246
  }
245
247
 
246
- // 28/08/2021 Sync wrapper
247
- // function removeTracksFromQueueSync() {
248
- // return new Promise((resolve, reject) => {
249
- // // To remove current item from the queue
250
- // node.SonosClient.removeTracksFromQueue(1,1).then(result => {
251
- // resolve(result);
252
- // }).catch(err => {
253
- // RED.log.error("ttsultimate: Error removeTracksFromQueueSync: " + err.message);
254
- // reject(err);
255
- // });
256
- // });
257
- // }
248
+ // 21/10/2021 Sync wrapper
249
+ function GETMutedSync() {
250
+ return new Promise((resolve, reject) => {
251
+ node.SonosClient.getMuted().then(state => {
252
+ resolve(state);
253
+ }).catch(err => {
254
+ RED.log.error("ttsultimate: Error GETMutedSync: " + err.message);
255
+ reject(err);
256
+ });
257
+ });
258
+ }
259
+
260
+ // 21/10/2021 Sync wrapper
261
+ function SETMutedSync(_muted) {
262
+ return new Promise((resolve, reject) => {
263
+ node.SonosClient.setMuted(_muted).then(state => {
264
+ resolve(state);
265
+ }).catch(err => {
266
+ RED.log.error("ttsultimate: Error SETMutedSync: " + err.message);
267
+ reject(err);
268
+ });
269
+ });
270
+ }
258
271
 
259
272
  // 20/03/2020 Join Coordinator queue
260
273
  // ######################################################
@@ -262,11 +275,12 @@ module.exports = function (RED) {
262
275
  // 05/07/2021 Get the main coordinator player previous volume set by app
263
276
  try {
264
277
  node.sonosCoordinatorPreviousVolumeSetByApp = await GETVOLUMESync();
278
+ node.sonosCoordinatorIsPreviouslyMuted = await GETMutedSync();
265
279
  } catch (error) {
266
280
  node.sonosCoordinatorPreviousVolumeSetByApp = node.sSonosVolume;
281
+ node.sonosCoordinatorIsPreviouslyMuted = false;
267
282
  }
268
283
  // 30/03/2020 in the middle of coronavirus emergency. Group Speakers
269
- // You don't have to worry about who is the coordinator.
270
284
  for (let index = 0; index < node.oAdditionalSonosPlayers.length; index++) {
271
285
  const element = node.oAdditionalSonosPlayers[index];
272
286
  try {
@@ -280,6 +294,12 @@ module.exports = function (RED) {
280
294
  } catch (error) {
281
295
  RED.log.warn("ttsultimate: Error setting volume of joined device " + error.message);
282
296
  }
297
+ // 21/10/2021 set the previous mute/unmute state
298
+ try {
299
+ element.isPreviouslyMuted = await element.getMuted();
300
+ } catch (error) {
301
+ RED.log.warn("ttsultimate: Error getMuted of joined device " + error.message);
302
+ }
283
303
  };
284
304
  }
285
305
 
@@ -291,6 +311,14 @@ module.exports = function (RED) {
291
311
  } catch (error) {
292
312
  RED.log.warn("ttsultimate: Error set preious volume on main coordinator in ungroupSpeakers " + error.message);
293
313
  }
314
+ // 21/10/2021 Unmute?
315
+ if (node.unmuteIfMuted && node.sonosCoordinatorIsPreviouslyMuted) {
316
+ try {
317
+ await SETMutedSync(true);
318
+ } catch (error) {
319
+ RED.log.warn("ttsultimate: Error set preivous mute state on main coordinator in ungroupSpeakers " + error.message);
320
+ }
321
+ }
294
322
 
295
323
  for (let index = 0; index < node.oAdditionalSonosPlayers.length; index++) {
296
324
  const element = node.oAdditionalSonosPlayers[index];
@@ -307,6 +335,14 @@ module.exports = function (RED) {
307
335
  RED.log.warn("ttsultimate: Error set previous volume on group device " + error.message);
308
336
  }
309
337
  }
338
+ // 21/10/2021 Unmute?
339
+ if (node.unmuteIfMuted && element.isPreviouslyMuted) {
340
+ try {
341
+ await element.setMuted(true);
342
+ } catch (error) {
343
+ RED.log.warn("ttsultimate: Error set previous mute state on group device " + error.message);
344
+ }
345
+ }
310
346
  }
311
347
  }
312
348
  // ######################################################
@@ -617,12 +653,15 @@ module.exports = function (RED) {
617
653
  volTemp = node.sSonosVolume;
618
654
  }
619
655
  await SETVOLUMESync(volTemp);
656
+ if (node.unmuteIfMuted) await SETMutedSync(false); // 21/10/2021 Unmute
657
+
620
658
  if (node.oAdditionalSonosPlayers.length > 0) {
621
659
  // 05/07/2021 set the volume of additional coordinatores
622
660
  for (let index = 0; index < node.oAdditionalSonosPlayers.length; index++) {
623
661
  const element = node.oAdditionalSonosPlayers[index];
624
662
  try {
625
663
  await element.setVolume(volTemp);
664
+ if (node.unmuteIfMuted) await element.setMuted(false); // 21/10/2021 Unmute
626
665
  } catch (error) {
627
666
  RED.log.error("ttsultimate: Handlequeue: Unable to set the volume on additional player " + error.message);
628
667
  }
@@ -767,6 +806,10 @@ module.exports = function (RED) {
767
806
  }
768
807
 
769
808
  node.on('input', function (msg) {
809
+ // if (msg.hasOwnProperty("banana")) {
810
+ // node.SonosClient.setMuted(msg.banana);
811
+ // return;
812
+ // }
770
813
 
771
814
  // 09/01/2021 Set the main player and groups IP on request
772
815
  // *********************************
@@ -819,6 +862,12 @@ module.exports = function (RED) {
819
862
  return;
820
863
  }
821
864
 
865
+
866
+ // 21/10/2021 force unmute
867
+ if (msg.hasOwnProperty("unmute")) {
868
+ node.unmuteIfMuted = msg.unmute;
869
+ }
870
+
822
871
  // 05/12/2020 handling Hailing
823
872
  var hailingMSG = null;
824
873
  if (msg.hasOwnProperty("nohailing") && (msg.nohailing == "1" || msg.nohailing.toLowerCase() == "true")) {
@@ -894,8 +943,7 @@ module.exports = function (RED) {
894
943
 
895
944
 
896
945
  // 30/01/2021 split the text if it's too long, otherwies i'll have issues with filename too long.
897
- const iLimitTTSFilenameLenght = 220;
898
- if (msg.payload.length >= iLimitTTSFilenameLenght) {
946
+ if (msg.payload.length >= node.server.limitTTSFilenameLenght) {
899
947
  let sTemp = "";
900
948
  let aSeps = [".", ",", ":", ";", "!", "?"];
901
949
  let sPayload = msg.payload.replace(/[\r\n]+/gm, "");
@@ -908,7 +956,7 @@ module.exports = function (RED) {
908
956
  node.tempMSGStorage.push(oMsg);
909
957
  sTemp = "";
910
958
  }
911
- if (sTemp.length > iLimitTTSFilenameLenght && element === " ") {
959
+ if (sTemp.length > node.server.limitTTSFilenameLenght && element === " ") {
912
960
  // Split using space
913
961
  const oMsg = RED.util.cloneMessage(msg);
914
962
  oMsg.payload = sTemp;
@@ -998,7 +1046,7 @@ module.exports = function (RED) {
998
1046
 
999
1047
  // 12/10/2021 Microsoft Azure TTS Service
1000
1048
  async function synthesizeSpeechMicrosoftAzureTTS(ttsService, params) {
1001
-
1049
+
1002
1050
  return new Promise(function (resolve, reject) {
1003
1051
  try {
1004
1052
 
@@ -1009,14 +1057,14 @@ module.exports = function (RED) {
1009
1057
  params.text,
1010
1058
  result => {
1011
1059
  ttsService.close();
1012
- resolve (Buffer.from(result.audioData));
1060
+ resolve(Buffer.from(result.audioData));
1013
1061
  },
1014
1062
  error => {
1015
1063
  ttsService.close();
1016
- reject (error);
1064
+ reject(error);
1017
1065
  });
1018
1066
  } catch (error) {
1019
- reject (error);
1067
+ reject(error);
1020
1068
  }
1021
1069
  });
1022
1070
  };