I played with this a bit further and added some more customizations. After adding this tile to your dashboard, you can edit the tile and there’s some settings you can tweak.
By default, this version uses the native local JavaScript API that you can enable in Fully Kiosk browser (Advanced Web Settings > Enable JS API). This should result in better stability and the ability to read the current volume level when the tile is first loaded.
You can also disable that in the tile settings and you’ll be presented with options to enter the IP address and passcode for Fully Remote Admin. That approach will use your popup communication by default or you can change the tile setting to 'iframe'
which is invisible and works on Fully but requires allowing mixed content in Chrome.
<!-- Do not edit below -->
<script type="application/json" id="tile-settings">
{
"schema": "0.1.0",
"settings": [
{
"type": "BOOLEAN",
"default": true,
"name": "useJsApi",
"label": "Use JavaScript API?"
},
{
"showIf": ["useJsApi", "==", false],
"label": "Fully IP Address",
"type": "STRING",
"name": "fullyIpAddress"
},
{
"name": "fullyPasscode",
"type": "STRING",
"showIf": ["useJsApi", "==", false],
"label": "Fully Passcode"
},
{
"name": "apiTactic",
"type": "ENUM",
"showIf": ["useJsApi", "==", false],
"values": ["iframe", "popup"],
"default": "popup",
"label": "API Communication"
},
{
"name": "audioChannel",
"label": "Audio Channel",
"type": "NUMBER",
"default": 3
}
],
"name": "Fully Volume Controller",
"dimensions": {"width": 1, "height": 3}
}
</script>
<!-- Do not edit above -->
<link href="https://unpkg.com/nouislider@15.5.1/dist/nouislider.css" rel="stylesheet">
<script src="https://unpkg.com/nouislider@15.5.1/dist/nouislider.min.js"></script>
<script src="https://cdn.sharptools.io/js/custom-tiles.js"></script>
<style>
html, body {
height: 100vh;
margin: 0;
}
.slide-container {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center; /* vertical align */
}
#range {
height: 75vh;
/* center it */
margin-left: auto;
margin-right: auto;
margin-top: 2vh; /* move everything down a tick */
}
.label {
text-align: center;
font-size: min(10vh, 20vw);
line-height: 1;
height: 18vh;
display: flex;
align-items: center; /* vertical center */
justify-content: center; /* horizontal center */
padding: 0 4vw;
}
iframe.api-caller {
display: none;
}
iframe.api-caller {
position: absolute;
border: 0;
width: 100%;
background: rgba(255,0,0,0.5); /* red transparent for debug */
top: 0; left: 0; right: 0; bottom: 0;
}
#fully-js-warning {
background: rgba(223, 0, 0, 0.7);
padding: 1em;
position: fixed;
bottom:0;
left: 0;
right: 0;
max-height: calc(90% - 2em);
overflow-y: auto;
}
#range[disabled] .noUi-handle, #range[disabled] .noUi-connects {
display: none;
}
</style>
<div class="slide-container">
<div id="range"></div>
<div class="label">
<span>
Volume: <span id="volume"></span>
</span>
</div>
</div>
<div id="fully-js-warning" style="display:none;">
The Fully JavaScript API option is selected in the tile settings, but the
<strong>Enable JavaScript Interface</strong> setting in Fully's <strong>Advanced Web Settings</strong>
section is not enabled.
<br>
<br>
Either enable the setting in Fully or disable the JavaScript option in this tile's settings.
</div>
<script>
let slider = document.getElementById("range");
let output = document.getElementById("volume");
let fullyJsWarning = document.getElementById("fully-js-warning");
//defaults, actuals get loaded below
let useJsApi = true;
let fullyIpAddress = "";
let fullyPasscode = "";
let audioStream = 3;
let apiTactic = "popup";
//when the tile settings are ready, let's grab them
stio.ready(function(data){
if(data.settings.useJsApi === false){
useJsApi = data.settings.useJsApi;
}
if(data.settings.fullyIpAddress != null){
let addr = data.settings.fullyIpAddress
//if there's a colon, assume the user entered a port
if(addr.indexOf(":") > -1){
fullyIpAddress = addr;
}
else{
fullyIpAddress = `${addr}:2323`; //add the default port
}
}
if(data.settings.fullyIpAddress != null){
fullyPasscode = data.settings.fullyPasscode
}
if(data.settings.audioStream != null){
audioStream = parseInt(data.settings.audioStream)
}
if(data.settings.apiTactic != null){
apiTactic = data.settings.apiTactic;
}
initVolume();
});
//create the slider
noUiSlider.create(slider, {
start: [50],
connect: 'lower',
range: {
'min': 0,
'max': 100
},
step: 10,
orientation: 'vertical',
direction: 'rtl', //0 at bottom
// Show a scale with the slider
pips: {
mode: 'steps',
stepped: true,
density: 1
}
});
//anytime the value updates while dragging, update the label
range.noUiSlider.on('update', function (values, handle) {
let value = parseInt(values[handle]); //store the value in a simplified variable name
output.innerHTML = value; //update the label
});
//only make the API call when the value is actually changed (drag handle released)
range.noUiSlider.on('change', function(values, handle) {
let value = parseInt(values[handle]);
setVolume(value);
})
function setVolume(level){
if(useJsApi){
void fully.setAudioVolume(level, audioStream)
}
else{
let url = getCommandUrl('setAudioVolume', {level: level, stream: audioStream})
console.debug('URL is', url);
loadUrl(url);
}
}
function getCommandUrl(command, params){
let url = `http://${fullyIpAddress}/?cmd=${command}&password=${fullyPasscode}`;
//layer in additional parameters
Object.keys(params).forEach(key =>{
url += `&${key}=${params[key]}`;
})
return url;
}
function loadUrl(url){
if(apiTactic === "iframe"){
//create an iframe with our class and url, then add it to the document
let iframe = document.createElement('iframe');
iframe.classList.add("api-caller")
iframe.src = url;
document.body.appendChild(iframe);
//then remove it in a second
setTimeout(()=>{
document.body.removeChild(iframe);
}, 1000)
}
else{
//make the API call by popping up the window
var popupwin = window.open(url,'Volume','width=15,height=15,left=5,top=3');
setTimeout(function() { popupwin.close();}, 1000);
}
}
function initVolume(){
//if the tile is configured to use the JS API
if(useJsApi){
//if the API isn't enabled, display the warning
if(window.fully == null){
slider.setAttribute("disabled", true); //disable the slider;
fullyJsWarning.style.display = "block";
}
else{
//make sure the slider is enabled and the warning is not displayed
slider.removeAttribute('disabled');
fullyJsWarning.style.display = "none";
//try to get the volume
let volume = fully.getAudioVolume(audioStream)
slider.noUiSlider.set(volume);
}
}
//get the value from the slider
output.innerHTML = parseInt(slider.noUiSlider.get());
}
</script>