node-red-contrib-tts-ultimate 2.0.6 → 2.0.8
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 +9 -0
- package/README.md +16 -5
- package/package.json +3 -3
- package/ttsultimate/ttsultimate-config.html +23 -12
- package/ttsultimate/ttsultimate-config.js +45 -4
- package/ttsultimate/ttsultimate.html +416 -415
- package/ttsultimate/ttsultimate.js +1152 -1112
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
</div>
|
|
45
45
|
<div class="form-row" id="divSSML">
|
|
46
46
|
<label></label>
|
|
47
|
-
<input type="checkbox" id="node-input-ssml" style="margin-left: 0px; vertical-align: top; width: auto !important;"> <label style="width:auto !important;"> Enable SSML (unsupported by Google
|
|
47
|
+
<input type="checkbox" id="node-input-ssml" style="margin-left: 0px; vertical-align: top; width: auto !important;"> <label style="width:auto !important;"> Enable SSML (unsupported by Google free TTS)</label>
|
|
48
48
|
</div>
|
|
49
49
|
<div class="form-row">
|
|
50
50
|
<label for="node-input-sonoshailing"><i class="fa fa-bell"></i> Hailing</label>
|
|
@@ -95,435 +95,435 @@
|
|
|
95
95
|
</script>
|
|
96
96
|
|
|
97
97
|
<script type="text/javascript">
|
|
98
|
-
|
|
98
|
+
RED.nodes.registerType('ttsultimate',
|
|
99
|
+
{
|
|
100
|
+
category: 'TTSUltimate',
|
|
101
|
+
color: '#ffe6ff', // #ffe6ff
|
|
102
|
+
defaults:
|
|
103
|
+
{
|
|
104
|
+
name: { value: "TTS-Ultimate" },
|
|
105
|
+
voice:
|
|
99
106
|
{
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
107
|
+
value: "Ivy",
|
|
108
|
+
required: false
|
|
109
|
+
},
|
|
110
|
+
ssml:
|
|
111
|
+
{
|
|
112
|
+
value: false,
|
|
113
|
+
required: false
|
|
114
|
+
},
|
|
115
|
+
sonosipaddress:
|
|
116
|
+
{
|
|
117
|
+
value: "",
|
|
118
|
+
required: false
|
|
119
|
+
},
|
|
120
|
+
sonosvolume:
|
|
121
|
+
{
|
|
122
|
+
value: "20",
|
|
123
|
+
required: false,
|
|
124
|
+
type: "text"
|
|
125
|
+
},
|
|
126
|
+
sonoshailing:
|
|
127
|
+
{
|
|
128
|
+
value: "Hailing_Hailing.mp3",
|
|
129
|
+
required: false,
|
|
130
|
+
},
|
|
131
|
+
config:
|
|
132
|
+
{
|
|
133
|
+
type: "ttsultimate-config",
|
|
134
|
+
required: false
|
|
135
|
+
},
|
|
136
|
+
property: { value: "payload", required: false, validate: RED.validators.typedInput("propertyType") },
|
|
137
|
+
propertyType: { value: "msg" },
|
|
138
|
+
rules: { value: [] },
|
|
139
|
+
playertype: { value: "sonos", required: false },
|
|
140
|
+
speakingrate: { value: "1", required: false },
|
|
141
|
+
speakingpitch: { value: "0", required: false },
|
|
142
|
+
unmuteIfMuted: { value: true },
|
|
143
|
+
elevenlabsStability: { value: "0.5", required: false },
|
|
144
|
+
elevenlabsSimilarity_boost: { value: "0.5", required: false },
|
|
145
|
+
},
|
|
146
|
+
inputs: 1,
|
|
147
|
+
outputs: 2,
|
|
148
|
+
outputLabels: function (i) {
|
|
149
|
+
var ret = "";
|
|
150
|
+
switch (i) {
|
|
151
|
+
case 0:
|
|
152
|
+
return "Play Status";
|
|
153
|
+
break;
|
|
154
|
+
case 1:
|
|
155
|
+
return "Error";
|
|
156
|
+
break;
|
|
157
|
+
default:
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
icon: "tts-icon.png",
|
|
162
|
+
label: function () {
|
|
163
|
+
return this.name || "TTS-ultimate";
|
|
164
|
+
},
|
|
165
|
+
oneditprepare: function () {
|
|
166
|
+
var node = this;
|
|
167
|
+
var oNodeServer = RED.nodes.node($("#node-input-config").val()); // Store the config-node
|
|
168
|
+
|
|
169
|
+
// 19/02/2020 Used to alert the user if the CSV file has not been loaded and to get the server sooner als deploy
|
|
170
|
+
// ###########################
|
|
171
|
+
$("#node-input-config").change(function () {
|
|
172
|
+
try {
|
|
173
|
+
oNodeServer = RED.nodes.node($(this).val());
|
|
174
|
+
getVoices();
|
|
175
|
+
if (oNodeServer.ttsservice === "googletts") {
|
|
176
|
+
$("#divGoogleTTSAudioConfig").show();
|
|
177
|
+
$("#divElevenLabsOptions").hide();
|
|
178
|
+
} else if (oNodeServer.ttsservice.includes("elevenlabs")) {
|
|
179
|
+
$("#divGoogleTTSAudioConfig").hide();
|
|
180
|
+
$("#divElevenLabsOptions").show();
|
|
181
|
+
$("#divSSML").hide();
|
|
182
|
+
} else {
|
|
183
|
+
$("#divGoogleTTSAudioConfig").hide();
|
|
184
|
+
$("#divElevenLabsOptions").hide();
|
|
185
|
+
$("#divSSML").show();
|
|
186
|
+
}
|
|
187
|
+
} catch (error) {
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
// ###########################
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
// 20/09/2021 Player type
|
|
194
|
+
// ###########################
|
|
195
|
+
if (node.playertype === undefined) {
|
|
196
|
+
node.playertype = "sonos";
|
|
197
|
+
$("#node-input-playertype").val("sonos");
|
|
198
|
+
}
|
|
199
|
+
if (node.playertype === "sonos") {
|
|
200
|
+
$("#divSonos").show();
|
|
201
|
+
} else {
|
|
202
|
+
$("#divSonos").hide();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
$("#node-input-playertype").change(function () {
|
|
206
|
+
if ($("#node-input-playertype").val() === "sonos") {
|
|
207
|
+
$("#divSonos").show();
|
|
208
|
+
} else {
|
|
209
|
+
$("#divSonos").hide();
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
// ###########################
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
// 24/12/2020 Check if the node is the absolute first in the flow. In this case, it has no http server instatiaced
|
|
216
|
+
// !oNodeServer.hasOwnProperty("noderedipaddress") is when the config node exists, but not deployed
|
|
217
|
+
// oNodeServer.noderedipaddress === "" is when the config node has been deployed, but not configured
|
|
218
|
+
if (oNodeServer === null || oNodeServer === undefined || !oNodeServer.hasOwnProperty("noderedipaddress")) {
|
|
219
|
+
$("#pleaseDeploy").show();
|
|
220
|
+
$("#allGUI").hide();
|
|
221
|
+
} else if (oNodeServer.hasOwnProperty("noderedipaddress") && oNodeServer.noderedipaddress === "") {
|
|
222
|
+
// Config Node deployed but not configured
|
|
223
|
+
$("#pleaseDeploy").html("<p align=\"center\"><b>GOOD, YOU'RE ALMOST DONE</b><br/>PLEASE FINISH CONFIGURING<br/>THE CONFIG NODE ABOVE,<br/><b>THEN SAVE AND FULL DEPLOY</b>.</p>");
|
|
224
|
+
$("#pleaseDeploy").show();
|
|
225
|
+
$("#allGUI").hide();
|
|
226
|
+
} else {
|
|
227
|
+
$("#pleaseDeploy").hide();
|
|
228
|
+
$("#allGUI").show();
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
// 26/10/2020 Retrieve all avaiables voices
|
|
233
|
+
// #####################################
|
|
234
|
+
function getVoices() {
|
|
235
|
+
$('#node-input-voice')
|
|
236
|
+
.find('option')
|
|
237
|
+
.remove()
|
|
238
|
+
.end();
|
|
239
|
+
$.getJSON("ttsgetvoices" + encodeURIComponent(oNodeServer.id) + "?ttsservice=" + oNodeServer.ttsservice, new Date().getTime(), (data) => {
|
|
240
|
+
var aVoices = data.sort(compareVoices);
|
|
241
|
+
for (let index = 0; index < aVoices.length; index++) {
|
|
242
|
+
const oVoice = aVoices[index];
|
|
243
|
+
$("#node-input-voice").append($("<option></option>")
|
|
244
|
+
.attr("value", oVoice.id)
|
|
245
|
+
.text(`${oVoice.name} (ID: ${oVoice.id})`)
|
|
246
|
+
)
|
|
247
|
+
if (oVoice.name.indexOf("ttsultimategooglecredentials") > -1) {
|
|
248
|
+
// Cred file not present or invalid
|
|
249
|
+
// The only way is to wait some time, then refresh
|
|
250
|
+
let myNotification = RED.notify("Either you haven't uploaded the credential file, or the file is invalid. " + oVoice.name,
|
|
251
|
+
{
|
|
252
|
+
modal: true,
|
|
253
|
+
fixed: true,
|
|
254
|
+
type: 'error',
|
|
255
|
+
buttons: [
|
|
256
|
+
{
|
|
257
|
+
"text": "OK",
|
|
258
|
+
"class": "primary",
|
|
259
|
+
"click": function (e) {
|
|
260
|
+
myNotification.close();
|
|
186
261
|
}
|
|
187
|
-
|
|
188
|
-
|
|
262
|
+
}]
|
|
263
|
+
});
|
|
264
|
+
break;
|
|
265
|
+
};
|
|
266
|
+
};
|
|
267
|
+
$("#node-input-voice").val(node.voice);
|
|
268
|
+
});
|
|
269
|
+
function compareVoices(a, b) {
|
|
270
|
+
// Use toUpperCase() to ignore character casing
|
|
271
|
+
const bandA = a.name.toUpperCase();
|
|
272
|
+
const bandB = b.name.toUpperCase();
|
|
273
|
+
|
|
274
|
+
let comparison = 0;
|
|
275
|
+
if (bandA > bandB) {
|
|
276
|
+
comparison = 1;
|
|
277
|
+
} else if (bandA < bandB) {
|
|
278
|
+
comparison = -1;
|
|
279
|
+
}
|
|
280
|
+
return comparison;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
getVoices();
|
|
284
|
+
// #####################################
|
|
285
|
+
|
|
286
|
+
// Refresh the combo
|
|
287
|
+
// #####################################
|
|
288
|
+
node.refreshHailingList = () => {
|
|
289
|
+
return new Promise((resolve, reject) => {
|
|
290
|
+
$('#node-input-sonoshailing')
|
|
291
|
+
.find('option')
|
|
292
|
+
.remove()
|
|
293
|
+
.end();
|
|
294
|
+
try {
|
|
295
|
+
$.getJSON("getHailingFilesList", new Date().getTime(), (data) => {
|
|
296
|
+
$("#node-input-sonoshailing").append($("<option></option>")
|
|
297
|
+
.attr("value", "0")
|
|
298
|
+
.text("Disable")
|
|
299
|
+
)
|
|
300
|
+
data.sort().forEach(oFile => {
|
|
301
|
+
$("#node-input-sonoshailing").append($("<option></option>")
|
|
302
|
+
.attr("value", oFile.filename)
|
|
303
|
+
.text(oFile.name)
|
|
304
|
+
)
|
|
189
305
|
});
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
306
|
+
$("#node-input-sonoshailing").val(typeof node.sonoshailing === "undefined" ? "Hailing_Hailing.mp3" : node.sonoshailing);
|
|
307
|
+
resolve(true);
|
|
308
|
+
});
|
|
309
|
+
} catch (error) {
|
|
310
|
+
return reject(error);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
// #####################################
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
// 09/03/2020 Upload file or files
|
|
318
|
+
// ##########################################################
|
|
319
|
+
$("#ownFileUpload").change(function (e) {
|
|
320
|
+
var oFiles;
|
|
321
|
+
oFiles = this.files;
|
|
322
|
+
$.each(oFiles, function (i, file) {
|
|
323
|
+
var fdata = new FormData();
|
|
324
|
+
fdata.append("customHailing", file);
|
|
325
|
+
|
|
326
|
+
$.ajax({
|
|
327
|
+
url: "ttsultimateHailing",
|
|
328
|
+
type: "POST",
|
|
329
|
+
data: fdata, //add the FormData object to the data parameter
|
|
330
|
+
processData: false, //tell jquery not to process data
|
|
331
|
+
contentType: false,
|
|
332
|
+
success: function (response, status, jqxhr) {
|
|
333
|
+
if (typeof response === "object" && response.status === 404) {
|
|
334
|
+
let myNotification = RED.notify(response.message,
|
|
335
|
+
{
|
|
336
|
+
modal: true,
|
|
337
|
+
fixed: true,
|
|
338
|
+
type: 'error',
|
|
339
|
+
buttons: [
|
|
340
|
+
{
|
|
341
|
+
text: "Understood",
|
|
342
|
+
click: function (e) {
|
|
343
|
+
myNotification.close();
|
|
344
|
+
}
|
|
345
|
+
}]
|
|
204
346
|
|
|
205
|
-
$("#node-input-playertype").change(function () {
|
|
206
|
-
if ($("#node-input-playertype").val() === "sonos") {
|
|
207
|
-
$("#divSonos").show();
|
|
208
|
-
} else {
|
|
209
|
-
$("#divSonos").hide();
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
// ###########################
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
// 24/12/2020 Check if the node is the absolute first in the flow. In this case, it has no http server instatiaced
|
|
216
|
-
// !oNodeServer.hasOwnProperty("noderedipaddress") is when the config node exists, but not deployed
|
|
217
|
-
// oNodeServer.noderedipaddress === "" is when the config node has been deployed, but not configured
|
|
218
|
-
if (oNodeServer === null || oNodeServer === undefined || !oNodeServer.hasOwnProperty("noderedipaddress")) {
|
|
219
|
-
$("#pleaseDeploy").show();
|
|
220
|
-
$("#allGUI").hide();
|
|
221
|
-
} else if (oNodeServer.hasOwnProperty("noderedipaddress") && oNodeServer.noderedipaddress === "") {
|
|
222
|
-
// Config Node deployed but not configured
|
|
223
|
-
$("#pleaseDeploy").html("<p align=\"center\"><b>GOOD, YOU'RE ALMOST DONE</b><br/>PLEASE FINISH CONFIGURING<br/>THE CONFIG NODE ABOVE,<br/><b>THEN SAVE AND FULL DEPLOY</b>.</p>");
|
|
224
|
-
$("#pleaseDeploy").show();
|
|
225
|
-
$("#allGUI").hide();
|
|
226
|
-
} else {
|
|
227
|
-
$("#pleaseDeploy").hide();
|
|
228
|
-
$("#allGUI").show();
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
// 26/10/2020 Retrieve all avaiables voices
|
|
233
|
-
// #####################################
|
|
234
|
-
function getVoices() {
|
|
235
|
-
$('#node-input-voice')
|
|
236
|
-
.find('option')
|
|
237
|
-
.remove()
|
|
238
|
-
.end();
|
|
239
|
-
$.getJSON("ttsgetvoices" + encodeURIComponent(oNodeServer.id) + "?ttsservice=" + oNodeServer.ttsservice, new Date().getTime(), (data) => {
|
|
240
|
-
var aVoices = data.sort(compareVoices);
|
|
241
|
-
for (let index = 0; index < aVoices.length; index++) {
|
|
242
|
-
const oVoice = aVoices[index];
|
|
243
|
-
$("#node-input-voice").append($("<option></option>")
|
|
244
|
-
.attr("value", oVoice.id)
|
|
245
|
-
.text(oVoice.name)
|
|
246
|
-
)
|
|
247
|
-
if (oVoice.name.indexOf("ttsultimategooglecredentials") > -1) {
|
|
248
|
-
// Cred file not present or invalid
|
|
249
|
-
// The only way is to wait some time, then refresh
|
|
250
|
-
let myNotification = RED.notify("Either you haven't uploaded the credential file, or the file is invalid. " + oVoice.name,
|
|
251
|
-
{
|
|
252
|
-
modal: true,
|
|
253
|
-
fixed: true,
|
|
254
|
-
type: 'error',
|
|
255
|
-
buttons: [
|
|
256
|
-
{
|
|
257
|
-
"text": "OK",
|
|
258
|
-
"class": "primary",
|
|
259
|
-
"click": function (e) {
|
|
260
|
-
myNotification.close();
|
|
261
|
-
}
|
|
262
|
-
}]
|
|
263
|
-
});
|
|
264
|
-
break;
|
|
265
|
-
};
|
|
266
|
-
};
|
|
267
|
-
$("#node-input-voice").val(node.voice);
|
|
268
347
|
});
|
|
269
|
-
|
|
270
|
-
// Use toUpperCase() to ignore character casing
|
|
271
|
-
const bandA = a.name.toUpperCase();
|
|
272
|
-
const bandB = b.name.toUpperCase();
|
|
273
|
-
|
|
274
|
-
let comparison = 0;
|
|
275
|
-
if (bandA > bandB) {
|
|
276
|
-
comparison = 1;
|
|
277
|
-
} else if (bandA < bandB) {
|
|
278
|
-
comparison = -1;
|
|
279
|
-
}
|
|
280
|
-
return comparison;
|
|
281
|
-
}
|
|
348
|
+
return;
|
|
282
349
|
}
|
|
283
|
-
getVoices();
|
|
284
|
-
// #####################################
|
|
285
|
-
|
|
286
350
|
// Refresh the combo
|
|
287
|
-
//
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
$("#node-input-sonoshailing").val(typeof node.sonoshailing === "undefined" ? "Hailing_Hailing.mp3" : node.sonoshailing);
|
|
307
|
-
resolve(true);
|
|
308
|
-
});
|
|
309
|
-
} catch (error) {
|
|
310
|
-
return reject(error);
|
|
351
|
+
// The only way is to wait some time, then refresh
|
|
352
|
+
let t = setTimeout(function () {
|
|
353
|
+
node.refreshHailingList().then((success, error) => {
|
|
354
|
+
$("#ownFileUpload").val("");// Otherwise will not re-upload a file with the same name
|
|
355
|
+
$("#node-input-sonoshailing").val("Hailing_" + file.name);
|
|
356
|
+
});
|
|
357
|
+
}, 500);
|
|
358
|
+
},
|
|
359
|
+
error: function (jqxhr, status, errorMessage) {
|
|
360
|
+
let myNotification = RED.notify(errorMessage,
|
|
361
|
+
{
|
|
362
|
+
modal: true,
|
|
363
|
+
fixed: true,
|
|
364
|
+
type: 'error',
|
|
365
|
+
buttons: [
|
|
366
|
+
{
|
|
367
|
+
text: "Understood",
|
|
368
|
+
click: function (e) {
|
|
369
|
+
myNotification.close();
|
|
311
370
|
}
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
// #####################################
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
// 09/03/2020 Upload file or files
|
|
318
|
-
// ##########################################################
|
|
319
|
-
$("#ownFileUpload").change(function (e) {
|
|
320
|
-
var oFiles;
|
|
321
|
-
oFiles = this.files;
|
|
322
|
-
$.each(oFiles, function (i, file) {
|
|
323
|
-
var fdata = new FormData();
|
|
324
|
-
fdata.append("customHailing", file);
|
|
325
|
-
|
|
326
|
-
$.ajax({
|
|
327
|
-
url: "ttsultimateHailing",
|
|
328
|
-
type: "POST",
|
|
329
|
-
data: fdata, //add the FormData object to the data parameter
|
|
330
|
-
processData: false, //tell jquery not to process data
|
|
331
|
-
contentType: false,
|
|
332
|
-
success: function (response, status, jqxhr) {
|
|
333
|
-
if (typeof response === "object" && response.status === 404) {
|
|
334
|
-
let myNotification = RED.notify(response.message,
|
|
335
|
-
{
|
|
336
|
-
modal: true,
|
|
337
|
-
fixed: true,
|
|
338
|
-
type: 'error',
|
|
339
|
-
buttons: [
|
|
340
|
-
{
|
|
341
|
-
text: "Understood",
|
|
342
|
-
click: function (e) {
|
|
343
|
-
myNotification.close();
|
|
344
|
-
}
|
|
345
|
-
}]
|
|
346
|
-
|
|
347
|
-
});
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
// Refresh the combo
|
|
351
|
-
// The only way is to wait some time, then refresh
|
|
352
|
-
let t = setTimeout(function () {
|
|
353
|
-
node.refreshHailingList().then((success, error) => {
|
|
354
|
-
$("#ownFileUpload").val("");// Otherwise will not re-upload a file with the same name
|
|
355
|
-
$("#node-input-sonoshailing").val("Hailing_" + file.name);
|
|
356
|
-
});
|
|
357
|
-
}, 500);
|
|
358
|
-
},
|
|
359
|
-
error: function (jqxhr, status, errorMessage) {
|
|
360
|
-
let myNotification = RED.notify(errorMessage,
|
|
361
|
-
{
|
|
362
|
-
modal: true,
|
|
363
|
-
fixed: true,
|
|
364
|
-
type: 'error',
|
|
365
|
-
buttons: [
|
|
366
|
-
{
|
|
367
|
-
text: "Understood",
|
|
368
|
-
click: function (e) {
|
|
369
|
-
myNotification.close();
|
|
370
|
-
}
|
|
371
|
-
}]
|
|
372
|
-
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
});
|
|
376
|
-
});
|
|
377
|
-
});
|
|
378
|
-
// ##########################################################
|
|
379
|
-
|
|
371
|
+
}]
|
|
380
372
|
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
// ##########################################################
|
|
381
379
|
|
|
382
|
-
// Delete selected file
|
|
383
|
-
// ##########################################################
|
|
384
|
-
$("#deleteSelectedFile").click(function () {
|
|
385
|
-
$.getJSON('deleteHailingFile?FileName=' + $("#node-input-sonoshailing").val(), (data) => {
|
|
386
|
-
node.refreshHailingList();
|
|
387
|
-
});
|
|
388
|
-
});
|
|
389
|
-
// ##########################################################
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
// 20/03/2020 Coronavirus issue in Itay. Getting all Sonos Groups
|
|
394
|
-
// ##########################################################
|
|
395
|
-
$.getJSON('sonosgetAllGroups', (data) => {
|
|
396
|
-
if (typeof data === "string" && data == "ERRORDISCOVERY") { // 10/04/2020 if error in discovery, fallback to manual IP input
|
|
397
|
-
// Transform the dropdown to a simple input
|
|
398
|
-
$("#node-input-sonosipaddress").replaceWith('<input type="text" id="node-input-sonosipaddress" style="width:150px">');
|
|
399
|
-
} else {
|
|
400
|
-
$("#node-input-sonosipaddress").replaceWith('<select id="node-input-sonosipaddress"></select>');
|
|
401
|
-
data.sort().forEach(oGroup => {
|
|
402
|
-
$("#node-input-sonosipaddress").append($("<option></option>")
|
|
403
|
-
.attr("value", oGroup.host)
|
|
404
|
-
.text(oGroup.name + " (" + oGroup.host + ")")
|
|
405
|
-
)
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
if (typeof node.sonosipaddress === "undefined" || node.sonosipaddress == "") {
|
|
409
|
-
$("#node-input-sonosipaddress").val($("#node-input-sonosipaddress option:first").val());
|
|
410
|
-
} else { $("#node-input-sonosipaddress").val(node.sonosipaddress) }
|
|
411
|
-
});
|
|
412
|
-
// ##########################################################
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
//#region ADDITIONAL PLAYERS
|
|
416
|
-
// 20/03/2020 ADDITIONAL PLAYERS
|
|
417
|
-
// ##########################################################
|
|
418
|
-
//var previousValueType = { value: "prev", label: this._("switch.previous"), hasValue: false };
|
|
419
|
-
|
|
420
|
-
// Add Selectbox with the volume for the additional players
|
|
421
|
-
function addAdditionalPlayerVolumeUI(row, _currentVolume = 0) {
|
|
422
|
-
let oAdjustVolume = $('<select/>', { class: "rowRulePlayerHostAdjustVolume", type: "text", style: "width:200px; margin-left: 5px; text-align: left;" }).appendTo(row);
|
|
423
|
-
for (let index = -100; index < 100; index += 5) {
|
|
424
|
-
let sTesto = "";
|
|
425
|
-
if (index === 0) sTesto = "Same volume as Main Sonos Player";
|
|
426
|
-
if (index < 0) sTesto = "Decrease volume by " + Math.abs(index);
|
|
427
|
-
if (index > 0) sTesto = "Increase volume by " + index;
|
|
428
|
-
oAdjustVolume.append($("<option></option>")
|
|
429
|
-
.attr("value", index)
|
|
430
|
-
.text(sTesto)
|
|
431
|
-
)
|
|
432
|
-
}
|
|
433
|
-
oAdjustVolume.val(_currentVolume);
|
|
434
|
-
}
|
|
435
|
-
function resizeRule(rule) { }
|
|
436
380
|
|
|
437
|
-
$("#node-input-rule-container").css('min-height', '150px').css('min-width', '450px').editableList({
|
|
438
|
-
addItem: function (container, i, opt) { // row, index, data
|
|
439
381
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
382
|
+
// Delete selected file
|
|
383
|
+
// ##########################################################
|
|
384
|
+
$("#deleteSelectedFile").click(function () {
|
|
385
|
+
$.getJSON('deleteHailingFile?FileName=' + $("#node-input-sonoshailing").val(), (data) => {
|
|
386
|
+
node.refreshHailingList();
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
// ##########################################################
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
// 20/03/2020 Coronavirus issue in Itay. Getting all Sonos Groups
|
|
394
|
+
// ##########################################################
|
|
395
|
+
$.getJSON('sonosgetAllGroups', (data) => {
|
|
396
|
+
if (typeof data === "string" && data == "ERRORDISCOVERY") { // 10/04/2020 if error in discovery, fallback to manual IP input
|
|
397
|
+
// Transform the dropdown to a simple input
|
|
398
|
+
$("#node-input-sonosipaddress").replaceWith('<input type="text" id="node-input-sonosipaddress" style="width:150px">');
|
|
399
|
+
} else {
|
|
400
|
+
$("#node-input-sonosipaddress").replaceWith('<select id="node-input-sonosipaddress"></select>');
|
|
401
|
+
data.sort().forEach(oGroup => {
|
|
402
|
+
$("#node-input-sonosipaddress").append($("<option></option>")
|
|
403
|
+
.attr("value", oGroup.host)
|
|
404
|
+
.text(oGroup.name + " (" + oGroup.host + ")")
|
|
405
|
+
)
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
if (typeof node.sonosipaddress === "undefined" || node.sonosipaddress == "") {
|
|
409
|
+
$("#node-input-sonosipaddress").val($("#node-input-sonosipaddress option:first").val());
|
|
410
|
+
} else { $("#node-input-sonosipaddress").val(node.sonosipaddress) }
|
|
411
|
+
});
|
|
412
|
+
// ##########################################################
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
//#region ADDITIONAL PLAYERS
|
|
416
|
+
// 20/03/2020 ADDITIONAL PLAYERS
|
|
417
|
+
// ##########################################################
|
|
418
|
+
//var previousValueType = { value: "prev", label: this._("switch.previous"), hasValue: false };
|
|
419
|
+
|
|
420
|
+
// Add Selectbox with the volume for the additional players
|
|
421
|
+
function addAdditionalPlayerVolumeUI(row, _currentVolume = 0) {
|
|
422
|
+
let oAdjustVolume = $('<select/>', { class: "rowRulePlayerHostAdjustVolume", type: "text", style: "width:200px; margin-left: 5px; text-align: left;" }).appendTo(row);
|
|
423
|
+
for (let index = -100; index < 100; index += 5) {
|
|
424
|
+
let sTesto = "";
|
|
425
|
+
if (index === 0) sTesto = "Same volume as Main Sonos Player";
|
|
426
|
+
if (index < 0) sTesto = "Decrease volume by " + Math.abs(index);
|
|
427
|
+
if (index > 0) sTesto = "Increase volume by " + index;
|
|
428
|
+
oAdjustVolume.append($("<option></option>")
|
|
429
|
+
.attr("value", index)
|
|
430
|
+
.text(sTesto)
|
|
431
|
+
)
|
|
432
|
+
}
|
|
433
|
+
oAdjustVolume.val(_currentVolume);
|
|
434
|
+
}
|
|
435
|
+
function resizeRule(rule) { }
|
|
436
|
+
|
|
437
|
+
$("#node-input-rule-container").css('min-height', '150px').css('min-width', '450px').editableList({
|
|
438
|
+
addItem: function (container, i, opt) { // row, index, data
|
|
439
|
+
|
|
440
|
+
if (!opt.hasOwnProperty('r')) {
|
|
441
|
+
opt.r = {};
|
|
442
|
+
}
|
|
443
|
+
let rule = opt.r;
|
|
444
|
+
if (!opt.hasOwnProperty('i')) {
|
|
445
|
+
opt._i = Math.floor((0x99999 - 0x10000) * Math.random()).toString();
|
|
446
|
+
}
|
|
447
|
+
container.css({
|
|
448
|
+
overflow: 'hidden',
|
|
449
|
+
whiteSpace: 'nowrap'
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
var row = $('<div class="form-row"/>').appendTo(container);
|
|
454
|
+
var oPlayer = $('<label>Discovering.... wait...</label>', { class: "rowRulePlayerHost", type: "text", style: "width:200px; margin-left: 5px; text-align: left;" }).appendTo(row);
|
|
455
|
+
oPlayer.on("change", function () {
|
|
456
|
+
resizeRule(container);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
$.getJSON('sonosgetAllGroups', (data) => {
|
|
460
|
+
if (typeof data === "string" && data == "ERRORDISCOVERY") { // 10/04/2020 if error in discovery, fallback to manual IP input
|
|
461
|
+
// Transform the dropdown to a simple input
|
|
462
|
+
oPlayer.remove();
|
|
463
|
+
oPlayer = $('<input/>', { class: "rowRulePlayerHost", type: "text", style: "width:200px; margin-left: 5px; text-align: left;" }).appendTo(row);
|
|
464
|
+
addAdditionalPlayerVolumeUI(row, rule.hostVolumeAdjust);
|
|
465
|
+
} else {
|
|
466
|
+
oPlayer.remove();
|
|
467
|
+
oPlayer = $('<select/>', { class: "rowRulePlayerHost", type: "text", style: "width:200px; margin-left: 5px; text-align: left;" }).appendTo(row);
|
|
468
|
+
data.sort().forEach(oGroup => {
|
|
469
|
+
oPlayer.append($("<option></option>")
|
|
470
|
+
.attr("value", oGroup.host)
|
|
471
|
+
.text(oGroup.name + " (" + oGroup.host + ")")
|
|
472
|
+
)
|
|
489
473
|
});
|
|
474
|
+
addAdditionalPlayerVolumeUI(row, rule.hostVolumeAdjust);
|
|
475
|
+
}
|
|
476
|
+
oPlayer.val(rule.host);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
//oPlayer.change();
|
|
480
|
+
},
|
|
481
|
+
removeItem: function (opt) {
|
|
482
|
+
|
|
483
|
+
},
|
|
484
|
+
resizeItem: resizeRule,
|
|
485
|
+
sortItems: function (rules) {
|
|
486
|
+
},
|
|
487
|
+
sortable: true,
|
|
488
|
+
removable: true
|
|
489
|
+
});
|
|
490
490
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
});
|
|
511
|
-
this.propertyType = $("#node-input-property").typedInput('type');
|
|
512
|
-
},
|
|
513
|
-
oneditresize: function (size) {
|
|
514
|
-
var node = this;
|
|
515
|
-
|
|
516
|
-
var rows = $("#dialog-form>div:not(.node-input-rule-container-row)");
|
|
517
|
-
var height = size.height;
|
|
518
|
-
for (var i = 0; i < rows.length; i++) {
|
|
519
|
-
height -= $(rows[i]).outerHeight(true);
|
|
520
|
-
}
|
|
521
|
-
var editorRow = $("#dialog-form>div.node-input-rule-container-row");
|
|
522
|
-
height -= (parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom")));
|
|
523
|
-
height += 16;
|
|
524
|
-
$("#node-input-rule-container").editableList('height', height);
|
|
525
|
-
}
|
|
491
|
+
// 20/03/2020 For each rule, create a row
|
|
492
|
+
for (var i = 0; i < this.rules.length; i++) {
|
|
493
|
+
let rule = this.rules[i];
|
|
494
|
+
$("#node-input-rule-container").editableList('addItem', { r: rule, i: i });
|
|
495
|
+
}
|
|
496
|
+
// ##########################################################
|
|
497
|
+
//#endregion
|
|
498
|
+
|
|
499
|
+
node.refreshHailingList();
|
|
500
|
+
|
|
501
|
+
}, oneditsave: function () {
|
|
502
|
+
let node = this;
|
|
503
|
+
let rules = $("#node-input-rule-container").editableList('items');
|
|
504
|
+
node.rules = [];
|
|
505
|
+
rules.each(function (i) {
|
|
506
|
+
let rule = $(this);
|
|
507
|
+
let rowRulePlayerHost = rule.find(".rowRulePlayerHost").val();
|
|
508
|
+
let rowRulePlayerHostAdjustVolume = rule.find(".rowRulePlayerHostAdjustVolume").val();
|
|
509
|
+
node.rules.push({ host: rowRulePlayerHost, hostVolumeAdjust: rowRulePlayerHostAdjustVolume });
|
|
526
510
|
});
|
|
511
|
+
this.propertyType = $("#node-input-property").typedInput('type');
|
|
512
|
+
},
|
|
513
|
+
oneditresize: function (size) {
|
|
514
|
+
var node = this;
|
|
515
|
+
|
|
516
|
+
var rows = $("#dialog-form>div:not(.node-input-rule-container-row)");
|
|
517
|
+
var height = size.height;
|
|
518
|
+
for (var i = 0; i < rows.length; i++) {
|
|
519
|
+
height -= $(rows[i]).outerHeight(true);
|
|
520
|
+
}
|
|
521
|
+
var editorRow = $("#dialog-form>div.node-input-rule-container-row");
|
|
522
|
+
height -= (parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom")));
|
|
523
|
+
height += 16;
|
|
524
|
+
$("#node-input-rule-container").editableList('height', height);
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
527
|
</script>
|
|
528
528
|
|
|
529
529
|
<script type="text/markdown" data-help-name="ttsultimate">
|
|
@@ -556,6 +556,7 @@
|
|
|
556
556
|
: sonoshailing (string) : Overrides the selected hailing and plays the filename you passed in. Please double check the spelling of the filename (must be the same as you can see in the dropdown list of your hailing files, in the ttsultimate config window) and do not include the .mp3 extenson. For example node.sonoshailing="ComputerCall".
|
|
557
557
|
: priority (boolean) : If set to true, the inbound flow message will cancel the current TTS queue, will stop the current phrase being spoken and the node will play this priority message. If there are other priority messages in the queue, they will be retained and the inbound priority flow message is added to the queue. If the inbound priority flow message is the first in the priority queue, the hailing is played first (if the hailing has been enabled or if the hailing has been overridden by node.sonoshailing).
|
|
558
558
|
: stop (boolean) : If set to true, stops whatever is playing and clears the TTS queue.
|
|
559
|
+
: voiceId (number) : Play a message with custom voice ID.
|
|
559
560
|
: setConfig (json) : This is the property where you can set all the things. It must be a JSON Object with the below specified properties. The setting is retained until the node receives another msg.setConfig or until node-red is restarted.
|
|
560
561
|
: setConfig {setMainPlayerIP} (string) : Sets the main player IP. This will also be the coordinator if you have a group of players.
|
|
561
562
|
: setConfig {setPlayerGroupArray} (array) : Sets the array of players beloging to the group, if any. You can also specify the volume variation from the main volume player, to adapt the additional player's perceived volume to the main sonos player volume. For example, if you have a speaker mounted in celiling, having less perceived volume, you can "push" the volume up, to match the whole perceived volume. Just add # after the IP and a number from -100 to 100 to subtract or add volume compared to the main sonos volume. For example, if the sonos main player volume is 40, you can push this celing speaker's volume to further 10, so it'll have the real volume of 50. See below, the example. Even if you have only one additional player, you need to put it into an array.
|