node-red-contrib-cast
Version:
NodeRED nodes to for Chromecast and Google Home
266 lines (238 loc) • 14.4 kB
HTML
<script type="text/html" data-template-name="cast-to-client">
<div class="form-row">
<label for="node-input-ip"><i class="fa fa-google"></i> IP</label>
<input type="text" id="node-input-ip" placeholder="0.0.0.0" style="width:60%;" >
<a id="node-config-lookup-ipaddress" class="btn"><i id="node-config-lookup-ipaddress-icon" class="fa fa-search"></i></a>
</div>
<div class="form-row">
<label for="node-input-port">Port</label>
<input type="text" id="node-input-port" placeholder="(optional) 8009">
</div>
<hr>
<div class="form-row">
<label for="node-input-url"><i class="fa fa-file-audio-o"></i> Media Url</label>
<input type="text" id="node-input-url" placeholder="(optional) Media Url">
</div>
<div class="form-row">
<label for="node-input-contentType"><i class="fa fa-file-o"></i> Media Type</label>
<input type="text" id="node-input-contentType" placeholder="audio/basic">
</div>
<div class="form-row">
<label for="node-input-imageUrl"><i class="fa fa-picture-o"></i> Image Url</label>
<input type="text" id="node-input-imageUrl" placeholder="(optional) Image Url (metadata)">
</div>
<hr>
<div class="form-row">
<label for="node-input-message"><i class="fa fa-commenting-o"></i> Message</label>
<input type="text" id="node-input-message" placeholder="(optional) Text to speach">
</div>
<div class="form-row">
<label for="node-input-language"><i class="fa fa-language"></i> language</label>
<input type="text" id="node-input-language" placeholder="en">
</div>
<hr>
<div class="form-row">
<label for="node-input-volume"><i class="fa fa-volume-up"></i> Volume</label>
<input type="text" id="node-input-volume" class="cast-volume" placeholder="(optional) 0-100">
</div>
<div class="form-tips"><b>Tip:</b> You can also define all properties as part of the incoming message. This would be used priories than the settings here.<br />
<code>msg.url</code> or <code>msg.payload.url</code><br />
<code>msg.urlList</code> / <code>msg.payload.urlList</code><br />
<code>msg.contentType</code> / <code>msg.payload.contentType</code><br />
<code>msg.imageUrl</code> / <code>msg.payload.imageUrl</code><br />
<code>msg.message</code> / <code>msg.payload.message</code><br />
<code>msg.language</code> / <code>msg.payload.language</code><br />
<code>msg.ip</code> / <code>msg.payload.ip</code> the IP address of the device to cast <br />
<code>msg.port</code> / <code>msg.payload.port</code> the port of the device (if not given default <code>8009</code> will be used)<br />
<code>msg.volume</code> / <code>msg.payload.volume</code> (optional) a number 0-100 where the volume should be set <br />
</div>
<hr>
<!-- By convention, most nodes have a 'name' property. The following div -->
<!-- provides the necessary field. Should always be the last option -->
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name of this Event">
</div>
</script>
<!-- Next, some simple help text is provided for the node. -->
<script type="text/html" data-help-name="cast-to-client">
<h3>About</h3>
<p>Plays a Media file on a chromecast or a Home device</p>
<h3>Inputs</h3>
<dl class="message-properties">
<dt>payload
<span class="property-type">string | object</span>
</dt>
<dd> the payload of the message to publish. </dd>
<dt>ip <span class="property-type">ip-address</span></dt>
<dd> the IP address of the device to cast the media. Could also be defined in the configuration of the node.</dd>
<dt class="optional">port <span class="property-type">number</span></dt>
<dd> the port of the device (if not given default <code>8009</code> will be used). Could also be defined in the configuration of the node.</dd>
<dt>url <span class="property-type">string</span></dt>
<dd> url to a media file which should be cast to the cast device. For a chromecast this should be a media or a video file. For a Google Home device without a display this could only be an audio file.</dd>
<dt class="optional">imageUrl <span class="property-type">string</span></dt>
<dd> url to a image file which represents the artwork for the url which should be cast to the cast device. For a chromecast this should be an image file. For a Google Home device without a display this is irrelevant.</dd>
<dt>contentType <span class="property-type">string</span></dt>
<dd> the content type of the file in the url. This property is required if a url is given. Could also be defined in the configuration of the node.</dd>
<dt class="optional">message <span class="property-type">string</span></dt>
<dd> a text which should be send to the google tts engine to convert to a mp3 file.</dd>
<dt class="optional">language <span class="property-type">string</span></dt>
<dd> the language which should be used for converting the message to the media file.</dd>
<dt class="optional">volume <span class="property-type">number 0-100</span></dt>
<dd> will set the device volume to this level. (if left blank, the volume will be unchanged.)</dd>
<dt class="optional">lowerVolumeLimit <span class="property-type">number 0-100</span></dt>
<dd> will set the volume to this value, if the current volume is below this value.</dd>
<dt class="optional">upperVolumeLimit <span class="property-type">number 0-100</span></dt>
<dd> will set the volume to this value, if the current volume is above this limit.</dd>
<dt class="optional">muted <span class="property-type">boolean</span></dt>
<dd> the volume should be muted if set to false, otherwise the volume will be unmuted.</dd>
<dt class="optional">status <span class="property-type">boolean</span></dt>
<dd> if this property is set to true, **only** the status will be given as output and nothing else will be done.</dd>
</dl>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Standard output
<dl class="message-properties">
<dt>payload <span class="property-type">string</span></dt>
<dd>the given status result by the cast2-client library.</dd>
</dl>
</li>
</ol>
<h3>Details</h3>
<p>there are 3 different use cases:</p>
<ol>
<li>play a media file <br />
For this the url and the content type needs to be specified. Thus can
be done in the configuration or passed as a message property <code>msg.url</code> and <code>msg.contentType</code> or give a payload object with the two properties <code>msg.payload.url</code> and <code>msg.payload.contentType</code><br /></li>
<li>play a queue of media file <br />
For this the urlList and the content type needs to be specified. Thus can
be done in the configuration or passed as a message property <code>msg.urlList</code> and <code>msg.contentType</code> or give a payload object with the two properties <code>msg.payload.urlList</code> and <code>msg.payload.contentType</code><br /></li>
<li>speak a given message, converted by google tts <br />
For this the message needs to be specified. Thus can
be done in the configuration or pass as a message property <code>msg.message</code> or give a payload object with the property <code>msg.payload.message</code></li>
<li>get the state of the cast device <br />
For this no url or message has to be specified.</li>
</ol>
<p>If there is a combination of the message and the url, first the media file given by URL will
be streamed and then the message.</p>
<p>If the payload is a string and no contentType is defined, the payload
will be used as a message which should be spoken.</p>
<h3>References</h3>
<ul>
<li><a href="https://github.com/Hypnos3/node-red-contrib-cast">GitHub</a> - the nodes github repository</li>
</ul>
</script>
<!-- Finally, the node type is registered along with all of its properties -->
<!-- The example below shows a small subset of the properties that can be set-->
<script type="text/javascript">
RED.nodes.registerType('cast-to-client', {
category: 'function',
defaults: {
name: {
value: '',
required: false
},
url: {
value: null,
required: false
},
contentType: {
value: '',
required: false
},
message: {
value: null,
required: false
},
language: {
value: 'en',
required: false,
validate: function (v) {
return RED.validators.regex(/^[a-zA-Z]{2,4}(-[a-zA-Z0-9]{2,5}(-[a-zA-Z0-9]{2,5})?(-(Standard|Wavenet)-[A-Z])?)?$/)(
v) || (v === '');
// we are allow a little more characters as currently used in language codes to be save for future changes
}
},
ip: {
value: '',
required: false,
validate: function (v) {
// IPv4
// hostname
// IPv6
return (v === '') ||
RED.validators.regex(/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/)(v) ||
RED.validators.regex(/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/)(v) ||
RED.validators.regex(/^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/)(v);
}
},
port: {
value: '',
required: false,
validate: function (v) {
return RED.validators.number()(v) || (v === '');
// RED.validators.regex(/^(\s*|\d+)$/) does not work, because can be empty
}
},
volume: {
value: null,
required: false,
validate: function (v) {
if (!v) {
return true; // empty
} else if (/^\d+$/.test(v)) {
return (parseInt(v) >= 0 && parseInt(v) <= 100);
}
return false;
}
}
},
inputs: 1, // set the number of inputs - only 0 or 1
outputs: 1, // set the number of outputs - 0 to n
icon: 'default.png', // saved in icons/myicon.png - myicon.png
color: '#FFFF00',
label: function () {
if (this.name) {
return this.name;
}
return ('Cast '+ this.ip + ((this.port) ? ':'+ this.port : ''));
},
labelStyle: function () {
return this.name ? 'node_label_italic' : '';
},
paletteLabel: 'Cast',
oneditprepare: function () {
$('.cast-volume').spinner({
max: 100,
min: 0
});
$('#node-config-lookup-ipaddress').click(() => {
$('#node-config-lookup-ipaddress-icon').removeClass('fa-search');
$('#node-config-lookup-ipaddress-icon').addClass('spinner');
$('#node-config-lookup-ipaddress').addClass('disabled');
$.getJSON('ipaddresses',(data) => {
$('#node-config-lookup-ipaddress-icon').addClass('fa-search');
$('#node-config-lookup-ipaddress-icon').removeClass('spinner');
$('#node-config-lookup-ipaddress').removeClass('disabled');
const items = [];
const unique = data.filter( (value, index, self) => {
return self.findIndex(i => (i.ip === value.ip && i.port === value.port)) === index;
}); // returns ['a', 1, 2, '1']
$.each(unique, (i, element) => {
items.push(element.label + ' (' + element.ip + ':' + element.port + ')');
});
$('#node-input-ip').autocomplete({
source:items,
minLength:0,
close: function( _event, _ui ) {
const str = this.value;
const elements = str.substring(str.lastIndexOf('(') + 1, str.lastIndexOf(')')).split(':');
$('#node-input-ip').val(elements[0]);
$('#node-input-port').val(elements[1]);
$('#node-input-ip').autocomplete( 'destroy' );
}
}).autocomplete('search','');
});
});
}
});</script>