@icebear
Installation
HEOS-Adapter installieren und starten (
https://github.com/withstu/ioBroker.heos)
HTML-State "0_userdata.0.heos.browse_result_html" als Zeichenkette anlegen (alternativ eigener State, dann in der Konfiguration anpassen)
dieses Script im Admin unter Skripte als neues Script hinzufügen.
dieses Script starten
in der ioBroker-vis ein basic-string(unescaped)-Widget in einem View einfügen, den HTML-State zuweisen
View ausführen
datapuunkt.JPG
-------------------
KONFIGURATION
------------------- */
// Ziel-State, in welchem das interpretierte HTML abgelegt wird. Der State muss vorher manuell angelegt worden sein
const STATE_BROWSE_RESULT_HTML = '0_userdata.0.heos.browse_result_html';
// play-Befehle werden immer via broadcast an alle Player gesendet, ist dieses nicht gewünscht, so können in dem hier
// anzugegebenen State die PIDs der Player angegeben werden, welche die play-Befehle erhalten sollen. Lässt sich
// z.B. gut mit dem Widget jqui-container-HTML-View "Setze Objekt-ID" nutzen.
// Im State sind die Player-PIDs durch , getrennt angebbar, der State muss vorher manuell angelegt worden sein
const STATE_BROWSE_RESULT_PLAYERS = '0_userdata.0.heos.browse_result_player';
// Font und Rahmen-Basisfarbe, bei dunklem Hintergrund durch '255,255,255' ersetzen
const HEOS_FONT_COLOR = '0,0,0';
// wenn es sich bei der HEOS Instanz nicht um die 0. handelt, bitte anpassen
const HEOS_INSTANCE = '0';
// sollen die Navigationsergebnisse beim Starten des Scripts bereits untersucht werden?
const BUILD_HTML_AT_SCRIPTSTART = true;
-------------------
Script
------------------- */
const STATE_BROWSE_RESULT = `heos.${HEOS_INSTANCE}.sources.browse_result`;
const STATE_COMMAND = `heos.${HEOS_INSTANCE}.command`;
const STATE_PLAYERS = `heos.${HEOS_INSTANCE}.players`;
function buildHTML( data ) {
// sind players angegeben, so wird das globale command-State durch die Player-command-States ersetzt und
// "player/" wird aus dem command selbst entfernt
function buildOnClick(cmd,cmdState,players,doPatch) {
let cmds = [cmdState];
let onClick = '';
// wenn nur ein player angesprochen werden soll, dann command-state auf den des players setzen
// und command anpassen 'player\' entfernen
if (doPatch && players!='') {
cmds = players.split(',');
for (let j=0; j<cmds.length; j++ ) cmds[j] = `${STATE_PLAYERS}.${cmds[j]}.command`;
cmd = cmd.substr(cmd.indexOf('/',cmd)+1,9999);
}
for (let j=0; j<cmds.length; j++ ) onClick += `servConn.setState('${cmds[j]}','${cmd}');`;
return onClick;
}
let html = "Sorry, no data!"
if(data){
let command = getState(STATE_COMMAND).val;
let playerIDs = '';
if (existsState(STATE_BROWSE_RESULT_PLAYERS)) playerIDs = getState(STATE_BROWSE_RESULT_PLAYERS).val;
// Playernamen ermitteln
let playerNames = 'Alle';
if (playerIDs!='') {
let players = playerIDs.split(',');
playerNames = '';
for (let i=0; i<players.length; i++) {
if (existsState(`${STATE_PLAYERS}.${players[i]}.name`))
playerNames += (playerNames==''?'':', ') + getState(`${STATE_PLAYERS}.${players[i]}.name`).val;
}
}
// subtitle soll Anzahl enthalten
let subtitle = '';
if (data.parameter.count) subtitle += `${data.parameter.count} Einträge`;
else if (data.payload) subtitle += `${data.payload.length} Einträge`;
if (data.parameter.range) subtitle += ` (${data.parameter.range.replace(',','-')})`;
if (subtitle!='') subtitle = `<div class="heos-head-subtext">${subtitle}</div>`;
// globale Funktionen wie Übersicht/Back/... in der Kopfzeile darstellen
let controls={'load_next':{'sym':'›', 'onClick':'' },
'load_prev':{'sym':'‹', 'onClick':'' },
'play_all': {'sym':'▸', 'onClick':'' },
'back': {'sym':'▴', 'onClick':'' },
'sources': {'sym':'=', 'onClick':'' },
'getHTML': function(key) {
return `<div class="heos-head-btn ${controls[key].onClick!=''?'heos-clickable':''}" ${controls[key].onClick}>
${controls[key].sym}
</div>`;
}
};
for (let i = 0; i < data.payload.length; i++) {
let payload = data.payload[i];
if (payload.type=="control" && controls.hasOwnProperty(payload.name)) {
let onClick = '';
for (let key in payload.commands)
onClick = buildOnClick(payload.commands[key],STATE_COMMAND,playerIDs,key=='play');
controls[payload.name].onClick = `onClick="${onClick}"`;
}
}
// Zeilen des Listview aufbauen
let rowsHTML='';
for (let i = 0; i < data.payload.length; i++) {
let payload = data.payload[i];
if (payload.type!="control") {
for (let key in payload.commands) {
let sym = '';
let onClick = buildOnClick(payload.commands[key],STATE_COMMAND,playerIDs,key=='play');
switch (key) {
case 'play': sym = '▸'; break;
case 'browse': break;
}
rowsHTML += `<div class="heos-row" onClick="${onClick}">
<div class="heos-img">${payload.image_url!=''?`<img class="heos-img-fit" src="${payload.image_url}" width="auto" height="100%" alt="">`:'<div class="heos-img-empty"></div>'}</div>
<div class="heos-text">${payload.name}</div>
<div class="heos-btn">${sym}</div>
</div>`;
}
}
}
html = `
<style type="text/css">
:root {--heos-font-color:rgba(${HEOS_FONT_COLOR},.9); --heos-border-color:rgba(${HEOS_FONT_COLOR},.05); }
.heos-root { position:absolute; height:100%; width:100%; color:var(--heos-font-color); }
.heos-head { height:96px; border-bottom:4px solid var(--heos-border-color); }
.heos-head-players { font-size:0.7em; opacity:0.66; text-align:right; }
.heos-head-img { width:72px; flex:none; height:72px; overflow:hidden; }
.heos-head-title { width:100%; overflow:hidden; display:flex; align-items:flex-start; }
.heos-head-text {text-align:left;padding-top:0.25em;padding-left:4px;font-size:1.1em;}
.heos-head-subtext { font-size:0.66em; opacity:0.66; }
.heos-head-btns {width:108px; flex:none; display:flex; flex-wrap:wrap; margin-left:auto;}
.heos-head-btns-row {width:100%; display:flex;}
.heos-head-btn {width:36px; height:36px; opacity:.2;text-align:center;font-size:2em; }
.heos-body { overflow-y:auto; height:calc(100% - 72px); }
.heos-img { width:64px; height:100%; overflow:hidden; }
.heos-img-fit { width:100%; height:100%; object-fit:contain; }
.heos-img-empty:after { content:'\\266A'; font-size:2.5em; opacity:0.2; display:flex; justify-content:center;}
.heos-row { width:100%;height:56px;border-bottom:1px solid var(--heos-border-color);padding-top:4px;padding-bottom:4px; display:flex; align-items: center;}
.heos-text {width:calc(100% - 112px);text-align:left;padding-left:4px;white-space: nowrap;overflow:hidden;}
.heos-btn {width:48px;border-radius: 2px;text-align:center;font-size:2em; border-left:1px solid var(--heos-border-color); }
.heos-clickable {opacity:1; }
.heos-row:hover,
.heos-clickable:hover {background-blend-mode:multiply;background-image: linear-gradient(var(--heos-border-color),var(--heos-border-color)) !important; }
[class*="_heos-"] {outline:1px solid rgba(255,0,0,.2); outline-offset:-1px;}
</style>
<div class="heos-root">
<div class="heos-head">
<div class="heos-head-title">
<div class="heos-head-img">${data.image_url!=''?`<img class="heos-img-fit" src="${data.image_url}" alt="">`:'<div class="heos-img-empty"></div>'}</div>
<div class="heos-head-text">
<div style="height:56px; overflow:hidden;">
${data.name == "sources"?"HEOS Quellen-Übersicht":data.name}
</div>
${subtitle}
</div>
<div class="heos-head-btns">
<div class="heos-head-btns-row"><div class="heos-head-btn"></div>${controls.getHTML('sources')+controls.getHTML('back')}</div>
<div class="heos-head-btns-row">${controls.getHTML('load_prev')+controls.getHTML('play_all')+controls.getHTML('load_next')}</div>
</div>
</div>
<div class="heos-head-players">Player: ${playerNames}</div>
</div>
<div class="heos-body">
${rowsHTML}
</div>
</div>`;
}
setState(STATE_BROWSE_RESULT_HTML, html);
};
// browse_result Überwachung
on({id: STATE_BROWSE_RESULT, change: 'any'}, function (obj) {
let data = JSON.parse(obj.state.val);
if(data) buildHTML(data);
});
on({id: STATE_BROWSE_RESULT_PLAYERS, change: 'any'}, function (obj) {
let data = JSON.parse(getState(STATE_BROWSE_RESULT).val);
if(data) buildHTML(data);
});
// Sriptstart
if (BUILD_HTML_AT_SCRIPTSTART)
buildHTML( JSON.parse(getState(STATE_BROWSE_RESULT).val) );
fehlermeldung.JPG