node-red-contrib-tts-ultimate 3.0.7 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,14 @@
1
1
  <script type="text/html" data-template-name="ttsultimate">
2
+ <style>
3
+ .ttsSliderValue {
4
+ display: inline-block;
5
+ min-width: 34px;
6
+ margin-left: 8px;
7
+ text-align: right;
8
+ font-weight: bold;
9
+ vertical-align: middle;
10
+ }
11
+ </style>
2
12
  <div class="form-row">
3
13
  <b>TTS Ultimate configuration</b>&nbsp&nbsp&nbsp&nbsp<span style="color:red"><i class="fa fa-question-circle"></i>&nbsp<a target="_blank" href="https://github.com/Supergiovane/node-red-contrib-tts-ultimate"><u>Help online</u></a></span>
4
14
  <br/>
@@ -17,48 +27,60 @@
17
27
  <div id="allGUI">
18
28
  <div class="form-row">
19
29
  <label for="node-input-voice"><i class="icon-tag"></i> Voice</label>
20
- <select id="node-input-voice">
30
+ <select id="node-input-voice" style="width:60%">
21
31
  <option value='Joanna'>Joanna (en-US)</option>
22
32
  </select>
33
+ <a id="refreshVoices" class="red-ui-button" title="Refresh voices list" style="margin-left:5px"><i class="fa fa-refresh"></i></a>
23
34
  </div>
24
35
 
25
36
  <div id = "divGoogleTTSAudioConfig">
26
37
  <div class="form-row">
27
38
  <label for="node-input-speakingrate"><i class="fa fa-volume-up"></i> Rate</label>
28
- <input type="text" id="node-input-speakingrate" style="width:60px"> (Between 0.25 and 4.0, default 1)
39
+ <input type="range" id="node-input-speakingrate" min="0.25" max="4.0" step="0.05" style="width:50%; vertical-align:middle;">
40
+ <span class="ttsSliderValue" id="val-speakingrate">1</span> <small>(0.25 - 4.0, default 1)</small>
29
41
  </div>
30
42
  <div class="form-row">
31
43
  <label for="node-input-speakingpitch"><i class="fa fa-volume-up"></i> Pitch</label>
32
- <input type="text" id="node-input-speakingpitch" style="width:60px"> (Between -20.0 and 20.0, default 0)
44
+ <input type="range" id="node-input-speakingpitch" min="-20" max="20" step="0.5" style="width:50%; vertical-align:middle;">
45
+ <span class="ttsSliderValue" id="val-speakingpitch">0</span> <small>(-20.0 - 20.0, default 0)</small>
33
46
  </div>
34
47
  </div>
35
48
  <div id="divElevenLabsOptions" hidden>
36
49
  <div class="form-row">
37
50
  <label for="node-input-elevenlabsStability"><i class="fa fa-volume-up"></i> Stability</label>
38
- <input type="text" id="node-input-elevenlabsStability" style="width:60px"> (default 0.5)
51
+ <input type="range" id="node-input-elevenlabsStability" min="0" max="1" step="0.05" style="width:50%; vertical-align:middle;">
52
+ <span class="ttsSliderValue" id="val-elevenlabsStability">0.5</span> <small>(default 0.5)</small>
39
53
  </div>
40
54
  <div class="form-row">
41
55
  <label for="node-input-elevenlabsSimilarity_boost"><i class="fa fa-volume-up"></i> Similarity boost</label>
42
- <input type="text" id="node-input-elevenlabsSimilarity_boost" style="width:60px"> (Default 0.5)
56
+ <input type="range" id="node-input-elevenlabsSimilarity_boost" min="0" max="1" step="0.05" style="width:50%; vertical-align:middle;">
57
+ <span class="ttsSliderValue" id="val-elevenlabsSimilarity_boost">0.5</span> <small>(default 0.5)</small>
43
58
  </div>
44
- <div class="form-row">
59
+ <div class="form-row" id="divElevenLabsStyle">
45
60
  <label for="node-input-elevenlabsStyle"><i class="fa fa-volume-up"></i> Style Exaggeration </label>
46
- <input type="text" id="node-input-elevenlabsStyle" style="width:60px"> (Default 0.0)
61
+ <input type="range" id="node-input-elevenlabsStyle" min="0" max="1" step="0.05" style="width:50%; vertical-align:middle;">
62
+ <span class="ttsSliderValue" id="val-elevenlabsStyle">0.0</span> <small>(default 0.0)</small>
47
63
  </div>
48
64
  <div class="form-row">
65
+ <label for="node-input-elevenlabsSpeed"><i class="fa fa-tachometer"></i> Speed</label>
66
+ <input type="range" id="node-input-elevenlabsSpeed" min="0.7" max="1.2" step="0.05" style="width:50%; vertical-align:middle;">
67
+ <span class="ttsSliderValue" id="val-elevenlabsSpeed">1.0</span> <small>(0.7 - 1.2, default 1.0)</small>
68
+ </div>
69
+ <div class="form-row" id="divElevenLabsSpeakerBoost">
49
70
  <label></label>
50
71
  <input type="checkbox" id="node-input-elevenlabsUse_speaker_boost" style="margin-left: 0px; vertical-align: top; width: auto !important;"> <label style="width:auto !important;"> Speaker boost</label>
51
72
  </div>
52
73
  <div class="form-row">
53
74
  <label for="node-input-elevenlabsModel"><i class="fa fa-cubes"></i> Model</label>
54
- <select id="node-input-elevenlabsModel" style="width:60%">
55
- <option value="">Automatic</option>
75
+ <select id="node-input-elevenlabsModel" style="width:55%">
76
+ <option value="">Automatic (recommended)</option>
56
77
  <option value="eleven_monolingual_v1">Eleven Monolingual v1</option>
57
78
  <option value="eleven_multilingual_v1">Eleven Multilingual v1</option>
58
79
  <option value="eleven_multilingual_v2">Eleven Multilingual v2</option>
59
80
  <option value="eleven_turbo_v2">Eleven Turbo v2</option>
60
81
  <option value="eleven_turbo_v2_5">Eleven Turbo v2.5</option>
61
82
  </select>
83
+ <a id="refreshElevenLabsModels" class="red-ui-button" title="Refresh models list" style="margin-left:5px"><i class="fa fa-refresh"></i></a>
62
84
  </div>
63
85
  <div class="form-row">
64
86
  <label for="node-input-elevenlabsOptimizeLatency"><i class="fa fa-tachometer"></i> Latency preset</label>
@@ -107,9 +129,30 @@
107
129
  <label for="node-input-playertype"><i class="fa fa-play"></i> Player</label>
108
130
  <select id="node-input-playertype">
109
131
  <option value="sonos">Sonos</option>
132
+ <option value="googlecast">Google Cast (Chromecast / Nest)</option>
133
+ <option value="dlna">DLNA / UPnP renderer</option>
110
134
  <option value="noplayer">No player, only output file name.</option>
111
135
  </select>
112
- </div>
136
+ </div>
137
+
138
+ <div id="divCastDlna">
139
+ <div class="form-row">
140
+ <label for="node-input-playervolume"><i class="fa fa-volume-up"></i> Volume</label>
141
+ <input type="text" id="node-input-playervolume" style="width:150px" placeholder="0-100">
142
+ </div>
143
+ <div class="form-row" id="playerDiscoverRow">
144
+ <label for="playerDiscoverList"><i class="fa fa-search"></i> Discover</label>
145
+ <select id="playerDiscoverList" style="width:250px"><option value="">-- press Discover --</option></select>
146
+ <a id="playerDiscoverBtn" class="red-ui-button" style="width:auto;"><i class="fa fa-refresh"></i> Discover</a>
147
+ </div>
148
+ <div class="form-row">
149
+ <label for="node-input-playeripaddress"><i class="fa fa-globe"></i> <span id="playeraddresslabel">Device</span></label>
150
+ <input type="text" id="node-input-playeripaddress" style="width:250px">
151
+ </div>
152
+ <div class="form-row">
153
+ <p id="playeraddresshint" style="margin-left:105px;color:#888;"></p>
154
+ </div>
155
+ </div>
113
156
 
114
157
  <div id="divSonos">
115
158
  <div class="form-row">
@@ -128,7 +171,9 @@
128
171
  <label for="node-input-sonosipaddress"><i class="fa fa-globe"></i> Main Sonos Player</label>
129
172
  <label style="width:200px;" id="node-input-sonosipaddress">Discovering.... wait...</label>
130
173
  </div>
131
-
174
+ </div>
175
+
176
+ <div id="divAdditionalPlayers">
132
177
  <dt><i class="fa fa-code-fork"></i>&nbsp; Additional players</dt>
133
178
  <div class="form-row node-input-rule-container-row">
134
179
  <ol id="node-input-rule-container"></ol>
@@ -175,6 +220,17 @@
175
220
  required: false,
176
221
  type: "text"
177
222
  },
223
+ playeripaddress:
224
+ {
225
+ value: "",
226
+ required: false
227
+ },
228
+ playervolume:
229
+ {
230
+ value: "50",
231
+ required: false,
232
+ type: "text"
233
+ },
178
234
  sonoshailing:
179
235
  {
180
236
  value: "Hailing_Hailing.mp3",
@@ -201,6 +257,7 @@
201
257
  elevenlabsStability: { value: "0.5", required: false },
202
258
  elevenlabsSimilarity_boost: { value: "0.5", required: false },
203
259
  elevenlabsStyle: { value: "0.0", required: false },
260
+ elevenlabsSpeed: { value: "1.0", required: false },
204
261
  elevenlabsUse_speaker_boost: { value: true, required: false },
205
262
  elevenlabsModel: { value: "", required: false },
206
263
  elevenlabsOptimizeLatency: { value: "", required: false },
@@ -230,6 +287,7 @@
230
287
  var node = this;
231
288
  var oNodeServer = RED.nodes.node($("#node-input-config").val()); // Store the config-node
232
289
  if (node.elevenlabsModel !== undefined) $("#node-input-elevenlabsModel").val(node.elevenlabsModel);
290
+ if (node.elevenlabsSpeed !== undefined) $("#node-input-elevenlabsSpeed").val(node.elevenlabsSpeed);
233
291
  if (node.elevenlabsOptimizeLatency !== undefined) $("#node-input-elevenlabsOptimizeLatency").val(node.elevenlabsOptimizeLatency);
234
292
  if (node.elevenlabsOutputFormat !== undefined) $("#node-input-elevenlabsOutputFormat").val(node.elevenlabsOutputFormat);
235
293
  if (node.elevenlabsSeed !== undefined) $("#node-input-elevenlabsSeed").val(node.elevenlabsSeed);
@@ -264,6 +322,7 @@
264
322
  try {
265
323
  oNodeServer = RED.nodes.node($(this).val());
266
324
  getVoices();
325
+ getElevenLabsModels();
267
326
  updateTtsOptionsVisibility();
268
327
  } catch (error) {
269
328
  }
@@ -277,18 +336,91 @@
277
336
  node.playertype = "sonos";
278
337
  $("#node-input-playertype").val("sonos");
279
338
  }
280
- if (node.playertype === "sonos") {
281
- $("#divSonos").show();
282
- } else {
283
- $("#divSonos").hide();
339
+ var playerDiscovered = false; // discover only once per shown section, unless refreshed manually
340
+
341
+ // Discovery endpoint used by the per-row "Additional players" list (Sonos / Cast / DLNA).
342
+ function currentPlayerDiscoveryUrl() {
343
+ var pt = $("#node-input-playertype").val();
344
+ if (pt === "googlecast") return 'ttsultimateDiscoverCast';
345
+ if (pt === "dlna") return 'ttsultimateDiscoverDLNA';
346
+ return 'sonosgetAllGroups';
284
347
  }
285
348
 
286
- $("#node-input-playertype").change(function () {
287
- if ($("#node-input-playertype").val() === "sonos") {
288
- $("#divSonos").show();
289
- } else {
290
- $("#divSonos").hide();
349
+ // Discovery for the MAIN player address field (Google Cast IP or DLNA description URL).
350
+ // All endpoints share the { name, host } shape, so a single code path works.
351
+ function runPlayerDiscovery() {
352
+ var pt = $("#node-input-playertype").val();
353
+ var noun = pt === "googlecast" ? "Cast devices" : "renderers";
354
+ $("#playerDiscoverList").html('<option value="">Discovering... please wait ~4s</option>');
355
+ $.getJSON(currentPlayerDiscoveryUrl(), function (data) {
356
+ $("#playerDiscoverList").empty();
357
+ if (typeof data === "string" || !data || data.length === 0) {
358
+ $("#playerDiscoverList").append($("<option></option>").attr("value", "").text("No " + noun + " found - type manually below"));
359
+ return;
360
+ }
361
+ $("#playerDiscoverList").append($("<option></option>").attr("value", "").text("-- select a discovered device --"));
362
+ data.forEach(function (dev) {
363
+ $("#playerDiscoverList").append($("<option></option>").attr("value", dev.host).text(dev.name + " (" + dev.host + ")"));
364
+ });
365
+ }).fail(function () {
366
+ $("#playerDiscoverList").html('<option value="">Discovery failed - type manually below</option>');
367
+ });
368
+ }
369
+
370
+ // Picking a discovered device fills the main address field.
371
+ $("#playerDiscoverList").change(function () {
372
+ var v = $(this).val();
373
+ if (v) $("#node-input-playeripaddress").val(v);
374
+ });
375
+ $("#playerDiscoverBtn").click(function (e) {
376
+ e.preventDefault();
377
+ runPlayerDiscovery();
378
+ });
379
+
380
+ // Re-render the additional players rows so each row re-discovers using the
381
+ // current player type (Sonos groups vs Cast devices), preserving entered hosts.
382
+ function rebuildAdditionalPlayers() {
383
+ try {
384
+ var items = $("#node-input-rule-container").editableList('items');
385
+ var saved = [];
386
+ items.each(function () {
387
+ var r = $(this);
388
+ saved.push({ host: r.find(".rowRulePlayerHost").val(), hostVolumeAdjust: r.find(".rowRulePlayerHostAdjustVolume").val() });
389
+ });
390
+ $("#node-input-rule-container").editableList('empty');
391
+ saved.forEach(function (rule, idx) {
392
+ $("#node-input-rule-container").editableList('addItem', { r: rule, i: idx });
393
+ });
394
+ } catch (e) { }
395
+ }
396
+
397
+ function refreshPlayerSections() {
398
+ var pt = $("#node-input-playertype").val();
399
+ $("#divSonos").toggle(pt === "sonos");
400
+ $("#divCastDlna").toggle(pt === "googlecast" || pt === "dlna");
401
+ $("#playerDiscoverRow").toggle(pt === "googlecast" || pt === "dlna");
402
+ // Additional players are supported for grouped Sonos and multi-room Google Cast / DLNA.
403
+ $("#divAdditionalPlayers").toggle(pt === "sonos" || pt === "googlecast" || pt === "dlna");
404
+ if (pt === "googlecast") {
405
+ $("#playeraddresslabel").text("Main Chromecast / Nest IP");
406
+ $("#playeraddresshint").text("IP address of the main Google Cast device, e.g. 192.168.1.50");
407
+ } else if (pt === "dlna") {
408
+ $("#playeraddresslabel").text("Renderer description URL");
409
+ $("#playeraddresshint").text("UPnP device description XML URL, e.g. http://192.168.1.50:1400/desc.xml");
291
410
  }
411
+ // Auto-discover the first time a discoverable section is shown.
412
+ if ((pt === "googlecast" || pt === "dlna") && !playerDiscovered) {
413
+ playerDiscovered = true;
414
+ runPlayerDiscovery();
415
+ }
416
+ }
417
+ refreshPlayerSections();
418
+
419
+ $("#node-input-playertype").change(function () {
420
+ playerDiscovered = false; // force re-discovery for the newly selected type
421
+ $("#playerDiscoverList").html('<option value="">-- press Discover --</option>');
422
+ rebuildAdditionalPlayers();
423
+ refreshPlayerSections();
292
424
  });
293
425
  // ###########################
294
426
 
@@ -363,8 +495,93 @@
363
495
  }
364
496
  updateTtsOptionsVisibility();
365
497
  getVoices();
498
+
499
+ // Refresh voices list on demand
500
+ $("#refreshVoices").click(function (e) {
501
+ e.preventDefault();
502
+ if (oNodeServer === null || oNodeServer === undefined || !oNodeServer.ttsservice) {
503
+ RED.notify("Please select a valid TTS service first.", { type: 'warning', timeout: 3000 });
504
+ return;
505
+ }
506
+ getVoices();
507
+ });
366
508
  // #####################################
367
509
 
510
+ // ElevenLabs: read models and their capabilities from the API, then propose them to the user.
511
+ // Options not supported by the selected model are automatically disabled.
512
+ // #####################################
513
+ function applyElevenLabsModelCapabilities() {
514
+ var $opt = $("#node-input-elevenlabsModel option:selected");
515
+ var modelVal = $("#node-input-elevenlabsModel").val();
516
+ // "Automatic" (empty) or models loaded without capability data => keep everything enabled.
517
+ var styleSupported = (modelVal === "" || modelVal === null) ? true : ($opt.data("style") !== false);
518
+ var boostSupported = (modelVal === "" || modelVal === null) ? true : ($opt.data("boost") !== false);
519
+ toggleElevenLabsRow("#divElevenLabsStyle", "#node-input-elevenlabsStyle", styleSupported);
520
+ toggleElevenLabsRow("#divElevenLabsSpeakerBoost", "#node-input-elevenlabsUse_speaker_boost", boostSupported);
521
+ }
522
+ function toggleElevenLabsRow(rowSelector, inputSelector, supported) {
523
+ if (supported) {
524
+ $(rowSelector).css("opacity", "1");
525
+ $(inputSelector).prop("disabled", false);
526
+ } else {
527
+ $(rowSelector).css("opacity", "0.4");
528
+ $(inputSelector).prop("disabled", true);
529
+ }
530
+ }
531
+
532
+ function getElevenLabsModels() {
533
+ if (oNodeServer === null || oNodeServer === undefined || !oNodeServer.ttsservice || oNodeServer.ttsservice.indexOf("elevenlabs") === -1) return;
534
+ $.getJSON("ttsgetmodels" + encodeURIComponent(oNodeServer.id) + "?ttsservice=" + oNodeServer.ttsservice, new Date().getTime(), (data) => {
535
+ if (!Array.isArray(data)) return;
536
+ // Backend signalled an error/missing key: keep the static fallback list and warn the user.
537
+ if (data.length === 1 && data[0].error) {
538
+ RED.notify(data[0].name, { type: 'warning', timeout: 6000 });
539
+ return;
540
+ }
541
+ if (data.length === 0) return;
542
+ var $sel = $("#node-input-elevenlabsModel");
543
+ $sel.find('option').remove().end();
544
+ $sel.append($("<option></option>").attr("value", "").text("Automatic (recommended)"));
545
+ data.forEach(m => {
546
+ var $o = $("<option></option>").attr("value", m.model_id).text(m.name);
547
+ $o.attr("data-style", m.can_use_style === true);
548
+ $o.attr("data-boost", m.can_use_speaker_boost === true);
549
+ if (m.languages && m.languages.length) $o.attr("title", "Languages: " + m.languages.join(", "));
550
+ $sel.append($o);
551
+ });
552
+ $sel.val(node.elevenlabsModel !== undefined ? node.elevenlabsModel : "");
553
+ if ($sel.val() === null) $sel.val(""); // Saved model no longer offered by the API
554
+ applyElevenLabsModelCapabilities();
555
+ });
556
+ }
557
+ getElevenLabsModels();
558
+
559
+ $("#node-input-elevenlabsModel").change(applyElevenLabsModelCapabilities);
560
+
561
+ // Refresh ElevenLabs models list on demand
562
+ $("#refreshElevenLabsModels").click(function (e) {
563
+ e.preventDefault();
564
+ if (oNodeServer === null || oNodeServer === undefined || !oNodeServer.ttsservice || oNodeServer.ttsservice.indexOf("elevenlabs") === -1) {
565
+ RED.notify("Please select an ElevenLabs TTS service first.", { type: 'warning', timeout: 3000 });
566
+ return;
567
+ }
568
+ getElevenLabsModels();
569
+ });
570
+ // #####################################
571
+
572
+ // Voice options sliders: show the current value live next to each slider.
573
+ // #####################################
574
+ function setupVoiceOptionSlider(id) {
575
+ var $input = $("#node-input-" + id);
576
+ var $out = $("#val-" + id);
577
+ if ($input.length === 0 || $out.length === 0) return;
578
+ var update = function () { $out.text($input.val()); };
579
+ $input.on("input change", update);
580
+ update();
581
+ }
582
+ ["speakingrate", "speakingpitch", "elevenlabsStability", "elevenlabsSimilarity_boost", "elevenlabsStyle", "elevenlabsSpeed"].forEach(setupVoiceOptionSlider);
583
+ // #####################################
584
+
368
585
  // Refresh the combo
369
586
  // #####################################
370
587
  node.refreshHailingList = () => {
@@ -504,7 +721,7 @@
504
721
  let oAdjustVolume = $('<select/>', { class: "rowRulePlayerHostAdjustVolume", type: "text", style: "width:200px; margin-left: 5px; text-align: left;" }).appendTo(row);
505
722
  for (let index = -100; index < 100; index += 5) {
506
723
  let sTesto = "";
507
- if (index === 0) sTesto = "Same volume as Main Sonos Player";
724
+ if (index === 0) sTesto = "Same volume as Main Player";
508
725
  if (index < 0) sTesto = "Decrease volume by " + Math.abs(index);
509
726
  if (index > 0) sTesto = "Increase volume by " + index;
510
727
  oAdjustVolume.append($("<option></option>")
@@ -538,8 +755,8 @@
538
755
  resizeRule(container);
539
756
  });
540
757
 
541
- $.getJSON('sonosgetAllGroups', (data) => {
542
- if (typeof data === "string" && data == "ERRORDISCOVERY") { // 10/04/2020 if error in discovery, fallback to manual IP input
758
+ $.getJSON(currentPlayerDiscoveryUrl(), (data) => {
759
+ if ((typeof data === "string" && data == "ERRORDISCOVERY") || !data || data.length === 0) { // 10/04/2020 if error/empty discovery, fallback to manual IP input
543
760
  // Transform the dropdown to a simple input
544
761
  oPlayer.remove();
545
762
  oPlayer = $('<input/>', { class: "rowRulePlayerHost", type: "text", style: "width:200px; margin-left: 5px; text-align: left;" }).appendTo(row);
@@ -609,7 +826,7 @@
609
826
  </script>
610
827
 
611
828
  <script type="text/markdown" data-help-name="ttsultimate">
612
- <p>This node transforms a text into a speech audio that you can hear natively via SONOS speakers, or save it to a file.</p>
829
+ <p>This node transforms a text into a speech audio that you can hear natively via Sonos speakers, Google Cast devices (Chromecast / Google Nest), generic DLNA/UPnP renderers, or save it to a file.</p>
613
830
 
614
831
  **General**
615
832
  |Property|Description|
@@ -618,12 +835,14 @@
618
835
  | Voice | Select your preferred voice. The list depends on the selected TTS engine (Google / ElevenLabs / Voice.ai). Google service without authentication has a limited set of voices. |
619
836
  | Hailing | Before the first TTS message of the message queues, the node will play an "hailing" sound. You can select the hailing, upload your own, or totally disable it. |
620
837
  | Upload hail | It allows you to upload your own hailing file. |
621
- | Player | Select the player. If you select not to use a player, the node will output a msg with an array of files, ready to be played by third party nodes. In case you select No player, only output file name, you'll get a message with an additional property filesArray, containing an array of all mp3 files ready to be played with third party nodes. Please see below the OUTPUT MESSAGES FROM THE NODE section. |
838
+ | Player | Select the player: **Sonos**, **Google Cast** (Chromecast / Google Nest), **DLNA / UPnP renderer**, or **No player**. If you select No player, only output file name, the node will output a msg with an additional property filesArray, containing an array of all mp3 files ready to be played by third party nodes. Please see below the OUTPUT MESSAGES FROM THE NODE section. |
622
839
  | Volume | Set the preferred TTS volume, from "0" to "100" (can be overridden by passing msg.volume = "40"; to the node). |
623
- | Unmute | Unmute the main and the addotional players, then restore the previous mute state once finished. (Can be overridden by passing msg.unmute = true; to the node). |
624
- | Resume | If music was playing prior to TTS messages, the node will try to resume it, but can fail in some cases. Enabla this option to avoid resuming music after TTS message. |
840
+ | Unmute | (Sonos only) Unmute the main and the addotional players, then restore the previous mute state once finished. (Can be overridden by passing msg.unmute = true; to the node). |
841
+ | Resume | (Sonos only) If music was playing prior to TTS messages, the node will try to resume it, but can fail in some cases. Enabla this option to avoid resuming music after TTS message. |
625
842
  | Main Sonos Player | Select your Sonos primary player. (It's strongly suggested to set a fixed IP for this player; you can reserve an IP using the DHCP Reservation function of your router/firewall's DHCP Server). It's possibile to group players, so your announcement can be played on all selected players. For this to happen, you need to select your primary coordinator player. All other players will be then controlled by this coordinator. |
626
- | Additional Players | Here you can add all additional players that will be grouped toghether to the Main Sonos Player coordinator group. You can add a player using the "ADD" button, below the list. For each additional player, you can adjust their volume, based on the Main Sonos Player volume -+100. |
843
+ | Main Chromecast / Nest IP | (Google Cast) IP address of the main Google Cast device (e.g. 192.168.1.50). Press **Discover** to auto-detect the Cast devices on your network and pick one from the list. |
844
+ | Renderer description URL | (DLNA) The full UPnP device description XML URL of the renderer (e.g. http://192.168.1.50:49153/nmrDescription.xml), **not** just the IP. Press **Discover** to auto-detect the DLNA renderers on your network and pick one from the list. |
845
+ | Additional Players | (Sonos / Google Cast / DLNA) Add additional players so your announcement is played on all of them at once. For Sonos they are grouped to the Main Player coordinator; for Google Cast and DLNA the TTS is played on every device in parallel. Use the "ADD" button below the list; for each additional player you can adjust its volume relative to the main volume (-100/+100). |
627
846
 
628
847
  **Google TTS Engines specific options**
629
848
  |Property|Description|
@@ -638,10 +857,23 @@
638
857
  | Stability | Only avaiable with Elevenlabs. Please refer to Elevenlabs.io description. Values from 0 to 1 (for example 0.2, 0.7, etc.) |
639
858
  | Similarity | Only avaiable with Elevenlabs. Please refer to Elevenlabs.io description. Values from 0 to 1 (for example 0.2, 0.7, etc.) |
640
859
  | Style Exaggeration | Only avaiable with Elevenlabs. Please refer to Elevenlabs.io description. Values from 0 to 1 (for example 0.2, 0.7, etc.)|
860
+ | Speed | Only avaiable with Elevenlabs v2. Controls the speaking speed. Values between 0.7 (slower) and 1.2 (faster), default 1.0. |
641
861
  | Speaker boost | Only avaiable with Elevenlabs. Please refer to Elevenlabs.io description. Values from 0 to 1 (for example 0.2, 0.7, etc.) |
862
+ | Model | The model list is read directly from ElevenLabs (press the refresh icon to reload it), so new models appear automatically. Options not supported by the selected model (such as Style Exaggeration or Speaker boost) are automatically disabled, based on the model capabilities reported by ElevenLabs. Leave on "Automatic" to let the node pick a sensible default. |
642
863
 
643
864
  <br/>
644
865
 
866
+ **Players**
867
+
868
+ | Player | Notes |
869
+ |--|--|
870
+ | Sonos | Native control: grouping, volume, mute/unmute and music resume after the announcement. |
871
+ | Google Cast | Chromecast and Google Nest devices, discovered via mDNS. Set the main device IP and, optionally, additional devices for synchronized multi-room playback. |
872
+ | DLNA / UPnP renderer | Generic UPnP MediaRenderers (smart TVs, AV receivers, etc.), discovered via SSDP. Works with renderers that nest a MediaRenderer sub-device too (e.g. Sonos). Requires the full device description XML URL. |
873
+ | No player | Only generates the mp3 file(s) and outputs their paths in filesArray, to be played by third party nodes. |
874
+
875
+ For Google Cast and DLNA the device fetches the generated mp3 over HTTP from Node-RED, so the Node-RED host/port (default 1980) must be reachable from the player and on the same subnet (mDNS/SSDP discovery does not cross subnets/VLANs).
876
+
645
877
  ### Inputs
646
878
 
647
879
  : volume (string) : Set the volume (values between "0" and "100").