node-red-contrib-tts-ultimate 2.0.6 → 2.0.7

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.
@@ -1,1206 +1,1211 @@
1
1
  const { Readable } = require('stream');
2
2
 
3
3
  module.exports = function (RED) {
4
- 'use strict';
5
-
6
- var fs = require('fs');
7
- var path = require('path');
8
- const sonos = require('sonos');
9
- const crypto = require("crypto");
10
-
11
- function slug(_text) {
12
- var sRet = _text;
13
- sRet = sRet.toString().replace(/\./g, "_stop_");
14
- sRet = sRet.toString().replace(/\?/g, "_qm_");
15
- sRet = sRet.toString().replace(/\!/g, "_em_");
16
- sRet = sRet.toString().replace(/\,/g, "_pause_");
17
- sRet = sRet.toString().replace(/\:/g, "_colon_");
18
- sRet = sRet.toString().replace(/\;/g, "_semicolon_");
19
- sRet = sRet.toString().replace(/\</g, "_less_");
20
- sRet = sRet.toString().replace(/\>/g, "_greater_");
21
- sRet = sRet.toString().replace(/\//g, "_sl_");
22
- sRet = sRet.toString().replace(/\'/g, "_ap_");
23
- sRet = sRet.toString().replace(/\=/g, "_ug_");
24
- sRet = sRet.toString().replace(/\\/g, "_bs_");
25
- sRet = sRet.toString().replace(/\(/g, "_pa_");
26
- sRet = sRet.toString().replace(/\)/g, "_pc_");
27
- sRet = sRet.toString().replace(/\*/g, "_as_");
28
- sRet = sRet.toString().replace(/\[/g, "_qa_");
29
- sRet = sRet.toString().replace(/\]/g, "_qc_");
30
- sRet = sRet.toString().replace(/\^/g, "_fu_");
31
- sRet = sRet.toString().replace(/\|/g, "_pi_");
32
- sRet = sRet.toString().replace(/\"/g, "_dc_");
33
- sRet = sRet.toString().replace(/\#/g, "_");
34
- // slug.charmap['.'] = '_stop_';
35
- // slug.charmap['?'] = '_qm_';
36
- // slug.charmap['!'] = '_em_';
37
- // slug.charmap[','] = '_pause_';
38
- // slug.charmap[':'] = '_colon_';
39
- // slug.charmap[';'] = '_semicolon_';
40
- // slug.charmap['<'] = '_less_';
41
- // slug.charmap['>'] = '_greater_';
42
- return sRet;
4
+ 'use strict';
5
+
6
+ var fs = require('fs');
7
+ var path = require('path');
8
+ const sonos = require('sonos');
9
+ const crypto = require("crypto");
10
+
11
+ function slug(_text) {
12
+ var sRet = _text;
13
+ sRet = sRet.toString().replace(/\./g, "_stop_");
14
+ sRet = sRet.toString().replace(/\?/g, "_qm_");
15
+ sRet = sRet.toString().replace(/\!/g, "_em_");
16
+ sRet = sRet.toString().replace(/\,/g, "_pause_");
17
+ sRet = sRet.toString().replace(/\:/g, "_colon_");
18
+ sRet = sRet.toString().replace(/\;/g, "_semicolon_");
19
+ sRet = sRet.toString().replace(/\</g, "_less_");
20
+ sRet = sRet.toString().replace(/\>/g, "_greater_");
21
+ sRet = sRet.toString().replace(/\//g, "_sl_");
22
+ sRet = sRet.toString().replace(/\'/g, "_ap_");
23
+ sRet = sRet.toString().replace(/\=/g, "_ug_");
24
+ sRet = sRet.toString().replace(/\\/g, "_bs_");
25
+ sRet = sRet.toString().replace(/\(/g, "_pa_");
26
+ sRet = sRet.toString().replace(/\)/g, "_pc_");
27
+ sRet = sRet.toString().replace(/\*/g, "_as_");
28
+ sRet = sRet.toString().replace(/\[/g, "_qa_");
29
+ sRet = sRet.toString().replace(/\]/g, "_qc_");
30
+ sRet = sRet.toString().replace(/\^/g, "_fu_");
31
+ sRet = sRet.toString().replace(/\|/g, "_pi_");
32
+ sRet = sRet.toString().replace(/\"/g, "_dc_");
33
+ sRet = sRet.toString().replace(/\#/g, "_");
34
+ // slug.charmap['.'] = '_stop_';
35
+ // slug.charmap['?'] = '_qm_';
36
+ // slug.charmap['!'] = '_em_';
37
+ // slug.charmap[','] = '_pause_';
38
+ // slug.charmap[':'] = '_colon_';
39
+ // slug.charmap[';'] = '_semicolon_';
40
+ // slug.charmap['<'] = '_less_';
41
+ // slug.charmap['>'] = '_greater_';
42
+ return sRet;
43
+ }
44
+
45
+
46
+ // Node Register
47
+ function PollyNode(config) {
48
+ RED.nodes.createNode(this, config);
49
+ var node = this;
50
+ node.server = RED.nodes.getNode(config.config);
51
+ if (!node.server) {
52
+ RED.log.error('Missing Polly config');
53
+ return;
54
+ }
55
+ node.ssml = config.ssml;
56
+ node.oTimerSonosConnectionCheck = null;
57
+ node.sSonosIPAddress = "";
58
+ node.sonosCoordinatorGroupName = "";
59
+ node.sonoshailing = "0"; // Hailing file
60
+ node.sSonosIPAddress = config.sonosipaddress.trim();
61
+ node.voiceId = config.voice || 0;
62
+ node.sSonosVolume = config.sonosvolume;
63
+ node.sonoshailing = config.sonoshailing;
64
+ node.msg = {}; // 08/05/2019 Node message
65
+ node.msg.completed = true;
66
+ node.msg.connectionerror = true;
67
+ node.userDir = node.server.TTSRootFolderPath === undefined ? path.join(RED.settings.userDir, "sonospollyttsstorage") : node.server.TTSRootFolderPath;
68
+ node.oAdditionalSonosPlayers = []; // 20/03/2020 Contains other players to be grouped
69
+ node.rules = config.rules || [{}];
70
+ node.sNoderedURL = "";
71
+ node.oTimerCacheFlowMSG = null; // 05/12/2020
72
+ node.tempMSGStorage = []; // 04/12/2020 Temporary stores the flow messages
73
+ node.bBusyPlayingQueue = false; // 04/12/2020 is busy during playing of the queue
74
+ node.currentMSGbeingSpoken = {}; // Stores the current message being spoken
75
+ node.sonosCoordinatorPreviousVolumeSetByApp = 0; // 05/07/2021 stores the main payer volume set by the sonos app
76
+ node.playertype = config.playertype === undefined ? "sonos" : config.playertype; // 20/09/2021 Player type
77
+ node.speakingpitch = config.speakingpitch === undefined ? "0" : config.speakingpitch; // 21/09/2021 AudioConfig speakingpitch
78
+ node.speakingrate = config.speakingrate === undefined ? "1" : config.speakingrate; // 21/09/2021 AudioConfig speakingrate
79
+ node.unmuteIfMuted = config.unmuteIfMuted === undefined ? false : config.unmuteIfMuted; // 21/10/2021 Unmute if previiously muted.
80
+ node.sonosCoordinatorIsPreviouslyMuted = false;
81
+ node.passThroughMessage = {};
82
+ node.bTimeOutPlay = false;
83
+
84
+ if (typeof node.server !== "undefined" && node.server !== null) {
85
+ node.sNoderedURL = node.server.sNoderedURL || "";
43
86
  }
44
87
 
88
+ // 20/11/2019 Used to call the status update
89
+ node.setNodeStatus = ({ fill, shape, text }) => {
90
+ try {
91
+ var dDate = new Date();
92
+ node.status({ fill: fill, shape: shape, text: text + " (" + dDate.getDate() + ", " + dDate.toLocaleTimeString() + ")" });
93
+ } catch (error) { }
45
94
 
46
- // Node Register
47
- function PollyNode(config) {
48
- RED.nodes.createNode(this, config);
49
- var node = this;
50
- node.server = RED.nodes.getNode(config.config);
51
- if (!node.server) {
52
- RED.log.error('Missing Polly config');
53
- return;
54
- }
55
- node.ssml = config.ssml;
56
- node.oTimerSonosConnectionCheck = null;
57
- node.sSonosIPAddress = "";
58
- node.sonosCoordinatorGroupName = "";
59
- node.sonoshailing = "0"; // Hailing file
60
- node.sSonosIPAddress = config.sonosipaddress.trim();
61
- node.voiceId = config.voice || 0;
62
- node.sSonosVolume = config.sonosvolume;
63
- node.sonoshailing = config.sonoshailing;
64
- node.msg = {}; // 08/05/2019 Node message
65
- node.msg.completed = true;
66
- node.msg.connectionerror = true;
67
- node.userDir = node.server.TTSRootFolderPath === undefined ? path.join(RED.settings.userDir, "sonospollyttsstorage") : node.server.TTSRootFolderPath;
68
- node.oAdditionalSonosPlayers = []; // 20/03/2020 Contains other players to be grouped
69
- node.rules = config.rules || [{}];
70
- node.sNoderedURL = "";
71
- node.oTimerCacheFlowMSG = null; // 05/12/2020
72
- node.tempMSGStorage = []; // 04/12/2020 Temporary stores the flow messages
73
- node.bBusyPlayingQueue = false; // 04/12/2020 is busy during playing of the queue
74
- node.currentMSGbeingSpoken = {}; // Stores the current message being spoken
75
- node.sonosCoordinatorPreviousVolumeSetByApp = 0; // 05/07/2021 stores the main payer volume set by the sonos app
76
- node.playertype = config.playertype === undefined ? "sonos" : config.playertype; // 20/09/2021 Player type
77
- node.speakingpitch = config.speakingpitch === undefined ? "0" : config.speakingpitch; // 21/09/2021 AudioConfig speakingpitch
78
- node.speakingrate = config.speakingrate === undefined ? "1" : config.speakingrate; // 21/09/2021 AudioConfig speakingrate
79
- node.unmuteIfMuted = config.unmuteIfMuted === undefined ? false : config.unmuteIfMuted; // 21/10/2021 Unmute if previiously muted.
80
- node.sonosCoordinatorIsPreviouslyMuted = false;
81
- node.passThroughMessage = {};
82
- node.bTimeOutPlay = false;
95
+ }
83
96
 
84
- if (typeof node.server !== "undefined" && node.server !== null) {
85
- node.sNoderedURL = node.server.sNoderedURL || "";
86
- }
97
+ //#region ASYNC DECLARATIONS
98
+ // 30/12/2020 we are at the end of this crazy 2020
99
+ function getMusicQueue(_oPlayer = node.SonosClient) {
100
+ return new Promise(function (resolve, reject) {
101
+ var oRet = null;
102
+ _oPlayer.currentTrack().then(track => {
103
+ oRet = track;// .queuePosition || 1; // Get the current track in the queue.
104
+ _oPlayer.getCurrentState().then(state => {
105
+ // A music queue is playing and no TTS is speaking?
106
+ oRet.state = state;
107
+ resolve(oRet);
108
+ }).catch(err => {
109
+ //console.log('ttsultimate: getCurrentState: Error occurred %j', err);
110
+ reject(err);
111
+ })
112
+ }).catch(err => {
113
+ reject(err);
114
+ //console.log('ttsultimate: Error currentTrackoccurred %j', err);
115
+ });
116
+ });
117
+ };
118
+
119
+
120
+ let iWaitAfterSync = 500;
121
+ // 24/08/2021 Sync wrapper
122
+ function PLAYSync(_toPlay, _oPlayer = node.SonosClient) {
123
+ return new Promise((resolve, reject) => {
124
+ _oPlayer.play(_toPlay).then(result => {
125
+ if (iWaitAfterSync > 2000) console.log("PLAYSYNC")
126
+ let t = setTimeout(() => {
127
+ resolve(true);
128
+ }, iWaitAfterSync);
129
+ }).catch(err => {
130
+ RED.log.error("ttsultimate: Error PLAYSync: " + err.message);
131
+ reject(err);
132
+ });
133
+ });
134
+ }
87
135
 
88
- // 20/11/2019 Used to call the status update
89
- node.setNodeStatus = ({ fill, shape, text }) => {
90
- try {
91
- var dDate = new Date();
92
- node.status({ fill: fill, shape: shape, text: text + " (" + dDate.getDate() + ", " + dDate.toLocaleTimeString() + ")" });
93
- } catch (error) { }
136
+ // 24/08/2021 Sync wrapper
137
+ function SEEKSync(_Position, _oPlayer = node.SonosClient) {
138
+ return new Promise((resolve, reject) => {
139
+ _oPlayer.seek(_Position).then(result => {
140
+ if (iWaitAfterSync > 2000) console.log("SEEKSync", _Position)
141
+ let t = setTimeout(() => {
142
+ resolve(true);
143
+ }, iWaitAfterSync);
144
+ }).catch(err => {
145
+ //RED.log.error("ttsultimate: Error SEEKSync: " + err.message);
146
+ reject(err);
147
+ });
148
+ });
149
+ }
94
150
 
95
- }
151
+ // 24/08/2021 Sync wrapper
152
+ function SELECTQUEUESync(_oPlayer = node.SonosClient) {
153
+ return new Promise((resolve, reject) => {
154
+ _oPlayer.selectQueue().then(result => {
155
+ if (iWaitAfterSync > 2000) console.log("SELECTQUEUESync")
156
+ try {
157
+ STOPSync(); // The SetQueue automatically starts playing, so i need to stop it now!
158
+ } catch (error) {
159
+ }
160
+ let t = setTimeout(() => {
161
+ resolve(true);
162
+ }, iWaitAfterSync);
163
+ }).catch(err => {
164
+ RED.log.error("ttsultimate: Error SELECTQUEUESync: " + err.message);
165
+ reject(err);
166
+ });
167
+ });
168
+ }
96
169
 
97
- //#region ASYNC DECLARATIONS
98
- // 30/12/2020 we are at the end of this crazy 2020
99
- function getMusicQueue(_oPlayer = node.SonosClient) {
100
- return new Promise(function (resolve, reject) {
101
- var oRet = null;
102
- _oPlayer.currentTrack().then(track => {
103
- oRet = track;// .queuePosition || 1; // Get the current track in the queue.
104
- _oPlayer.getCurrentState().then(state => {
105
- // A music queue is playing and no TTS is speaking?
106
- oRet.state = state;
107
- resolve(oRet);
108
- }).catch(err => {
109
- //console.log('ttsultimate: getCurrentState: Error occurred %j', err);
110
- reject(err);
111
- })
112
- }).catch(err => {
113
- reject(err);
114
- //console.log('ttsultimate: Error currentTrackoccurred %j', err);
115
- });
116
- });
117
- };
170
+ // 24/08/2021 Sync wrapper
171
+ function SELECTTRACKSync(_queuePosition, _oPlayer = node.SonosClient) {
172
+ return new Promise((resolve, reject) => {
173
+ _oPlayer.selectTrack(_queuePosition).then(result => {
174
+ if (iWaitAfterSync > 2000) console.log("SELECTTRACKSync", _queuePosition)
175
+ let t = setTimeout(() => {
176
+ resolve(true);
177
+ }, iWaitAfterSync);
178
+ }).catch(err => {
179
+ RED.log.error("ttsultimate: Error SELECTTRACKSync: " + err.message);
180
+ reject(err);
181
+ });
182
+ });
183
+ }
118
184
 
185
+ // 24/08/2021 Sync wrapper
186
+ function STOPSync(_oPlayer = node.SonosClient) {
187
+ return new Promise((resolve, reject) => {
188
+ _oPlayer.stop().then(result => {
189
+ if (iWaitAfterSync > 2000) console.log("STOPSync")
190
+ let t = setTimeout(() => {
191
+ resolve(true);
192
+ }, iWaitAfterSync);
193
+ }).catch(err => {
194
+ //RED.log.error("ttsultimate: Error STOPSync: " + err.message);
195
+ reject(err);
196
+ });
197
+ });
198
+ }
119
199
 
120
- let iWaitAfterSync = 500;
121
- // 24/08/2021 Sync wrapper
122
- function PLAYSync(_toPlay, _oPlayer = node.SonosClient) {
123
- return new Promise((resolve, reject) => {
124
- _oPlayer.play(_toPlay).then(result => {
125
- if (iWaitAfterSync > 2000) console.log("PLAYSYNC")
126
- let t = setTimeout(() => {
127
- resolve(true);
128
- }, iWaitAfterSync);
129
- }).catch(err => {
130
- RED.log.error("ttsultimate: Error PLAYSync: " + err.message);
131
- reject(err);
132
- });
133
- });
134
- }
200
+ // 24/08/2021 Sync wrapper
201
+ function GETVOLUMESync(_oPlayer = node.SonosClient) {
202
+ return new Promise((resolve, reject) => {
203
+ _oPlayer.getVolume().then(volume => {
204
+ if (iWaitAfterSync > 2000) console.log("GETVOLUMESync", volume)
205
+ resolve(volume);
206
+ }).catch(err => {
207
+ RED.log.error("ttsultimate: Error GETVOLUMESync: " + err.message);
208
+ reject(err);
209
+ });
210
+ });
211
+ }
135
212
 
136
- // 24/08/2021 Sync wrapper
137
- function SEEKSync(_Position, _oPlayer = node.SonosClient) {
138
- return new Promise((resolve, reject) => {
139
- _oPlayer.seek(_Position).then(result => {
140
- if (iWaitAfterSync > 2000) console.log("SEEKSync", _Position)
141
- let t = setTimeout(() => {
142
- resolve(true);
143
- }, iWaitAfterSync);
144
- }).catch(err => {
145
- //RED.log.error("ttsultimate: Error SEEKSync: " + err.message);
146
- reject(err);
147
- });
148
- });
149
- }
213
+ // 24/08/2021 Sync wrapper
214
+ function SETVOLUMESync(_volume, _oPlayer = node.SonosClient) {
215
+ return new Promise((resolve, reject) => {
216
+ _oPlayer.setVolume(_volume).then(result => {
217
+ if (iWaitAfterSync > 2000) console.log("SETVOLUMESync", _volume)
218
+ resolve(true);
219
+ }).catch(err => {
220
+ RED.log.error("ttsultimate: Error SETVOLUMESync: " + err.message);
221
+ reject(err);
222
+ });
223
+ });
224
+ }
150
225
 
151
- // 24/08/2021 Sync wrapper
152
- function SELECTQUEUESync(_oPlayer = node.SonosClient) {
153
- return new Promise((resolve, reject) => {
154
- _oPlayer.selectQueue().then(result => {
155
- if (iWaitAfterSync > 2000) console.log("SELECTQUEUESync")
156
- try {
157
- STOPSync(); // The SetQueue automatically starts playing, so i need to stop it now!
158
- } catch (error) {
159
- }
160
- let t = setTimeout(() => {
161
- resolve(true);
162
- }, iWaitAfterSync);
163
- }).catch(err => {
164
- RED.log.error("ttsultimate: Error SELECTQUEUESync: " + err.message);
165
- reject(err);
166
- });
167
- });
168
- }
226
+ // 24/08/2021 Sync wrapper
227
+ function setAVTransportURISync(_Uri, _oPlayer = node.SonosClient) {
228
+ return new Promise((resolve, reject) => {
229
+ _oPlayer.setAVTransportURI(_Uri).then(volume => {
230
+ if (iWaitAfterSync > 2000) console.log("setAVTransportURISync", _Uri)
231
+ resolve(true);
232
+ }).catch(err => {
233
+ RED.log.error("ttsultimate: Error setAVTransportURISync: " + err.message);
234
+ reject(err);
235
+ });
236
+ });
237
+ }
169
238
 
170
- // 24/08/2021 Sync wrapper
171
- function SELECTTRACKSync(_queuePosition, _oPlayer = node.SonosClient) {
172
- return new Promise((resolve, reject) => {
173
- _oPlayer.selectTrack(_queuePosition).then(result => {
174
- if (iWaitAfterSync > 2000) console.log("SELECTTRACKSync", _queuePosition)
175
- let t = setTimeout(() => {
176
- resolve(true);
177
- }, iWaitAfterSync);
178
- }).catch(err => {
179
- RED.log.error("ttsultimate: Error SELECTTRACKSync: " + err.message);
180
- reject(err);
181
- });
182
- });
183
- }
239
+ // 24/08/2021 Sync wrapper
240
+ function getCurrentStateSync(_oPlayer = node.SonosClient) {
241
+ return new Promise((resolve, reject) => {
242
+ _oPlayer.getCurrentState().then(state => {
243
+ resolve(state);
244
+ }).catch(err => {
245
+ RED.log.error("ttsultimate: Error getCurrentStateSync: " + err.message);
246
+ reject(err);
247
+ });
248
+ });
249
+ }
184
250
 
185
- // 24/08/2021 Sync wrapper
186
- function STOPSync(_oPlayer = node.SonosClient) {
187
- return new Promise((resolve, reject) => {
188
- _oPlayer.stop().then(result => {
189
- if (iWaitAfterSync > 2000) console.log("STOPSync")
190
- let t = setTimeout(() => {
191
- resolve(true);
192
- }, iWaitAfterSync);
193
- }).catch(err => {
194
- //RED.log.error("ttsultimate: Error STOPSync: " + err.message);
195
- reject(err);
196
- });
197
- });
198
- }
251
+ // 21/10/2021 Sync wrapper
252
+ function GETMutedSync(_oPlayer = node.SonosClient) {
253
+ return new Promise((resolve, reject) => {
254
+ _oPlayer.getMuted().then(state => {
255
+ resolve(state);
256
+ }).catch(err => {
257
+ RED.log.error("ttsultimate: Error GETMutedSync: " + err.message);
258
+ reject(err);
259
+ });
260
+ });
261
+ }
199
262
 
200
- // 24/08/2021 Sync wrapper
201
- function GETVOLUMESync(_oPlayer = node.SonosClient) {
202
- return new Promise((resolve, reject) => {
203
- _oPlayer.getVolume().then(volume => {
204
- if (iWaitAfterSync > 2000) console.log("GETVOLUMESync", volume)
205
- resolve(volume);
206
- }).catch(err => {
207
- RED.log.error("ttsultimate: Error GETVOLUMESync: " + err.message);
208
- reject(err);
209
- });
210
- });
211
- }
263
+ // 21/10/2021 Sync wrapper
264
+ function SETMutedSync(_muted, _oPlayer = node.SonosClient) {
265
+ return new Promise((resolve, reject) => {
266
+ _oPlayer.setMuted(_muted).then(state => {
267
+ resolve(state);
268
+ }).catch(err => {
269
+ RED.log.error("ttsultimate: Error SETMutedSync: " + err.message);
270
+ reject(err);
271
+ });
272
+ });
273
+ }
212
274
 
213
- // 24/08/2021 Sync wrapper
214
- function SETVOLUMESync(_volume, _oPlayer = node.SonosClient) {
215
- return new Promise((resolve, reject) => {
216
- _oPlayer.setVolume(_volume).then(result => {
217
- if (iWaitAfterSync > 2000) console.log("SETVOLUMESync", _volume)
218
- resolve(true);
219
- }).catch(err => {
220
- RED.log.error("ttsultimate: Error SETVOLUMESync: " + err.message);
221
- reject(err);
222
- });
223
- });
275
+ // 20/03/2020 Join Coordinator queue
276
+ // ######################################################
277
+ async function groupSpeakersSync() {
278
+ // 05/07/2021 Get the main coordinator player previous volume set by app
279
+ try {
280
+ node.sonosCoordinatorPreviousVolumeSetByApp = await GETVOLUMESync();
281
+ node.sonosCoordinatorIsPreviouslyMuted = await GETMutedSync();
282
+ } catch (error) {
283
+ node.sonosCoordinatorPreviousVolumeSetByApp = node.sSonosVolume;
284
+ node.sonosCoordinatorIsPreviouslyMuted = false;
285
+ }
286
+ // 30/03/2020 in the middle of coronavirus emergency. Group Speakers
287
+ for (let index = 0; index < node.oAdditionalSonosPlayers.length; index++) {
288
+ let element = node.oAdditionalSonosPlayers[index].oPlayer;
289
+
290
+ // 02/07/2021 Get the additional's player's volume set by app and the current track, to be set again in ungroupspealers
291
+ try {
292
+ element.additionalPlayerPreviousVolumeSetByApp = await element.getVolume();
293
+ element.additionalPlayerCurrentTrack = await getMusicQueue(element);
294
+ } catch (error) {
295
+ RED.log.warn("ttsultimate: Error setting volume of joined device " + error.message);
224
296
  }
225
-
226
- // 24/08/2021 Sync wrapper
227
- function setAVTransportURISync(_Uri, _oPlayer = node.SonosClient) {
228
- return new Promise((resolve, reject) => {
229
- _oPlayer.setAVTransportURI(_Uri).then(volume => {
230
- if (iWaitAfterSync > 2000) console.log("setAVTransportURISync", _Uri)
231
- resolve(true);
232
- }).catch(err => {
233
- RED.log.error("ttsultimate: Error setAVTransportURISync: " + err.message);
234
- reject(err);
235
- });
236
- });
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);
237
302
  }
238
303
 
239
- // 24/08/2021 Sync wrapper
240
- function getCurrentStateSync(_oPlayer = node.SonosClient) {
241
- return new Promise((resolve, reject) => {
242
- _oPlayer.getCurrentState().then(state => {
243
- resolve(state);
244
- }).catch(err => {
245
- RED.log.error("ttsultimate: Error getCurrentStateSync: " + err.message);
246
- reject(err);
247
- });
248
- });
304
+ try {
305
+ await element.joinGroup(node.sonosCoordinatorGroupName);
306
+ } catch (error) {
307
+ RED.log.warn("ttsultimate: Error joining device " + error.message);
249
308
  }
309
+ };
310
+ }
250
311
 
251
- // 21/10/2021 Sync wrapper
252
- function GETMutedSync(_oPlayer = node.SonosClient) {
253
- return new Promise((resolve, reject) => {
254
- _oPlayer.getMuted().then(state => {
255
- resolve(state);
256
- }).catch(err => {
257
- RED.log.error("ttsultimate: Error GETMutedSync: " + err.message);
258
- reject(err);
259
- });
260
- });
312
+ // 20/03/2020 Ungroup Coordinator queue
313
+ async function ungroupSpeakersSync() {
314
+ // 05/07/2021, set the previous app volume for main coordinator player
315
+ try {
316
+ await SETVOLUMESync(node.sonosCoordinatorPreviousVolumeSetByApp);
317
+ } catch (error) {
318
+ RED.log.warn("ttsultimate: Error set preious volume on main coordinator in ungroupSpeakers " + error.message);
319
+ }
320
+ // 21/10/2021 Unmute?
321
+ if (node.unmuteIfMuted && node.sonosCoordinatorIsPreviouslyMuted) {
322
+ try {
323
+ await SETMutedSync(true);
324
+ } catch (error) {
325
+ RED.log.warn("ttsultimate: Error set preivous mute state on main coordinator in ungroupSpeakers " + error.message);
261
326
  }
262
-
263
- // 21/10/2021 Sync wrapper
264
- function SETMutedSync(_muted, _oPlayer = node.SonosClient) {
265
- return new Promise((resolve, reject) => {
266
- _oPlayer.setMuted(_muted).then(state => {
267
- resolve(state);
268
- }).catch(err => {
269
- RED.log.error("ttsultimate: Error SETMutedSync: " + err.message);
270
- reject(err);
271
- });
272
- });
327
+ }
328
+
329
+ for (let index = 0; index < node.oAdditionalSonosPlayers.length; index++) {
330
+ let element = node.oAdditionalSonosPlayers[index].oPlayer;
331
+ try {
332
+ await element.leaveGroup();
333
+ } catch (error) {
334
+ // Dont care
335
+ RED.log.warn("ttsultimate: Error leaving group device " + error.message);
273
336
  }
274
-
275
- // 20/03/2020 Join Coordinator queue
276
- // ######################################################
277
- async function groupSpeakersSync() {
278
- // 05/07/2021 Get the main coordinator player previous volume set by app
279
- try {
280
- node.sonosCoordinatorPreviousVolumeSetByApp = await GETVOLUMESync();
281
- node.sonosCoordinatorIsPreviouslyMuted = await GETMutedSync();
282
- } catch (error) {
283
- node.sonosCoordinatorPreviousVolumeSetByApp = node.sSonosVolume;
284
- node.sonosCoordinatorIsPreviouslyMuted = false;
285
- }
286
- // 30/03/2020 in the middle of coronavirus emergency. Group Speakers
287
- for (let index = 0; index < node.oAdditionalSonosPlayers.length; index++) {
288
- let element = node.oAdditionalSonosPlayers[index].oPlayer;
289
-
290
- // 02/07/2021 Get the additional's player's volume set by app and the current track, to be set again in ungroupspealers
291
- try {
292
- element.additionalPlayerPreviousVolumeSetByApp = await element.getVolume();
293
- element.additionalPlayerCurrentTrack = await getMusicQueue(element);
294
- } catch (error) {
295
- RED.log.warn("ttsultimate: Error setting volume of joined device " + error.message);
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
- }
303
-
304
- try {
305
- await element.joinGroup(node.sonosCoordinatorGroupName);
306
- } catch (error) {
307
- RED.log.warn("ttsultimate: Error joining device " + error.message);
308
- }
309
- };
337
+ if (element.additionalPlayerPreviousVolumeSetByApp !== undefined) {
338
+ try {
339
+ await element.setVolume(element.additionalPlayerPreviousVolumeSetByApp);
340
+ } catch (error) {
341
+ RED.log.warn("ttsultimate: Error set previous volume on group device " + error.message);
342
+ }
310
343
  }
311
-
312
- // 20/03/2020 Ungroup Coordinator queue
313
- async function ungroupSpeakersSync() {
314
- // 05/07/2021, set the previous app volume for main coordinator player
315
- try {
316
- await SETVOLUMESync(node.sonosCoordinatorPreviousVolumeSetByApp);
317
- } catch (error) {
318
- RED.log.warn("ttsultimate: Error set preious volume on main coordinator in ungroupSpeakers " + error.message);
319
- }
320
- // 21/10/2021 Unmute?
321
- if (node.unmuteIfMuted && node.sonosCoordinatorIsPreviouslyMuted) {
322
- try {
323
- await SETMutedSync(true);
324
- } catch (error) {
325
- RED.log.warn("ttsultimate: Error set preivous mute state on main coordinator in ungroupSpeakers " + error.message);
326
- }
327
- }
328
-
329
- for (let index = 0; index < node.oAdditionalSonosPlayers.length; index++) {
330
- let element = node.oAdditionalSonosPlayers[index].oPlayer;
331
- try {
332
- await element.leaveGroup();
333
- } catch (error) {
334
- // Dont care
335
- RED.log.warn("ttsultimate: Error leaving group device " + error.message);
336
- }
337
- if (element.additionalPlayerPreviousVolumeSetByApp !== undefined) {
338
- try {
339
- await element.setVolume(element.additionalPlayerPreviousVolumeSetByApp);
340
- } catch (error) {
341
- RED.log.warn("ttsultimate: Error set previous volume on group device " + error.message);
342
- }
343
- }
344
- // 21/10/2021 Unmute?
345
- if (node.unmuteIfMuted && element.isPreviouslyMuted) {
346
- try {
347
- await element.setMuted(true);
348
- } catch (error) {
349
- RED.log.warn("ttsultimate: Error set previous mute state on group device " + error.message);
350
- }
351
- }
352
- }
344
+ // 21/10/2021 Unmute?
345
+ if (node.unmuteIfMuted && element.isPreviouslyMuted) {
346
+ try {
347
+ await element.setMuted(true);
348
+ } catch (error) {
349
+ RED.log.warn("ttsultimate: Error set previous mute state on group device " + error.message);
350
+ }
353
351
  }
354
- // ######################################################
355
-
356
- async function delay(ms) {
357
- return new Promise(function (resolve, reject) {
358
- try {
359
- node.timerWait = setTimeout(resolve, ms);
360
- } catch (error) {
361
- reject();
362
- }
363
- });
352
+ }
353
+ }
354
+ // ######################################################
355
+
356
+ async function delay(ms) {
357
+ return new Promise(function (resolve, reject) {
358
+ try {
359
+ node.timerWait = setTimeout(resolve, ms);
360
+ } catch (error) {
361
+ reject();
364
362
  }
363
+ });
364
+ }
365
365
 
366
- //#endregion
366
+ //#endregion
367
367
 
368
368
 
369
- // 27/11/2019 Check Sonos connection health
369
+ // 27/11/2019 Check Sonos connection health
370
370
 
371
- node.CheckSonosConnection = () => {
372
- if (node.playertype === "sonos") {
373
- node.SonosClient.getCurrentState().then(state => {
374
-
375
- // 11/12/202020 The connection with Sonos is OK.
376
- if (node.msg.connectionerror == true) {
377
- node.flushQueue();
378
- node.setNodeStatus({ fill: "green", shape: "dot", text: "Sonos is connected." });
379
- node.msg.connectionerror = false;
380
- node.send([null, { payload: node.msg.connectionerror }]);
381
- }
382
- node.oTimerSonosConnectionCheck = setTimeout(function () { node.CheckSonosConnection(); }, 5000);
383
-
384
- }).catch(err => {
385
- node.setNodeStatus({ fill: "red", shape: "dot", text: "Sonos connection is DOWN: " + err.message });
386
- node.flushQueue();
387
- // 11/12/2020 Set node output to signal connectio error
388
- if (node.msg.connectionerror == false) {
389
- node.msg.connectionerror = true;
390
- node.send([null, { payload: node.msg.connectionerror }]);
391
- }
392
- node.oTimerSonosConnectionCheck = setTimeout(function () { node.CheckSonosConnection(); }, 10000);
393
- });
394
- } else {
395
- node.setNodeStatus({ fill: "green", shape: "dot", text: "Ready." });
396
- node.msg.connectionerror = false;
397
- node.send([null, { payload: node.msg.connectionerror }]);
398
- }
371
+ node.CheckSonosConnection = () => {
372
+ if (node.playertype === "sonos") {
373
+ node.SonosClient.getCurrentState().then(state => {
399
374
 
400
- }
375
+ // 11/12/202020 The connection with Sonos is OK.
376
+ if (node.msg.connectionerror == true) {
377
+ node.flushQueue();
378
+ node.setNodeStatus({ fill: "green", shape: "dot", text: "Sonos is connected." });
379
+ node.msg.connectionerror = false;
380
+ node.send([null, { payload: node.msg.connectionerror }]);
381
+ }
382
+ node.oTimerSonosConnectionCheck = setTimeout(function () { node.CheckSonosConnection(); }, 5000);
383
+
384
+ }).catch(err => {
385
+ node.setNodeStatus({ fill: "red", shape: "dot", text: "Sonos connection is DOWN: " + err.message });
386
+ node.flushQueue();
387
+ // 11/12/2020 Set node output to signal connectio error
388
+ if (node.msg.connectionerror == false) {
389
+ node.msg.connectionerror = true;
390
+ node.send([null, { payload: node.msg.connectionerror }]);
391
+ }
392
+ node.oTimerSonosConnectionCheck = setTimeout(function () { node.CheckSonosConnection(); }, 10000);
393
+ });
394
+ } else {
395
+ node.setNodeStatus({ fill: "green", shape: "dot", text: "Ready." });
396
+ node.msg.connectionerror = false;
397
+ node.send([null, { payload: node.msg.connectionerror }]);
398
+ }
401
399
 
402
- // 20/09/2021 If Sonos, do init
403
- if (node.playertype === "sonos") {
404
- // Create sonos client & groups
405
- node.SonosClient = new sonos.Sonos(node.sSonosIPAddress);
406
- // 20/03/2020 Set the coorinator's zone name
407
- node.SonosClient.getName().then(info => {
408
- node.sonosCoordinatorGroupName = info;
409
- RED.log.info("ttsultimate: ZONE COORDINATOR " + JSON.stringify(info));
410
- }).catch(err => {
400
+ }
411
401
 
412
- });
413
- // Fill the node.oAdditionalSonosPlayers with all sonos object in the rules
414
- for (let index = 0; index < node.rules.length; index++) {
415
- let element = node.rules[index]; // Rule row is {host:"192.168.1.12,hostVolumeAdjust:0}
416
- // 12/04/2022 Create an object containing the addidtional player and the adapted volume
417
- node.oAdditionalSonosPlayers.push({ oPlayer: new sonos.Sonos(element.host), hostVolumeAdjust: Number(element.hostVolumeAdjust) });
418
- RED.log.info("ttsultimate: FOUND ADDITIONAL PLAYER " + element.host + " Adjusted volume: " + element.hostVolumeAdjust);
419
- }
420
- // 27/11/2019 Start the connection healty check
421
- node.oTimerSonosConnectionCheck = setTimeout(function () { node.CheckSonosConnection(); }, 5000);
422
- } else if (node.playertype === "noplayer") {
423
- node.msg.connectionerror = false;
424
- }
402
+ // 20/09/2021 If Sonos, do init
403
+ if (node.playertype === "sonos") {
404
+ // Create sonos client & groups
405
+ node.SonosClient = new sonos.Sonos(node.sSonosIPAddress);
406
+ // 20/03/2020 Set the coorinator's zone name
407
+ node.SonosClient.getName().then(info => {
408
+ node.sonosCoordinatorGroupName = info;
409
+ RED.log.info("ttsultimate: ZONE COORDINATOR " + JSON.stringify(info));
410
+ }).catch(err => {
411
+
412
+ });
413
+ // Fill the node.oAdditionalSonosPlayers with all sonos object in the rules
414
+ for (let index = 0; index < node.rules.length; index++) {
415
+ let element = node.rules[index]; // Rule row is {host:"192.168.1.12,hostVolumeAdjust:0}
416
+ // 12/04/2022 Create an object containing the addidtional player and the adapted volume
417
+ node.oAdditionalSonosPlayers.push({ oPlayer: new sonos.Sonos(element.host), hostVolumeAdjust: Number(element.hostVolumeAdjust) });
418
+ RED.log.info("ttsultimate: FOUND ADDITIONAL PLAYER " + element.host + " Adjusted volume: " + element.hostVolumeAdjust);
419
+ }
420
+ // 27/11/2019 Start the connection healty check
421
+ node.oTimerSonosConnectionCheck = setTimeout(function () { node.CheckSonosConnection(); }, 5000);
422
+ } else if (node.playertype === "noplayer") {
423
+ node.msg.connectionerror = false;
424
+ }
425
425
 
426
- node.setNodeStatus({ fill: 'grey', shape: 'ring', text: 'Initialized.' });
426
+ node.setNodeStatus({ fill: 'grey', shape: 'ring', text: 'Initialized.' });
427
+
428
+ // 22/09/2020 Flush Queue and set to stopped
429
+ node.flushQueue = () => {
430
+ // 10/04/2018 Remove the TTS message from the queue
431
+ node.tempMSGStorage = [];
432
+ // Exit whatever cycle
433
+ node.bTimeOutPlay = true;
434
+ node.bBusyPlayingQueue = false;
435
+ node.currentMSGbeingSpoken = {};
436
+ if (node.server.whoIsUsingTheServer === node.id) node.server.whoIsUsingTheServer = "";
437
+ }
427
438
 
428
- // 22/09/2020 Flush Queue and set to stopped
429
- node.flushQueue = () => {
430
- // 10/04/2018 Remove the TTS message from the queue
431
- node.tempMSGStorage = [];
432
- // Exit whatever cycle
433
- node.bTimeOutPlay = true;
434
- node.bBusyPlayingQueue = false;
435
- node.currentMSGbeingSpoken = {};
436
- if (node.server.whoIsUsingTheServer === node.id) node.server.whoIsUsingTheServer = "";
439
+ // 30/12/2020 Supergiovane resume queue for radio, queue music, TV in , line in etc.
440
+ async function resumeMusicQueue(_oTrack, _oPlayer = node.SonosClient) {
441
+
442
+ if (_oTrack !== null) {
443
+ // Do some checks on the track.
444
+ if (_oTrack.hasOwnProperty("duration") && _oTrack.duration === 0 ||
445
+ (_oTrack.uri.startsWith("x-sonosprog-http") || _oTrack.uri.startsWith("x-sonosapi-hls-static") || _oTrack.uri.startsWith("x-sonos-spotify"))) {
446
+ // Stream
447
+ _oTrack.trackType = "stream";
448
+ } else if (_oTrack.hasOwnProperty("duration") && isNaN(_oTrack.duration)) {
449
+ // Line input
450
+ _oTrack.trackType = "lineinput";
451
+ } else {
452
+ // Music queue
453
+ _oTrack.trackType = "musicqueue";
454
+ }
455
+ } else {
456
+ // Track is null, nothing to resume.
457
+ return false;
458
+ }
459
+
460
+ // It's a radio station or a generic stream, not a queue.
461
+ if (_oTrack.trackType === "stream") {
462
+ if (_oTrack.state === "playing") {
463
+ // 03/09/2021 Play if it was playing
464
+ try {
465
+ await PLAYSync(_oTrack.uri, _oPlayer);
466
+ } catch (error) {
467
+ return error;
468
+ }
469
+ try {
470
+ await delay(1000);
471
+ await SEEKSync(_oTrack.position, _oPlayer);
472
+ } catch (error) {
473
+ // Don't care
474
+ }
475
+ }
476
+ } else {
477
+ if (_oTrack.trackType === "musicqueue") { // This indicates that is an audio file or stream station
478
+ try {
479
+ await SELECTQUEUESync(_oPlayer);
480
+ } catch (error) {
481
+ return error;
482
+ }
483
+ try {
484
+ await delay(1000);
485
+ await SELECTTRACKSync(_oTrack.queuePosition, _oPlayer);
486
+ } catch (error) {
487
+ return error;
488
+ }
489
+ try {
490
+ await delay(1000);
491
+ await SEEKSync(_oTrack.position, _oPlayer);
492
+ } catch (error) {
493
+ // Don't care
494
+ }
495
+ if (_oTrack.state === "playing") {
496
+ // 24/08/2021 Play if it was playing
497
+ try {
498
+ await PLAYSync(_oPlayer);
499
+ } catch (error) {
500
+ return error;
501
+ }
502
+ } else {
503
+ /// 03/09/2021
504
+ try {
505
+ await STOPSync(_oPlayer);
506
+ } catch (error) {
507
+ return error;
508
+ }
509
+ }
510
+ } else if (_oTrack.trackType === "lineinput") {
511
+ // Line in, TV in, etc...
512
+ if (_oTrack.state === "playing") {
513
+ try {
514
+ await setAVTransportURISync(_oTrack.uri, _oPlayer);
515
+ } catch (error) {
516
+ return error;
517
+ }
518
+ }
519
+ }
520
+ }
521
+ let t = setTimeout(() => { return true; }, 5000); // Wait some seconds
522
+ };
523
+
524
+ // Handle the queue
525
+ async function HandleQueue() {
526
+ node.bBusyPlayingQueue = true;
527
+ node.server.whoIsUsingTheServer = node.id; // Signal to other ttsultimate node, that i'm using the Sonos device
528
+ try {
529
+
530
+ if (node.playertype !== "noplayer") {
531
+ // Get the current music queue, if one
532
+ var oCurTrack = null;
533
+ try {
534
+ oCurTrack = await getMusicQueue();
535
+ // 19/04/2022 The current track of additional players is read in the groupSpeakerySync function
536
+ } catch (error) {
537
+ oCurTrack = null;
538
+ }
539
+
540
+ // 05/12/2020 Set "completed" to false and send it
541
+ node.msg.completed = false;
542
+ try {
543
+ await groupSpeakersSync(); // 20/03/2020 Group Speakers toghether and reads each current track
544
+ } catch (error) {
545
+ // Don't care.
546
+ node.setNodeStatus({ fill: "red", shape: "ring", text: "Error grouping speakers: " + error.message });
547
+ RED.log.error("ttsultimate: Error grouping speakers: " + error.message);
548
+ }
549
+
550
+ node.send([{ passThroughMessage: node.passThroughMessage, payload: node.msg.completed }, null]);
551
+
552
+ // 24/08/2021 If something was playing, stop the player https://github.com/Supergiovane/node-red-contrib-tts-ultimate/issues/32
553
+ try {
554
+ //await node.SonosClient.stop(); //.then(result => {
555
+ await STOPSync();
556
+ } catch (error) {
557
+ //RED.log.error("ttsultimate: Error stopping in HandleSend: " + error.message);
558
+ }
559
+ } else {
560
+ node.msg.completed = false;
561
+ node.send([{ passThroughMessage: node.passThroughMessage, payload: node.msg.completed }, null]);
437
562
  }
438
563
 
439
- // 30/12/2020 Supergiovane resume queue for radio, queue music, TV in , line in etc.
440
- async function resumeMusicQueue(_oTrack, _oPlayer = node.SonosClient) {
441
-
442
- if (_oTrack !== null) {
443
- // Do some checks on the track.
444
- if (_oTrack.hasOwnProperty("duration") && _oTrack.duration === 0 ||
445
- (_oTrack.uri.startsWith("x-sonosprog-http") || _oTrack.uri.startsWith("x-sonosapi-hls-static") || _oTrack.uri.startsWith("x-sonos-spotify"))) {
446
- // Stream
447
- _oTrack.trackType = "stream";
448
- } else if (_oTrack.hasOwnProperty("duration") && isNaN(_oTrack.duration)) {
449
- // Line input
450
- _oTrack.trackType = "lineinput";
564
+ while (node.tempMSGStorage.length > 0) {
565
+ node.currentMSGbeingSpoken = node.tempMSGStorage.shift()//node.tempMSGStorage[0];// Advise the whole node of the currently spoken MSG
566
+ const msg = node.currentMSGbeingSpoken.payload.toString(); // Get the text to be spoken
567
+ //node.tempMSGStorage.splice(0, 1); // Remove the first item in the array
568
+ node.sFileToBePlayed = "";
569
+ node.setNodeStatus({ fill: "gray", shape: "ring", text: "Read " + msg });
570
+
571
+ // 04/12/2020 check what really is the file to be played
572
+ if (msg.toLowerCase().startsWith("http://") || msg.toLowerCase().startsWith("https://")) {
573
+ RED.log.info('ttsultimate: HTTP filename: ' + msg);
574
+ node.sFileToBePlayed = msg;
575
+ } else if (msg.indexOf("OwnFile_") !== -1) {
576
+ RED.log.info('ttsultimate: OwnFile .MP3, skip tts, filename: ' + msg);
577
+ node.sFileToBePlayed = path.join(node.userDir, "ttspermanentfiles", msg);
578
+ } else if (msg.indexOf("Hailing_") !== -1) {
579
+ RED.log.info('ttsultimate: Hailing .MP3, skip tts, filename: ' + msg);
580
+ node.sFileToBePlayed = path.join(node.userDir, "hailingpermanentfiles", msg);
581
+ } else {
582
+ try {
583
+ // No file in cache. Download from tts service
584
+ var data = undefined;
585
+ if (node.server.ttsservice === "polly") {
586
+ var params = {
587
+ OutputFormat: "mp3",
588
+ SampleRate: '22050',
589
+ Text: msg,
590
+ TextType: node.ssml ? 'ssml' : 'text'
591
+ };
592
+ // 02/03/2022 check wether standard or neural engine is POLLY is selected
593
+ if (node.voiceId.includes("#engineType:")) {
594
+ params.VoiceId = node.voiceId.split("#engineType:")[0];
595
+ params.Engine = node.voiceId.split("#engineType:")[1];
451
596
  } else {
452
- // Music queue
453
- _oTrack.trackType = "musicqueue";
597
+ params.VoiceId = node.voiceId;
454
598
  }
455
- } else {
456
- // Track is null, nothing to resume.
457
- return false;
458
- }
459
-
460
- // It's a radio station or a generic stream, not a queue.
461
- if (_oTrack.trackType === "stream") {
462
- if (_oTrack.state === "playing") {
463
- // 03/09/2021 Play if it was playing
464
- try {
465
- await PLAYSync(_oTrack.uri, _oPlayer);
466
- } catch (error) {
467
- return error;
468
- }
469
- try {
470
- await delay(1000);
471
- await SEEKSync(_oTrack.position, _oPlayer);
472
- } catch (error) {
473
- // Don't care
474
- }
599
+ // Download or read from cache
600
+ node.sFileToBePlayed = getFilename(msg, params);
601
+ node.sFileToBePlayed = path.join(node.userDir, "ttsfiles", node.sFileToBePlayed);
602
+ if (!fs.existsSync(node.sFileToBePlayed)) {
603
+ node.setNodeStatus({ fill: 'blue', shape: 'ring', text: 'Download using' + node.server.ttsservice });
604
+ data = await synthesizeSpeechPolly([node.server.polly, params]);
605
+ } else {
606
+ node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Reading offline from cache' });
475
607
  }
476
- } else {
477
- if (_oTrack.trackType === "musicqueue") { // This indicates that is an audio file or stream station
478
- try {
479
- await SELECTQUEUESync(_oPlayer);
480
- } catch (error) {
481
- return error;
482
- }
483
- try {
484
- await delay(1000);
485
- await SELECTTRACKSync(_oTrack.queuePosition, _oPlayer);
486
- } catch (error) {
487
- return error;
488
- }
489
- try {
490
- await delay(1000);
491
- await SEEKSync(_oTrack.position, _oPlayer);
492
- } catch (error) {
493
- // Don't care
494
- }
495
- if (_oTrack.state === "playing") {
496
- // 24/08/2021 Play if it was playing
497
- try {
498
- await PLAYSync(_oPlayer);
499
- } catch (error) {
500
- return error;
501
- }
502
- } else {
503
- /// 03/09/2021
504
- try {
505
- await STOPSync(_oPlayer);
506
- } catch (error) {
507
- return error;
508
- }
509
- }
510
- } else if (_oTrack.trackType === "lineinput") {
511
- // Line in, TV in, etc...
512
- if (_oTrack.state === "playing") {
513
- try {
514
- await setAVTransportURISync(_oTrack.uri, _oPlayer);
515
- } catch (error) {
516
- return error;
517
- }
518
- }
608
+ } else if (node.server.ttsservice === "googletts") {
609
+ // VoiceId is: name + "#" + languageCode + "#" + ssmlGender
610
+ // speakingRate tra 0.25 e 4.0
611
+ // pitch tra -20.0 e 20.0
612
+ const params = {
613
+ voice: { name: node.voiceId.split("#")[0], languageCode: node.voiceId.split("#")[1], ssmlGender: node.voiceId.split("#")[2] },
614
+ audioConfig: { audioEncoding: "MP3", speakingRate: parseFloat(node.speakingrate), pitch: parseFloat(node.speakingpitch), },
615
+ };
616
+ params.input = node.ssml === false ? { text: msg } : { ssml: msg };
617
+
618
+ // Download or read from cache
619
+ node.sFileToBePlayed = getFilename(msg, params);
620
+ node.sFileToBePlayed = path.join(node.userDir, "ttsfiles", node.sFileToBePlayed);
621
+ if (!fs.existsSync(node.sFileToBePlayed)) {
622
+ node.setNodeStatus({ fill: 'blue', shape: 'ring', text: 'Download using' + node.server.ttsservice });
623
+ data = await synthesizeSpeechGoogleTTS([node.server.googleTTS, params]);
624
+ } else {
625
+ node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Reading offline from cache' });
519
626
  }
520
- }
521
- let t = setTimeout(() => { return true; }, 5000); // Wait some seconds
522
- };
523
-
524
- // Handle the queue
525
- async function HandleQueue() {
526
- node.bBusyPlayingQueue = true;
527
- node.server.whoIsUsingTheServer = node.id; // Signal to other ttsultimate node, that i'm using the Sonos device
528
- try {
529
-
530
- if (node.playertype !== "noplayer") {
531
- // Get the current music queue, if one
532
- var oCurTrack = null;
533
- try {
534
- oCurTrack = await getMusicQueue();
535
- // 19/04/2022 The current track of additional players is read in the groupSpeakerySync function
536
- } catch (error) {
537
- oCurTrack = null;
538
- }
539
-
540
- // 05/12/2020 Set "completed" to false and send it
541
- node.msg.completed = false;
542
- try {
543
- await groupSpeakersSync(); // 20/03/2020 Group Speakers toghether and reads each current track
544
- } catch (error) {
545
- // Don't care.
546
- node.setNodeStatus({ fill: "red", shape: "ring", text: "Error grouping speakers: " + error.message });
547
- RED.log.error("ttsultimate: Error grouping speakers: " + error.message);
548
- }
549
-
550
- node.send([{ passThroughMessage: node.passThroughMessage, payload: node.msg.completed }, null]);
627
+ } else if (node.server.ttsservice === "googletranslate") {
628
+ // VoiceId is: code. SSML is not supported by google translate
629
+ if (node.voiceId === "cmn-Hant-TW") node.voiceId = "zh-CN"; // 06/08/2022 fix for a wrong voiceid sent by google translate as voice code
630
+ const params = {
631
+ text: msg,
632
+ voice: node.voiceId,
633
+ slow: false // optional
634
+ };
551
635
 
552
- // 24/08/2021 If something was playing, stop the player https://github.com/Supergiovane/node-red-contrib-tts-ultimate/issues/32
553
- try {
554
- //await node.SonosClient.stop(); //.then(result => {
555
- await STOPSync();
556
- } catch (error) {
557
- //RED.log.error("ttsultimate: Error stopping in HandleSend: " + error.message);
558
- }
636
+ // Download or read from cache
637
+ node.sFileToBePlayed = getFilename(msg, params);
638
+ node.sFileToBePlayed = path.join(node.userDir, "ttsfiles", node.sFileToBePlayed);
639
+ if (!fs.existsSync(node.sFileToBePlayed)) {
640
+ node.setNodeStatus({ fill: 'blue', shape: 'ring', text: 'Download using' + node.server.ttsservice });
641
+ data = await synthesizeSpeechGoogleTranslate(node.server.googleTranslateTTS, params);
559
642
  } else {
560
- node.msg.completed = false;
561
- node.send([{ passThroughMessage: node.passThroughMessage, payload: node.msg.completed }, null]);
643
+ node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Reading offline from cache' });
562
644
  }
645
+ } else if (node.server.ttsservice === "microsoftazuretts") {
646
+ // VoiceId is: code
647
+ const params = {
648
+ text: msg,
649
+ voice: node.voiceId
650
+ };
563
651
 
564
- while (node.tempMSGStorage.length > 0) {
565
- node.currentMSGbeingSpoken = node.tempMSGStorage.shift()//node.tempMSGStorage[0];// Advise the whole node of the currently spoken MSG
566
- const msg = node.currentMSGbeingSpoken.payload.toString(); // Get the text to be spoken
567
- //node.tempMSGStorage.splice(0, 1); // Remove the first item in the array
568
- node.sFileToBePlayed = "";
569
- node.setNodeStatus({ fill: "gray", shape: "ring", text: "Read " + msg });
570
-
571
- // 04/12/2020 check what really is the file to be played
572
- if (msg.toLowerCase().startsWith("http://") || msg.toLowerCase().startsWith("https://")) {
573
- RED.log.info('ttsultimate: HTTP filename: ' + msg);
574
- node.sFileToBePlayed = msg;
575
- } else if (msg.indexOf("OwnFile_") !== -1) {
576
- RED.log.info('ttsultimate: OwnFile .MP3, skip tts, filename: ' + msg);
577
- node.sFileToBePlayed = path.join(node.userDir, "ttspermanentfiles", msg);
578
- } else if (msg.indexOf("Hailing_") !== -1) {
579
- RED.log.info('ttsultimate: Hailing .MP3, skip tts, filename: ' + msg);
580
- node.sFileToBePlayed = path.join(node.userDir, "hailingpermanentfiles", msg);
581
- } else {
582
- try {
583
- // No file in cache. Download from tts service
584
- var data = undefined;
585
- if (node.server.ttsservice === "polly") {
586
- var params = {
587
- OutputFormat: "mp3",
588
- SampleRate: '22050',
589
- Text: msg,
590
- TextType: node.ssml ? 'ssml' : 'text'
591
- };
592
- // 02/03/2022 check wether standard or neural engine is POLLY is selected
593
- if (node.voiceId.includes("#engineType:")) {
594
- params.VoiceId = node.voiceId.split("#engineType:")[0];
595
- params.Engine = node.voiceId.split("#engineType:")[1];
596
- } else {
597
- params.VoiceId = node.voiceId;
598
- }
599
- // Download or read from cache
600
- node.sFileToBePlayed = getFilename(msg, params);
601
- node.sFileToBePlayed = path.join(node.userDir, "ttsfiles", node.sFileToBePlayed);
602
- if (!fs.existsSync(node.sFileToBePlayed)) {
603
- node.setNodeStatus({ fill: 'blue', shape: 'ring', text: 'Download using' + node.server.ttsservice });
604
- data = await synthesizeSpeechPolly([node.server.polly, params]);
605
- } else {
606
- node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Reading offline from cache' });
607
- }
608
- } else if (node.server.ttsservice === "googletts") {
609
- // VoiceId is: name + "#" + languageCode + "#" + ssmlGender
610
- // speakingRate tra 0.25 e 4.0
611
- // pitch tra -20.0 e 20.0
612
- const params = {
613
- voice: { name: node.voiceId.split("#")[0], languageCode: node.voiceId.split("#")[1], ssmlGender: node.voiceId.split("#")[2] },
614
- audioConfig: { audioEncoding: "MP3", speakingRate: parseFloat(node.speakingrate), pitch: parseFloat(node.speakingpitch), },
615
- };
616
- params.input = node.ssml === false ? { text: msg } : { ssml: msg };
617
-
618
- // Download or read from cache
619
- node.sFileToBePlayed = getFilename(msg, params);
620
- node.sFileToBePlayed = path.join(node.userDir, "ttsfiles", node.sFileToBePlayed);
621
- if (!fs.existsSync(node.sFileToBePlayed)) {
622
- node.setNodeStatus({ fill: 'blue', shape: 'ring', text: 'Download using' + node.server.ttsservice });
623
- data = await synthesizeSpeechGoogleTTS([node.server.googleTTS, params]);
624
- } else {
625
- node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Reading offline from cache' });
626
- }
627
- } else if (node.server.ttsservice === "googletranslate") {
628
- // VoiceId is: code. SSML is not supported by google translate
629
- if (node.voiceId === "cmn-Hant-TW") node.voiceId = "zh-CN"; // 06/08/2022 fix for a wrong voiceid sent by google translate as voice code
630
- const params = {
631
- text: msg,
632
- voice: node.voiceId,
633
- slow: false // optional
634
- };
635
-
636
- // Download or read from cache
637
- node.sFileToBePlayed = getFilename(msg, params);
638
- node.sFileToBePlayed = path.join(node.userDir, "ttsfiles", node.sFileToBePlayed);
639
- if (!fs.existsSync(node.sFileToBePlayed)) {
640
- node.setNodeStatus({ fill: 'blue', shape: 'ring', text: 'Download using' + node.server.ttsservice });
641
- data = await synthesizeSpeechGoogleTranslate(node.server.googleTranslateTTS, params);
642
- } else {
643
- node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Reading offline from cache' });
644
- }
645
- } else if (node.server.ttsservice === "microsoftazuretts") {
646
- // VoiceId is: code
647
- const params = {
648
- text: msg,
649
- voice: node.voiceId
650
- };
651
-
652
- // Download or read from cache
653
- node.sFileToBePlayed = getFilename(msg, params);
654
- node.sFileToBePlayed = path.join(node.userDir, "ttsfiles", node.sFileToBePlayed);
655
- if (!fs.existsSync(node.sFileToBePlayed)) {
656
- node.setNodeStatus({ fill: 'blue', shape: 'ring', text: 'Download using' + node.server.ttsservice });
657
- data = await synthesizeSpeechMicrosoftAzureTTS(node.server.microsoftAzureTTS, params);
658
- } else {
659
- node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Reading offline from cache' });
660
- }
661
- } else if (node.server.ttsservice === "elevenlabs") {
662
- // VoiceId is: code
663
- const params = {
664
- text: msg,
665
- voice: node.voiceId,
666
- model_id: "eleven_monolingual_v1",
667
- voice_settings: {
668
- stability: config.elevenlabsStability,
669
- similarity_boost: config.elevenlabsSimilarity_boost
670
- }
671
- };
672
- // Download or read from cache
673
- node.sFileToBePlayed = getFilename(msg, params);
674
- node.sFileToBePlayed = path.join(node.userDir, "ttsfiles", node.sFileToBePlayed);
675
- if (!fs.existsSync(node.sFileToBePlayed)) {
676
- node.setNodeStatus({ fill: 'blue', shape: 'ring', text: 'Download using' + node.server.ttsservice });
677
- data = await synthesizeSpeechElevenLabs(node.server.elevenlabsTTS, params);
678
- } else {
679
- node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Reading offline from cache' });
680
- }
681
- }
682
-
683
- // Save the downloaded file into the cache
684
- if (data !== undefined) {
685
- try {
686
- console.log("Salvelox " + node.sFileToBePlayed)
687
- fs.writeFileSync(node.sFileToBePlayed, data);
688
- } catch (error) {
689
- RED.log.error("ttsultimate: node id: " + node.id + " Unable to save the file " + error.message);
690
- node.setNodeStatus({ fill: "red", shape: "ring", text: "Unable to save the file " + node.sFileToBePlayed + " " + error.message });
691
- throw (error);
692
- }
693
- }
694
-
695
- } catch (error) {
696
- RED.log.error("ttsultimate: node id: " + node.id + " Error Downloading TTS: " + error.message + ". THE TTS SERVICE MAY BE DOWN.");
697
- node.setNodeStatus({ fill: 'red', shape: 'ring', text: 'Error Downloading TTS:' + error.message });
698
- node.sFileToBePlayed = "";
699
- }
700
- }
701
-
702
- // Ready to play
703
- if (node.sFileToBePlayed !== "") {
704
-
705
- //#region Now i am ready to play the file
706
- if (node.playertype === "sonos") {
707
-
708
- // Play with Sonos
709
- node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Play ' + msg });
710
-
711
- // Play directly files starting with http://
712
- if (!node.sFileToBePlayed.toLowerCase().startsWith("http://") && !node.sFileToBePlayed.toLowerCase().startsWith("https://")) {
713
- node.sFileToBePlayed = node.sNoderedURL + "/tts/tts.mp3?f=" + encodeURIComponent(node.sFileToBePlayed);
714
- }
715
-
716
- // Set Volume
717
- try {
718
- let volTemp = 0;
719
- if (node.currentMSGbeingSpoken.hasOwnProperty("volume")) {
720
- volTemp = Number(node.currentMSGbeingSpoken.volume);
721
- } else {
722
- volTemp = Number(node.sSonosVolume);
723
- }
724
- await SETVOLUMESync(volTemp);
725
- if (node.unmuteIfMuted) await SETMutedSync(false); // 21/10/2021 Unmute
726
-
727
- if (node.oAdditionalSonosPlayers.length > 0) {
728
- // 05/07/2021 set the volume of additional coordinators
729
- for (let index = 0; index < node.oAdditionalSonosPlayers.length; index++) {
730
- let element = node.oAdditionalSonosPlayers[index].oPlayer;
731
- //node.oAdditionalSonosPlayers.push({ oPlayer: new sonos.Sonos(element.host), hostVolumeAdjust: element.hostVolumeAdjust });
732
- try {
733
- // 12/04/20222 Set the adjusted volume, based on the main player volume + the adjusted volume in %
734
- let iAdjustedVol = Number(volTemp) + Number((node.oAdditionalSonosPlayers[index].hostVolumeAdjust || 0));
735
- if (iAdjustedVol < 0) iAdjustedVol = 0;
736
- if (iAdjustedVol > 100) iAdjustedVol = 100;
737
- await element.setVolume(iAdjustedVol);
738
- if (node.unmuteIfMuted) await element.setMuted(false); // 21/10/2021 Unmute
739
- } catch (error) {
740
- RED.log.error("ttsultimate: Handlequeue: Unable to set the volume on additional player " + error.message);
741
- }
742
- };
743
- };
744
-
745
- } catch (error) {
746
- RED.log.error("ttsultimate: Unable to set the volume for " + node.sFileToBePlayed);
747
- }
748
- try {
749
-
750
- await setAVTransportURISync(node.sFileToBePlayed);
751
-
752
- // Wait for start playing
753
- var state = "";
754
- if (node.timerbTimeOutPlay !== null) clearTimeout(node.timerbTimeOutPlay);
755
- node.bTimeOutPlay = false;
756
- node.timerbTimeOutPlay = setTimeout(() => {
757
- node.bTimeOutPlay = true;
758
- }, 10000);
759
- while (state !== "playing" && !node.bTimeOutPlay) {
760
- try {
761
- //state = await node.SonosClient.getCurrentState();
762
- state = await getCurrentStateSync();
763
- } catch (error) {
764
- node.setNodeStatus({ fill: 'yellow', shape: 'ring', text: 'Error getCurrentState of playing ' + msg });
765
- RED.log.error("ttsultimate: Error getCurrentState of playing " + error.message);
766
- throw new MessageEvent("Error getCurrentState of playing " + error.message);
767
- }
768
- }
769
- if (node.timerbTimeOutPlay !== null) clearTimeout(node.timerbTimeOutPlay);
770
- switch (node.bTimeOutPlay) {
771
- case false:
772
- node.setNodeStatus({ fill: 'green', shape: 'dot', text: 'Playing ' + msg });
773
- break;
774
- default:
775
- node.setNodeStatus({ fill: 'grey', shape: 'dot', text: 'Timeout waiting start play state: ' + msg });
776
- break;
777
- }
778
-
779
- // Wait for end
780
- if (node.timerbTimeOutPlay !== null) clearTimeout(node.timerbTimeOutPlay);
781
- node.bTimeOutPlay = false;
782
- state = "";
783
- node.timerbTimeOutPlay = setTimeout(() => {
784
- node.bTimeOutPlay = true;
785
- }, 60000 * 10); // 10 minutes timeout
786
- while (state !== "stopped" && !node.bTimeOutPlay) {
787
- try {
788
- state = await getCurrentStateSync();
789
- } catch (error) {
790
- node.setNodeStatus({ fill: 'yellow', shape: 'ring', text: 'Error getCurrentState of stopped ' + msg });
791
- RED.log.error("ttsultimate: Error getCurrentState of stopped " + error.message);
792
- throw new MessageEvent("Error getCurrentState of stopped " + error.message);
793
- }
794
- }
795
- if (node.timerbTimeOutPlay !== null) clearTimeout(node.timerbTimeOutPlay);
796
- switch (node.bTimeOutPlay) {
797
- case false:
798
- node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'End playing ' + msg });
799
- break;
800
- default:
801
- node.setNodeStatus({ fill: 'grey', shape: 'dot', text: 'Timeout waiting end play state: ' + msg });
802
- break;
803
- }
804
-
805
- } catch (error) {
806
- if (node.timerbTimeOutPlay !== null) clearTimeout(node.timerbTimeOutPlay); // Clear the player timeout
807
- RED.log.error("ttsultimate: Error HandleQueue for " + node.sFileToBePlayed + " " + error.message);
808
- node.setNodeStatus({ fill: 'red', shape: 'dot', text: 'Error ' + msg + " " + error.message });
809
- }
810
-
811
- } else if (node.playertype === "noplayer") {
812
- // Output only the filename
813
- if (noPlayerFileArray === undefined || noPlayerFileArray === null) var noPlayerFileArray = [];
814
- noPlayerFileArray.push({ file: node.sFileToBePlayed });
815
- }
816
- }
817
- //#endregion
818
-
819
-
820
- }; // End Loop
821
-
822
- // End task
823
- if (node.playertype === "sonos") {
824
- // Ends the tasks of Sonos
825
-
826
- // Ungroup speaker
827
- try {
828
- await ungroupSpeakersSync(); // Ungroup speakers
829
- } catch (error) {
830
- // Don't care.
831
- node.setNodeStatus({ fill: "red", shape: "ring", text: "Error ungrouping speakers: " + error.message });
832
- }
833
-
834
- await delay(2000);
835
-
836
- // Resume music
837
- try {
838
- if (oCurTrack !== null && (!oCurTrack.hasOwnProperty("title") || oCurTrack.title.indexOf(".mp3") === -1)) {
839
- node.setNodeStatus({ fill: 'grey', shape: 'ring', text: "Resuming original queue..." });
840
- await resumeMusicQueue(oCurTrack);
841
- node.setNodeStatus({ fill: 'green', shape: 'ring', text: "Done resuming queue." });
842
- } else {
843
- // 28/08/2021 There was no queue playing. Delete the TTS from the queue
844
- node.setNodeStatus({ fill: 'green', shape: 'ring', text: "No queue to resume." });
845
- }
846
- } catch (error) {
847
- node.setNodeStatus({ fill: 'red', shape: 'ring', text: "Error resuming queue: " + error.message });
848
- }
849
-
850
-
851
- // 19/04/2022 Resume music queue of additional players
852
- for (let index = 0; index < node.oAdditionalSonosPlayers.length; index++) {
853
- let addPlayer = node.oAdditionalSonosPlayers[index].oPlayer;
854
- let trackAddPlayer = addPlayer.additionalPlayerCurrentTrack;
855
- if (trackAddPlayer !== null) {
856
- try {
857
- await resumeMusicQueue(trackAddPlayer, addPlayer);
858
- node.setNodeStatus({ fill: 'green', shape: 'ring', text: "Done resuming queue additional player " + addPlayer.host || "" });
859
- } catch (error) {
860
- // Dont care
861
- RED.log.warn("ttsultimate: Error resuming music queue of additional player " + error.message + " " + addPlayer.host || "");
862
- }
863
- } else {
864
- node.setNodeStatus({ fill: 'green', shape: 'ring', text: "No queue to resume for " + addPlayer.host || "" });
865
- }
866
- }
867
-
868
- // Signal end playing
869
- let t = setTimeout(() => {
870
- node.msg.completed = true;
871
- node.currentMSGbeingSpoken = {};
872
- node.send([{ passThroughMessage: node.passThroughMessage, payload: node.msg.completed }, null]);
873
- node.bBusyPlayingQueue = false
874
- node.server.whoIsUsingTheServer = ""; // Signal to other ttsultimate node, that i'm not using the Sonos device anymore
875
- }, 500)
876
-
877
- } else if (node.playertype === "noplayer") {
878
- // End task if no player is selected.
879
- // Output the array of files
880
- // Signal end playing
881
- //let t = setTimeout(() => {
882
- node.msg.completed = true;
883
- node.currentMSGbeingSpoken = {};
884
- node.send([{ passThroughMessage: node.passThroughMessage, payload: node.msg.completed, filesArray: noPlayerFileArray }, null]);
885
- node.bBusyPlayingQueue = false
886
- node.server.whoIsUsingTheServer = ""; // Signal to other ttsultimate node, that i'm not using the Sonos device anymore
887
- //}, 1000)
652
+ // Download or read from cache
653
+ node.sFileToBePlayed = getFilename(msg, params);
654
+ node.sFileToBePlayed = path.join(node.userDir, "ttsfiles", node.sFileToBePlayed);
655
+ if (!fs.existsSync(node.sFileToBePlayed)) {
656
+ node.setNodeStatus({ fill: 'blue', shape: 'ring', text: 'Download using' + node.server.ttsservice });
657
+ data = await synthesizeSpeechMicrosoftAzureTTS(node.server.microsoftAzureTTS, params);
658
+ } else {
659
+ node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Reading offline from cache' });
888
660
  }
889
-
890
- } catch (error) {
891
- // Should'nt be there
892
- RED.log.error("ttsultimate: BIG Error HandleQueue MAIN " + error.message);
893
- node.setNodeStatus({ fill: "grey", shape: "ring", text: "Error Handlequeue " + error.message });
894
- node.flushQueue();
895
- }
896
-
897
- }
898
-
899
- node.on('input', function (msg) {
900
- // if (msg.hasOwnProperty("banana")) {
901
- // node.SonosClient.setMuted(msg.banana);
902
- // return;
903
- // }
904
-
905
- // 05/01/2022 Set the passtrough message o come cazzo si scrive
906
- node.passThroughMessage = RED.util.cloneMessage(msg);
907
-
908
- // 09/01/2021 Set the main player and groups IP on request
909
- // *********************************
910
- if (msg.hasOwnProperty("setConfig")) {
911
- if (msg.setConfig.hasOwnProperty("setMainPlayerIP")) {
912
- node.sSonosIPAddress = msg.setConfig.setMainPlayerIP;
913
- RED.log.info("ttsultimate: new main player set by msg: " + node.sSonosIPAddress);
914
- node.setNodeStatus({ fill: 'grey', shape: 'ring', text: "Main Player changed to " + node.sSonosIPAddress });
915
- // RE-Create sonos client & groups
916
- node.SonosClient = new sonos.Sonos(node.sSonosIPAddress);
917
- node.SonosClient.getName().then(info => {
918
- node.sonosCoordinatorGroupName = info;
919
- RED.log.info("ttsultimate: new zone coordinator set by msg: " + JSON.stringify(info));
920
- }).catch(err => {
921
- });
661
+ } else if (node.server.ttsservice === "elevenlabs") {
662
+ // VoiceId is: code
663
+ const params = {
664
+ text: msg,
665
+ voice: node.voiceId,
666
+ model_id: "eleven_monolingual_v1",
667
+ voice_settings: {
668
+ stability: config.elevenlabsStability,
669
+ similarity_boost: config.elevenlabsSimilarity_boost
670
+ }
922
671
  };
923
- if (msg.setConfig.hasOwnProperty("setPlayerGroupArray")) {
924
- node.setNodeStatus({ fill: 'grey', shape: 'ring', text: "Group players changed" });
925
- // Fill the node.oAdditionalSonosPlayers with all sonos IPs in the setPlayerGroupArray
926
- node.oAdditionalSonosPlayers = [];
927
- for (let index = 0; index < msg.setConfig.setPlayerGroupArray.length; index++) {
928
- const sRow = msg.setConfig.setPlayerGroupArray[index];
929
- let host = "";
930
- let hostVolumeAdjust = 0;
931
- if (sRow.includes("#")) {
932
- host = sRow.split("#")[0];
933
- hostVolumeAdjust = sRow.split("#")[1];
934
- } else {
935
- host = sRow;
936
- }
937
- //node.oAdditionalSonosPlayers.push({ oPlayer: new sonos.Sonos(element.host), hostVolumeAdjust: element.hostVolumeAdjust });
938
- node.oAdditionalSonosPlayers.push({ oPlayer: new sonos.Sonos(host), hostVolumeAdjust: Number(hostVolumeAdjust) });
939
- RED.log.info("ttsultimate: new group player set by msg: " + host + " adjusted volume: " + Number(hostVolumeAdjust));
940
- }
941
- };
942
- };
943
- // *********************************
944
-
945
- // In case of connection error, doesn't accept any message
946
- if (node.msg.connectionerror && node.playertype !== "noplayer") {
947
- RED.log.warn("ttsultimate: Sonos is offline. The new msg coming from the flow will be rejected.");
948
- node.setNodeStatus({ fill: 'red', shape: 'ring', text: "Sonos is offline. The msg has been rejected." });
949
- return;
950
- }
672
+ // Download or read from cache
673
+ node.sFileToBePlayed = getFilename(msg, params);
674
+ node.sFileToBePlayed = path.join(node.userDir, "ttsfiles", node.sFileToBePlayed);
675
+ if (!fs.existsSync(node.sFileToBePlayed)) {
676
+ node.setNodeStatus({ fill: 'blue', shape: 'ring', text: 'Download using' + node.server.ttsservice });
677
+ data = await synthesizeSpeechElevenLabs(node.server.elevenlabsTTS, params);
678
+ } else {
679
+ node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Reading offline from cache' });
680
+ }
681
+ }
951
682
 
952
- // 27/01/2021 Stop whatever in play.
953
- if (msg.hasOwnProperty("stop") && msg.stop === true) {
954
- node.flushQueue();
683
+ // Save the downloaded file into the cache
684
+ if (data !== undefined) {
955
685
  try {
956
- STOPSync();
686
+ console.log("Salvelox " + node.sFileToBePlayed)
687
+ fs.writeFileSync(node.sFileToBePlayed, data);
957
688
  } catch (error) {
689
+ RED.log.error("ttsultimate: node id: " + node.id + " Unable to save the file " + error.message);
690
+ node.setNodeStatus({ fill: "red", shape: "ring", text: "Unable to save the file " + node.sFileToBePlayed + " " + error.message });
691
+ throw (error);
958
692
  }
959
- node.setNodeStatus({ fill: 'red', shape: 'ring', text: "Forced stop." });
960
- return;
961
- }
693
+ }
962
694
 
963
- if (!msg.hasOwnProperty("payload")) {
964
- notifyError(msg, 'msg.payload must be of type String');
965
- return;
695
+ } catch (error) {
696
+ RED.log.error("ttsultimate: node id: " + node.id + " Error Downloading TTS: " + error.message + ". THE TTS SERVICE MAY BE DOWN.");
697
+ node.setNodeStatus({ fill: 'red', shape: 'ring', text: 'Error Downloading TTS:' + error.message });
698
+ node.sFileToBePlayed = "";
966
699
  }
700
+ }
967
701
 
702
+ // Ready to play
703
+ if (node.sFileToBePlayed !== "") {
968
704
 
969
- // 21/10/2021 force unmute
970
- if (msg.hasOwnProperty("unmute")) {
971
- node.unmuteIfMuted = msg.unmute;
972
- }
705
+ //#region Now i am ready to play the file
706
+ if (node.playertype === "sonos") {
973
707
 
974
- // 05/12/2020 handling Hailing
975
- var hailingMSG = null;
976
- if (msg.hasOwnProperty("nohailing") && (msg.nohailing == "1" || msg.nohailing.toLowerCase() == "true")) {
977
- hailingMSG = null;
978
- } else {
708
+ // Play with Sonos
709
+ node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Play ' + msg });
979
710
 
980
- // Backward compatibiliyy, to remove with the next Version
981
- // ################
982
- if (config.sonoshailing === "0") {
983
- // Remove the hailing.mp3 default file
984
- RED.log.info('ttsultimate: Hailing disabled');
985
- } else if (config.sonoshailing == "1") {
986
- RED.log.warn("ttsultimate you've an old hailing setting. PLEASE SET AGAIN THE HAILING IN THE CONFIG NODE");
987
- config.sonoshailing = "Hailing_Hailing.mp3";
988
- } else if (config.sonoshailing == "2") {
989
- RED.log.warn("ttsultimate you've an old hailing setting. PLEASE SET AGAIN THE HAILING IN THE CONFIG NODE");
990
- config.sonoshailing = "Hailing_ComputerCall.mp3";
991
- } else if (config.sonoshailing == "3") {
992
- RED.log.warn("ttsultimate you've an old hailing setting. PLEASE SET AGAIN THE HAILING IN THE CONFIG NODE");
993
- config.sonoshailing = "Hailing_VintageSpace.mp3";
994
- }
995
- // ################
996
- if (config.sonoshailing !== "0") {
997
- hailingMSG = { payload: config.sonoshailing };
711
+ // Play directly files starting with http://
712
+ if (!node.sFileToBePlayed.toLowerCase().startsWith("http://") && !node.sFileToBePlayed.toLowerCase().startsWith("https://")) {
713
+ node.sFileToBePlayed = node.sNoderedURL + "/tts/tts.mp3?f=" + encodeURIComponent(node.sFileToBePlayed);
714
+ }
715
+
716
+ // Set Volume
717
+ try {
718
+ let volTemp = 0;
719
+ if (node.currentMSGbeingSpoken.hasOwnProperty("volume")) {
720
+ volTemp = Number(node.currentMSGbeingSpoken.volume);
998
721
  } else {
999
- hailingMSG = null;
722
+ volTemp = Number(node.sSonosVolume);
1000
723
  }
1001
- if (msg.hasOwnProperty("sonoshailing")) hailingMSG = { payload: "Hailing_" + msg.sonoshailing + ".mp3" };
1002
- }
1003
-
1004
- // 27/01/2021 Handling priority messages
1005
- // ########################
1006
- if (msg.hasOwnProperty("priority") && msg.priority === true) {
1007
- // 10/04/2018 Take only the TTS message from the queue, that are not prioritized and removes others.
1008
- let arrayTemp = [];
1009
- for (let index = 0; index < node.tempMSGStorage.length; index++) {
1010
- const element = node.tempMSGStorage[index];
1011
- if (element.hasOwnProperty("priority") && element.priority === true) {
1012
- arrayTemp.push(element);
724
+ await SETVOLUMESync(volTemp);
725
+ if (node.unmuteIfMuted) await SETMutedSync(false); // 21/10/2021 Unmute
726
+
727
+ if (node.oAdditionalSonosPlayers.length > 0) {
728
+ // 05/07/2021 set the volume of additional coordinators
729
+ for (let index = 0; index < node.oAdditionalSonosPlayers.length; index++) {
730
+ let element = node.oAdditionalSonosPlayers[index].oPlayer;
731
+ //node.oAdditionalSonosPlayers.push({ oPlayer: new sonos.Sonos(element.host), hostVolumeAdjust: element.hostVolumeAdjust });
732
+ try {
733
+ // 12/04/20222 Set the adjusted volume, based on the main player volume + the adjusted volume in %
734
+ let iAdjustedVol = Number(volTemp) + Number((node.oAdditionalSonosPlayers[index].hostVolumeAdjust || 0));
735
+ if (iAdjustedVol < 0) iAdjustedVol = 0;
736
+ if (iAdjustedVol > 100) iAdjustedVol = 100;
737
+ await element.setVolume(iAdjustedVol);
738
+ if (node.unmuteIfMuted) await element.setMuted(false); // 21/10/2021 Unmute
739
+ } catch (error) {
740
+ RED.log.error("ttsultimate: Handlequeue: Unable to set the volume on additional player " + error.message);
1013
741
  }
1014
- }
1015
- node.tempMSGStorage = arrayTemp;
742
+ };
743
+ };
1016
744
 
1017
- // If the queue is empty and if i can play the Haniling, add the hailing file first
1018
- if (node.tempMSGStorage.length == 0 && hailingMSG !== null) {
1019
- node.tempMSGStorage.push(hailingMSG);
1020
- node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Queued Hail in priority mode' });
745
+ } catch (error) {
746
+ RED.log.error("ttsultimate: Unable to set the volume for " + node.sFileToBePlayed);
747
+ }
748
+ try {
749
+
750
+ await setAVTransportURISync(node.sFileToBePlayed);
751
+
752
+ // Wait for start playing
753
+ var state = "";
754
+ if (node.timerbTimeOutPlay !== null) clearTimeout(node.timerbTimeOutPlay);
755
+ node.bTimeOutPlay = false;
756
+ node.timerbTimeOutPlay = setTimeout(() => {
757
+ node.bTimeOutPlay = true;
758
+ }, 10000);
759
+ while (state !== "playing" && !node.bTimeOutPlay) {
760
+ try {
761
+ //state = await node.SonosClient.getCurrentState();
762
+ state = await getCurrentStateSync();
763
+ } catch (error) {
764
+ node.setNodeStatus({ fill: 'yellow', shape: 'ring', text: 'Error getCurrentState of playing ' + msg });
765
+ RED.log.error("ttsultimate: Error getCurrentState of playing " + error.message);
766
+ throw new MessageEvent("Error getCurrentState of playing " + error.message);
767
+ }
1021
768
  }
1022
-
1023
- // Priority checks
1024
- if (node.currentMSGbeingSpoken.hasOwnProperty("priority") && node.currentMSGbeingSpoken.priority === true) {
1025
- // There is already a priority message being spoken, do nothing
1026
- node.setNodeStatus({ fill: 'grey', shape: 'ring', text: 'There is already a priority message being spoken...queuing' });
1027
- } else {
1028
- if (node.playertype !== 'noplayer') {
1029
- node.SonosClient.stop().then(result => {
1030
- node.bTimeOutPlay = true;
1031
- node.currentMSGbeingSpoken = msg; // Set immediately, otherwise if comes new flow messages, currentMSGbeingSpoken is too old.
1032
- }).catch(err => {
1033
- // Don't care
1034
- node.bTimeOutPlay = true;
1035
- node.currentMSGbeingSpoken = msg;// Set immediately, otherwise if comes new flow messages, currentMSGbeingSpoken is too old.
1036
- })
1037
- } else {
1038
- node.currentMSGbeingSpoken = msg; // Set immediately, otherwise if comes new flow messages, currentMSGbeingSpoken is too old.
1039
- }
769
+ if (node.timerbTimeOutPlay !== null) clearTimeout(node.timerbTimeOutPlay);
770
+ switch (node.bTimeOutPlay) {
771
+ case false:
772
+ node.setNodeStatus({ fill: 'green', shape: 'dot', text: 'Playing ' + msg });
773
+ break;
774
+ default:
775
+ node.setNodeStatus({ fill: 'grey', shape: 'dot', text: 'Timeout waiting start play state: ' + msg });
776
+ break;
1040
777
  }
1041
778
 
1042
- } else {
1043
- // If the queue is empty and if i can play the Haniling, add the hailing file first
1044
- if (node.tempMSGStorage.length == 0 && hailingMSG !== null && !node.bBusyPlayingQueue) {
1045
- node.tempMSGStorage.push(hailingMSG);
1046
- node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Queued Hail' });
779
+ // Wait for end
780
+ if (node.timerbTimeOutPlay !== null) clearTimeout(node.timerbTimeOutPlay);
781
+ node.bTimeOutPlay = false;
782
+ state = "";
783
+ node.timerbTimeOutPlay = setTimeout(() => {
784
+ node.bTimeOutPlay = true;
785
+ }, 60000 * 10); // 10 minutes timeout
786
+ while (state !== "stopped" && !node.bTimeOutPlay) {
787
+ try {
788
+ state = await getCurrentStateSync();
789
+ } catch (error) {
790
+ node.setNodeStatus({ fill: 'yellow', shape: 'ring', text: 'Error getCurrentState of stopped ' + msg });
791
+ RED.log.error("ttsultimate: Error getCurrentState of stopped " + error.message);
792
+ throw new MessageEvent("Error getCurrentState of stopped " + error.message);
793
+ }
794
+ }
795
+ if (node.timerbTimeOutPlay !== null) clearTimeout(node.timerbTimeOutPlay);
796
+ switch (node.bTimeOutPlay) {
797
+ case false:
798
+ node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'End playing ' + msg });
799
+ break;
800
+ default:
801
+ node.setNodeStatus({ fill: 'grey', shape: 'dot', text: 'Timeout waiting end play state: ' + msg });
802
+ break;
1047
803
  }
1048
- }
1049
- // ########################
1050
804
 
1051
- // Starts main queue watching
1052
- node.tempMSGStorage.push(msg);
1053
- node.waitForQueue();
805
+ } catch (error) {
806
+ if (node.timerbTimeOutPlay !== null) clearTimeout(node.timerbTimeOutPlay); // Clear the player timeout
807
+ RED.log.error("ttsultimate: Error HandleQueue for " + node.sFileToBePlayed + " " + error.message);
808
+ node.setNodeStatus({ fill: 'red', shape: 'dot', text: 'Error ' + msg + " " + error.message });
809
+ }
1054
810
 
1055
- });
811
+ } else if (node.playertype === "noplayer") {
812
+ // Output only the filename
813
+ if (noPlayerFileArray === undefined || noPlayerFileArray === null) var noPlayerFileArray = [];
814
+ noPlayerFileArray.push({ file: node.sFileToBePlayed });
815
+ }
816
+ }
817
+ //#endregion
1056
818
 
1057
- // This starts a timer watching for queue each second.
1058
- node.waitForQueue = () => {
1059
- // Allow some time to wait for all messages from flow
1060
- if (node.oTimerCacheFlowMSG !== null) clearTimeout(node.oTimerCacheFlowMSG);
1061
- node.oTimerCacheFlowMSG = setTimeout(() => {
1062
- // Checks if someone else is using the server node
1063
- if (node.server.whoIsUsingTheServer === "" || node.server.whoIsUsingTheServer === node.id) {
1064
- if (!node.bBusyPlayingQueue && node.tempMSGStorage.length > 0) {
1065
- HandleQueue();
1066
- } else {
1067
- if (node.tempMSGStorage.length > 0) node.setNodeStatus({ fill: 'grey', shape: 'ring', text: "Busy with " + node.tempMSGStorage.length + " items in queue. Retry..." });
1068
- }
1069
- } else {
1070
- node.setNodeStatus({ fill: 'yellow', shape: 'ring', text: "Sonos is occupied by " + node.server.whoIsUsingTheServer + " Retry..." });
1071
- }
1072
- node.waitForQueue();
1073
- }, node.playertype === "noplayer" ? 100 : 1000);
1074
819
 
1075
- }
820
+ }; // End Loop
821
+
822
+ // End task
823
+ if (node.playertype === "sonos") {
824
+ // Ends the tasks of Sonos
825
+
826
+ // Ungroup speaker
827
+ try {
828
+ await ungroupSpeakersSync(); // Ungroup speakers
829
+ } catch (error) {
830
+ // Don't care.
831
+ node.setNodeStatus({ fill: "red", shape: "ring", text: "Error ungrouping speakers: " + error.message });
832
+ }
833
+
834
+ await delay(2000);
835
+
836
+ // Resume music
837
+ try {
838
+ if (oCurTrack !== null && (!oCurTrack.hasOwnProperty("title") || oCurTrack.title.indexOf(".mp3") === -1)) {
839
+ node.setNodeStatus({ fill: 'grey', shape: 'ring', text: "Resuming original queue..." });
840
+ await resumeMusicQueue(oCurTrack);
841
+ node.setNodeStatus({ fill: 'green', shape: 'ring', text: "Done resuming queue." });
842
+ } else {
843
+ // 28/08/2021 There was no queue playing. Delete the TTS from the queue
844
+ node.setNodeStatus({ fill: 'green', shape: 'ring', text: "No queue to resume." });
845
+ }
846
+ } catch (error) {
847
+ node.setNodeStatus({ fill: 'red', shape: 'ring', text: "Error resuming queue: " + error.message });
848
+ }
849
+
850
+
851
+ // 19/04/2022 Resume music queue of additional players
852
+ for (let index = 0; index < node.oAdditionalSonosPlayers.length; index++) {
853
+ let addPlayer = node.oAdditionalSonosPlayers[index].oPlayer;
854
+ let trackAddPlayer = addPlayer.additionalPlayerCurrentTrack;
855
+ if (trackAddPlayer !== null) {
856
+ try {
857
+ await resumeMusicQueue(trackAddPlayer, addPlayer);
858
+ node.setNodeStatus({ fill: 'green', shape: 'ring', text: "Done resuming queue additional player " + addPlayer.host || "" });
859
+ } catch (error) {
860
+ // Dont care
861
+ RED.log.warn("ttsultimate: Error resuming music queue of additional player " + error.message + " " + addPlayer.host || "");
862
+ }
863
+ } else {
864
+ node.setNodeStatus({ fill: 'green', shape: 'ring', text: "No queue to resume for " + addPlayer.host || "" });
865
+ }
866
+ }
1076
867
 
1077
- node.on('close', function (done) {
1078
- clearTimeout(node.oTimerSonosConnectionCheck);
1079
- if (node.timerbTimeOutPlay !== null) clearTimeout(node.timerbTimeOutPlay);
868
+ // Signal end playing
869
+ let t = setTimeout(() => {
1080
870
  node.msg.completed = true;
871
+ node.currentMSGbeingSpoken = {};
1081
872
  node.send([{ passThroughMessage: node.passThroughMessage, payload: node.msg.completed }, null]);
1082
- node.setNodeStatus({ fill: "green", shape: "ring", text: "Shutdown" });
1083
- node.flushQueue();
1084
- done();
873
+ node.bBusyPlayingQueue = false
874
+ node.server.whoIsUsingTheServer = ""; // Signal to other ttsultimate node, that i'm not using the Sonos device anymore
875
+ }, 500)
1085
876
 
1086
- });
1087
-
1088
- // Amazon AWS Service
1089
- function synthesizeSpeechPolly([ttsService, params]) {
1090
- return new Promise((resolve, reject) => {
1091
- ttsService.synthesizeSpeech(params, function (err, data) {
1092
- if (err !== null) {
1093
- return reject(err);
1094
- }
1095
- resolve(data.AudioStream);
1096
- });
1097
- });
1098
- }
1099
- // 23/12/2020 Google TTS Service
1100
- function synthesizeSpeechGoogleTTS([ttsService, params]) {
1101
- return new Promise((resolve, reject) => {
1102
- ttsService.synthesizeSpeech(params, function (err, data) {
1103
- if (err !== null) {
1104
- return reject(err);
1105
- }
1106
- resolve(data.audioContent);
1107
- });
1108
- });
877
+ } else if (node.playertype === "noplayer") {
878
+ // End task if no player is selected.
879
+ // Output the array of files
880
+ // Signal end playing
881
+ //let t = setTimeout(() => {
882
+ node.msg.completed = true;
883
+ node.currentMSGbeingSpoken = {};
884
+ node.send([{ passThroughMessage: node.passThroughMessage, payload: node.msg.completed, filesArray: noPlayerFileArray }, null]);
885
+ node.bBusyPlayingQueue = false
886
+ node.server.whoIsUsingTheServer = ""; // Signal to other ttsultimate node, that i'm not using the Sonos device anymore
887
+ //}, 1000)
1109
888
  }
1110
- // 26/12/2020 Google TTS Service
1111
- async function synthesizeSpeechGoogleTranslate(ttsService, params) {
1112
- try {
1113
- // 30/01/2021 changed how google handles voices
1114
- // https://github.com/ncpierson/google-translate-tts/issues/5#issuecomment-770209715
1115
- if (params.voice.includes("-")) params.voice = params.voice.split("-")[0];
1116
889
 
1117
- const buffer = await ttsService.synthesize(params);
1118
- return (buffer);
1119
- } catch (error) {
1120
- throw (error);
1121
- }
1122
- };
1123
- // 12/10/2021 Microsoft Azure TTS Service
1124
- async function synthesizeSpeechMicrosoftAzureTTS(ttsService, params) {
890
+ } catch (error) {
891
+ // Should'nt be there
892
+ RED.log.error("ttsultimate: BIG Error HandleQueue MAIN " + error.message);
893
+ node.setNodeStatus({ fill: "grey", shape: "ring", text: "Error Handlequeue " + error.message });
894
+ node.flushQueue();
895
+ }
1125
896
 
1126
- return new Promise(function (resolve, reject) {
1127
- try {
897
+ }
1128
898
 
1129
- // Microsoft fa sempre tutto diverso dagli altri, per cui mi tocca reinstanziare l'oggetto
1130
- ttsService = node.server.setMicrosoftAzureVoice(params.voice);
1131
-
1132
- ttsService.speakTextAsync(
1133
- params.text,
1134
- result => {
1135
- ttsService.close();
1136
- resolve(Buffer.from(result.audioData));
1137
- },
1138
- error => {
1139
- ttsService.close();
1140
- reject(error);
1141
- });
1142
- } catch (error) {
1143
- reject(error);
1144
- }
1145
- });
899
+ node.on('input', function (msg) {
900
+ // if (msg.hasOwnProperty("banana")) {
901
+ // node.SonosClient.setMuted(msg.banana);
902
+ // return;
903
+ // }
904
+
905
+ // 05/01/2022 Set the passtrough message o come cazzo si scrive
906
+ node.passThroughMessage = RED.util.cloneMessage(msg);
907
+
908
+ // 09/01/2021 Set the main player and groups IP on request
909
+ // *********************************
910
+ if (msg.hasOwnProperty("setConfig")) {
911
+ if (msg.setConfig.hasOwnProperty("setMainPlayerIP")) {
912
+ node.sSonosIPAddress = msg.setConfig.setMainPlayerIP;
913
+ RED.log.info("ttsultimate: new main player set by msg: " + node.sSonosIPAddress);
914
+ node.setNodeStatus({ fill: 'grey', shape: 'ring', text: "Main Player changed to " + node.sSonosIPAddress });
915
+ // RE-Create sonos client & groups
916
+ node.SonosClient = new sonos.Sonos(node.sSonosIPAddress);
917
+ node.SonosClient.getName().then(info => {
918
+ node.sonosCoordinatorGroupName = info;
919
+ RED.log.info("ttsultimate: new zone coordinator set by msg: " + JSON.stringify(info));
920
+ }).catch(err => {
921
+ });
1146
922
  };
1147
- // elevenLabs TTS Service
1148
- function synthesizeSpeechElevenLabs(ttsService, params) {
1149
- // const params = {
1150
- // text: msg,
1151
- // voice: node.voiceId
1152
- // };
1153
- function stream2buffer(stream) {
1154
- return new Promise((resolve, reject) => {
1155
- try {
1156
- const _buf = [];
1157
-
1158
- stream.on("data", (chunk) => _buf.push(chunk));
1159
- stream.on("end", () => resolve(Buffer.concat(_buf)));
1160
- stream.on("error", (err) => reject(err));
1161
- } catch (error) {
1162
- }
1163
- });
923
+ if (msg.setConfig.hasOwnProperty("setPlayerGroupArray")) {
924
+ node.setNodeStatus({ fill: 'grey', shape: 'ring', text: "Group players changed" });
925
+ // Fill the node.oAdditionalSonosPlayers with all sonos IPs in the setPlayerGroupArray
926
+ node.oAdditionalSonosPlayers = [];
927
+ for (let index = 0; index < msg.setConfig.setPlayerGroupArray.length; index++) {
928
+ const sRow = msg.setConfig.setPlayerGroupArray[index];
929
+ let host = "";
930
+ let hostVolumeAdjust = 0;
931
+ if (sRow.includes("#")) {
932
+ host = sRow.split("#")[0];
933
+ hostVolumeAdjust = sRow.split("#")[1];
934
+ } else {
935
+ host = sRow;
1164
936
  }
1165
- return new Promise((resolve, reject) => {
1166
- // "model_id": "eleven_multilingual_v1",
1167
- ttsService.textToSpeechStream(node.server.credentials.elevenlabsKey, params.voice, params.text, null, null, "eleven_multilingual_v1").then((res) => {
1168
- try {
1169
- if (res !== undefined) {
1170
- resolve(stream2buffer(res));
1171
- } else {
1172
- reject(new Error("Please check credentials. The stream cannot be read from the elevenlabs TTS server"))
1173
- }
1174
- } catch (error) {
1175
- reject(error)
1176
- }
1177
- });
1178
- });
937
+ //node.oAdditionalSonosPlayers.push({ oPlayer: new sonos.Sonos(element.host), hostVolumeAdjust: element.hostVolumeAdjust });
938
+ node.oAdditionalSonosPlayers.push({ oPlayer: new sonos.Sonos(host), hostVolumeAdjust: Number(hostVolumeAdjust) });
939
+ RED.log.info("ttsultimate: new group player set by msg: " + host + " adjusted volume: " + Number(hostVolumeAdjust));
940
+ }
941
+ };
942
+ };
943
+ // *********************************
944
+
945
+ // In case of connection error, doesn't accept any message
946
+ if (node.msg.connectionerror && node.playertype !== "noplayer") {
947
+ RED.log.warn("ttsultimate: Sonos is offline. The new msg coming from the flow will be rejected.");
948
+ node.setNodeStatus({ fill: 'red', shape: 'ring', text: "Sonos is offline. The msg has been rejected." });
949
+ return;
950
+ }
951
+
952
+ // 27/01/2021 Stop whatever in play.
953
+ if (msg.hasOwnProperty("stop") && msg.stop === true) {
954
+ node.flushQueue();
955
+ try {
956
+ STOPSync();
957
+ } catch (error) {
958
+ }
959
+ node.setNodeStatus({ fill: 'red', shape: 'ring', text: "Forced stop." });
960
+ return;
961
+ }
962
+
963
+ if (!msg.hasOwnProperty("payload")) {
964
+ notifyError(msg, 'msg.payload must be of type String');
965
+ return;
966
+ }
967
+
968
+
969
+ // 21/10/2021 force unmute
970
+ if (msg.hasOwnProperty("unmute")) {
971
+ node.unmuteIfMuted = msg.unmute;
972
+ }
973
+
974
+ // 23/01/2024 allow input to set voice
975
+ if (msg.hasOwnProperty("voiceId")) {
976
+ node.voiceId = msg.voiceId;
977
+ }
978
+
979
+ // 05/12/2020 handling Hailing
980
+ var hailingMSG = null;
981
+ if (msg.hasOwnProperty("nohailing") && (msg.nohailing == "1" || msg.nohailing.toLowerCase() == "true")) {
982
+ hailingMSG = null;
983
+ } else {
984
+
985
+ // Backward compatibiliyy, to remove with the next Version
986
+ // ################
987
+ if (config.sonoshailing === "0") {
988
+ // Remove the hailing.mp3 default file
989
+ RED.log.info('ttsultimate: Hailing disabled');
990
+ } else if (config.sonoshailing == "1") {
991
+ RED.log.warn("ttsultimate you've an old hailing setting. PLEASE SET AGAIN THE HAILING IN THE CONFIG NODE");
992
+ config.sonoshailing = "Hailing_Hailing.mp3";
993
+ } else if (config.sonoshailing == "2") {
994
+ RED.log.warn("ttsultimate you've an old hailing setting. PLEASE SET AGAIN THE HAILING IN THE CONFIG NODE");
995
+ config.sonoshailing = "Hailing_ComputerCall.mp3";
996
+ } else if (config.sonoshailing == "3") {
997
+ RED.log.warn("ttsultimate you've an old hailing setting. PLEASE SET AGAIN THE HAILING IN THE CONFIG NODE");
998
+ config.sonoshailing = "Hailing_VintageSpace.mp3";
999
+ }
1000
+ // ################
1001
+ if (config.sonoshailing !== "0") {
1002
+ hailingMSG = { payload: config.sonoshailing };
1003
+ } else {
1004
+ hailingMSG = null;
1005
+ }
1006
+ if (msg.hasOwnProperty("sonoshailing")) hailingMSG = { payload: "Hailing_" + msg.sonoshailing + ".mp3" };
1007
+ }
1008
+
1009
+ // 27/01/2021 Handling priority messages
1010
+ // ########################
1011
+ if (msg.hasOwnProperty("priority") && msg.priority === true) {
1012
+ // 10/04/2018 Take only the TTS message from the queue, that are not prioritized and removes others.
1013
+ let arrayTemp = [];
1014
+ for (let index = 0; index < node.tempMSGStorage.length; index++) {
1015
+ const element = node.tempMSGStorage[index];
1016
+ if (element.hasOwnProperty("priority") && element.priority === true) {
1017
+ arrayTemp.push(element);
1018
+ }
1019
+ }
1020
+ node.tempMSGStorage = arrayTemp;
1021
+
1022
+ // If the queue is empty and if i can play the Haniling, add the hailing file first
1023
+ if (node.tempMSGStorage.length == 0 && hailingMSG !== null) {
1024
+ node.tempMSGStorage.push(hailingMSG);
1025
+ node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Queued Hail in priority mode' });
1179
1026
  }
1180
1027
 
1181
- // 04/01/2021 hashing filename to avoid issues with long filenames.
1182
- function getFilename(_text, _params) {
1183
- let sTextToBeHashed = _text.concat(JSON.stringify(_params));
1184
- const hashSum = crypto.createHash('md5');
1185
- hashSum.update(sTextToBeHashed);
1186
- return hashSum.digest('hex') + ".mp3";
1028
+ // Priority checks
1029
+ if (node.currentMSGbeingSpoken.hasOwnProperty("priority") && node.currentMSGbeingSpoken.priority === true) {
1030
+ // There is already a priority message being spoken, do nothing
1031
+ node.setNodeStatus({ fill: 'grey', shape: 'ring', text: 'There is already a priority message being spoken...queuing' });
1032
+ } else {
1033
+ if (node.playertype !== 'noplayer') {
1034
+ node.SonosClient.stop().then(result => {
1035
+ node.bTimeOutPlay = true;
1036
+ node.currentMSGbeingSpoken = msg; // Set immediately, otherwise if comes new flow messages, currentMSGbeingSpoken is too old.
1037
+ }).catch(err => {
1038
+ // Don't care
1039
+ node.bTimeOutPlay = true;
1040
+ node.currentMSGbeingSpoken = msg;// Set immediately, otherwise if comes new flow messages, currentMSGbeingSpoken is too old.
1041
+ })
1042
+ } else {
1043
+ node.currentMSGbeingSpoken = msg; // Set immediately, otherwise if comes new flow messages, currentMSGbeingSpoken is too old.
1044
+ }
1187
1045
  }
1188
1046
 
1189
- function notifyError(msg, err) {
1190
- var errorMessage = err.message;
1191
- // Output error to console
1192
- node.setNodeStatus({
1193
- fill: 'red',
1194
- shape: 'dot',
1195
- text: 'notifyError: ' + errorMessage
1047
+ } else {
1048
+ // If the queue is empty and if i can play the Haniling, add the hailing file first
1049
+ if (node.tempMSGStorage.length == 0 && hailingMSG !== null && !node.bBusyPlayingQueue) {
1050
+ node.tempMSGStorage.push(hailingMSG);
1051
+ node.setNodeStatus({ fill: 'green', shape: 'ring', text: 'Queued Hail' });
1052
+ }
1053
+ }
1054
+ // ########################
1055
+
1056
+ // Starts main queue watching
1057
+ node.tempMSGStorage.push(msg);
1058
+ node.waitForQueue();
1059
+
1060
+ });
1061
+
1062
+ // This starts a timer watching for queue each second.
1063
+ node.waitForQueue = () => {
1064
+ // Allow some time to wait for all messages from flow
1065
+ if (node.oTimerCacheFlowMSG !== null) clearTimeout(node.oTimerCacheFlowMSG);
1066
+ node.oTimerCacheFlowMSG = setTimeout(() => {
1067
+ // Checks if someone else is using the server node
1068
+ if (node.server.whoIsUsingTheServer === "" || node.server.whoIsUsingTheServer === node.id) {
1069
+ if (!node.bBusyPlayingQueue && node.tempMSGStorage.length > 0) {
1070
+ HandleQueue();
1071
+ } else {
1072
+ if (node.tempMSGStorage.length > 0) node.setNodeStatus({ fill: 'grey', shape: 'ring', text: "Busy with " + node.tempMSGStorage.length + " items in queue. Retry..." });
1073
+ }
1074
+ } else {
1075
+ node.setNodeStatus({ fill: 'yellow', shape: 'ring', text: "Sonos is occupied by " + node.server.whoIsUsingTheServer + " Retry..." });
1076
+ }
1077
+ node.waitForQueue();
1078
+ }, node.playertype === "noplayer" ? 100 : 1000);
1079
+
1080
+ }
1081
+
1082
+ node.on('close', function (done) {
1083
+ clearTimeout(node.oTimerSonosConnectionCheck);
1084
+ if (node.timerbTimeOutPlay !== null) clearTimeout(node.timerbTimeOutPlay);
1085
+ node.msg.completed = true;
1086
+ node.send([{ passThroughMessage: node.passThroughMessage, payload: node.msg.completed }, null]);
1087
+ node.setNodeStatus({ fill: "green", shape: "ring", text: "Shutdown" });
1088
+ node.flushQueue();
1089
+ done();
1090
+
1091
+ });
1092
+
1093
+ // Amazon AWS Service
1094
+ function synthesizeSpeechPolly([ttsService, params]) {
1095
+ return new Promise((resolve, reject) => {
1096
+ ttsService.synthesizeSpeech(params, function (err, data) {
1097
+ if (err !== null) {
1098
+ return reject(err);
1099
+ }
1100
+ resolve(data.AudioStream);
1101
+ });
1102
+ });
1103
+ }
1104
+ // 23/12/2020 Google TTS Service
1105
+ function synthesizeSpeechGoogleTTS([ttsService, params]) {
1106
+ return new Promise((resolve, reject) => {
1107
+ ttsService.synthesizeSpeech(params, function (err, data) {
1108
+ if (err !== null) {
1109
+ return reject(err);
1110
+ }
1111
+ resolve(data.audioContent);
1112
+ });
1113
+ });
1114
+ }
1115
+ // 26/12/2020 Google TTS Service
1116
+ async function synthesizeSpeechGoogleTranslate(ttsService, params) {
1117
+ try {
1118
+ // 30/01/2021 changed how google handles voices
1119
+ // https://github.com/ncpierson/google-translate-tts/issues/5#issuecomment-770209715
1120
+ if (params.voice.includes("-")) params.voice = params.voice.split("-")[0];
1121
+
1122
+ const buffer = await ttsService.synthesize(params);
1123
+ return (buffer);
1124
+ } catch (error) {
1125
+ throw (error);
1126
+ }
1127
+ };
1128
+ // 12/10/2021 Microsoft Azure TTS Service
1129
+ async function synthesizeSpeechMicrosoftAzureTTS(ttsService, params) {
1130
+
1131
+ return new Promise(function (resolve, reject) {
1132
+ try {
1133
+
1134
+ // Microsoft fa sempre tutto diverso dagli altri, per cui mi tocca reinstanziare l'oggetto
1135
+ ttsService = node.server.setMicrosoftAzureVoice(params.voice);
1136
+
1137
+ ttsService.speakTextAsync(
1138
+ params.text,
1139
+ result => {
1140
+ ttsService.close();
1141
+ resolve(Buffer.from(result.audioData));
1142
+ },
1143
+ error => {
1144
+ ttsService.close();
1145
+ reject(error);
1196
1146
  });
1197
- // Set error in message
1198
- msg.error = errorMessage;
1147
+ } catch (error) {
1148
+ reject(error);
1199
1149
  }
1150
+ });
1151
+ };
1152
+ // elevenLabs TTS Service
1153
+ function synthesizeSpeechElevenLabs(ttsService, params) {
1154
+ // const params = {
1155
+ // text: msg,
1156
+ // voice: node.voiceId
1157
+ // };
1158
+ function stream2buffer(stream) {
1159
+ return new Promise((resolve, reject) => {
1160
+ try {
1161
+ const _buf = [];
1162
+
1163
+ stream.on("data", (chunk) => _buf.push(chunk));
1164
+ stream.on("end", () => resolve(Buffer.concat(_buf)));
1165
+ stream.on("error", (err) => reject(err));
1166
+ } catch (error) {
1167
+ }
1168
+ });
1169
+ }
1170
+ return new Promise((resolve, reject) => {
1171
+ // "model_id": "eleven_multilingual_v1",
1172
+ ttsService.textToSpeechStream(node.server.credentials.elevenlabsKey, params.voice, params.text, null, null, "eleven_multilingual_v1").then((res) => {
1173
+ try {
1174
+ if (res !== undefined) {
1175
+ resolve(stream2buffer(res));
1176
+ } else {
1177
+ reject(new Error("Please check credentials. The stream cannot be read from the elevenlabs TTS server"))
1178
+ }
1179
+ } catch (error) {
1180
+ reject(error)
1181
+ }
1182
+ });
1183
+ });
1184
+ }
1200
1185
 
1186
+ // 04/01/2021 hashing filename to avoid issues with long filenames.
1187
+ function getFilename(_text, _params) {
1188
+ let sTextToBeHashed = _text.concat(JSON.stringify(_params));
1189
+ const hashSum = crypto.createHash('md5');
1190
+ hashSum.update(sTextToBeHashed);
1191
+ return hashSum.digest('hex') + ".mp3";
1192
+ }
1201
1193
 
1194
+ function notifyError(msg, err) {
1195
+ var errorMessage = err.message;
1196
+ // Output error to console
1197
+ node.setNodeStatus({
1198
+ fill: 'red',
1199
+ shape: 'dot',
1200
+ text: 'notifyError: ' + errorMessage
1201
+ });
1202
+ // Set error in message
1203
+ msg.error = errorMessage;
1202
1204
  }
1203
- RED.nodes.registerType('ttsultimate', PollyNode);
1205
+
1206
+
1207
+ }
1208
+ RED.nodes.registerType('ttsultimate', PollyNode);
1204
1209
 
1205
1210
 
1206
1211
  }