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.
- package/CHANGELOG.md +4 -0
- package/README.md +7 -0
- package/package.json +1 -1
- package/ttsultimate/ttsultimate.html +415 -414
- package/ttsultimate/ttsultimate.js +1118 -1113
|
@@ -1,1206 +1,1211 @@
|
|
|
1
1
|
const { Readable } = require('stream');
|
|
2
2
|
|
|
3
3
|
module.exports = function (RED) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
85
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
|
|
366
|
+
//#endregion
|
|
367
367
|
|
|
368
368
|
|
|
369
|
-
|
|
369
|
+
// 27/11/2019 Check Sonos connection health
|
|
370
370
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
|
|
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
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|
-
|
|
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
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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
|
-
|
|
453
|
-
_oTrack.trackType = "musicqueue";
|
|
597
|
+
params.VoiceId = node.voiceId;
|
|
454
598
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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
|
-
|
|
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
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
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
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
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
|
-
|
|
953
|
-
|
|
954
|
-
node.flushQueue();
|
|
683
|
+
// Save the downloaded file into the cache
|
|
684
|
+
if (data !== undefined) {
|
|
955
685
|
try {
|
|
956
|
-
|
|
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
|
-
|
|
960
|
-
return;
|
|
961
|
-
}
|
|
693
|
+
}
|
|
962
694
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
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
|
-
|
|
970
|
-
if (
|
|
971
|
-
node.unmuteIfMuted = msg.unmute;
|
|
972
|
-
}
|
|
705
|
+
//#region Now i am ready to play the file
|
|
706
|
+
if (node.playertype === "sonos") {
|
|
973
707
|
|
|
974
|
-
|
|
975
|
-
|
|
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
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
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
|
-
|
|
722
|
+
volTemp = Number(node.sSonosVolume);
|
|
1000
723
|
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
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
|
-
|
|
742
|
+
};
|
|
743
|
+
};
|
|
1016
744
|
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
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
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
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
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
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
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
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
|
-
|
|
1078
|
-
|
|
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.
|
|
1083
|
-
node.
|
|
1084
|
-
|
|
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
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
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
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
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
|
-
|
|
1127
|
-
try {
|
|
897
|
+
}
|
|
1128
898
|
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
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
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
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
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
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
|
-
//
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
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
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
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
|
-
|
|
1198
|
-
|
|
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
|
-
|
|
1205
|
+
|
|
1206
|
+
|
|
1207
|
+
}
|
|
1208
|
+
RED.nodes.registerType('ttsultimate', PollyNode);
|
|
1204
1209
|
|
|
1205
1210
|
|
|
1206
1211
|
}
|