1
0
mirror of https://github.com/SuperBFG7/ympd synced 2025-04-10 04:36:46 +00:00

Merge pull request #77 from jcorporation/devel

Merge devel into master for 4.7.0 release
This commit is contained in:
Jürgen Mang 2018-12-03 21:13:43 +01:00 committed by GitHub
commit 2991a85d86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1478 additions and 481 deletions

View File

@ -3,8 +3,8 @@ cmake_minimum_required(VERSION 2.6)
project (mympd C)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/")
set(CPACK_PACKAGE_VERSION_MAJOR "4")
set(CPACK_PACKAGE_VERSION_MINOR "6")
set(CPACK_PACKAGE_VERSION_PATCH "1")
set(CPACK_PACKAGE_VERSION_MINOR "7")
set(CPACK_PACKAGE_VERSION_PATCH "0")
if(CMAKE_BUILD_TYPE MATCHES RELEASE)
set(ASSETS_PATH "/usr/share/${PROJECT_NAME}/htdocs")
@ -32,6 +32,7 @@ set(SOURCES
src/mympd.c
src/mpd_client.c
src/list.c
src/validate.c
dist/src/mongoose/mongoose.c
dist/src/frozen/frozen.c
dist/src/inih/ini.c

View File

@ -4,7 +4,7 @@
pkgname=mympd
_pkgname=myMPD
pkgver=4.6.1
pkgver=4.7.0
pkgrel=1
pkgdesc="myMPD is a standalone and mobile friendly web mpdclient."
arch=('x86_64' 'armv7h' 'aarch64')

View File

@ -20,10 +20,10 @@ This fork provides a reworked ui based on Bootstrap 4, a modernized backend and
- Browse filesystem and playlists
- Queue management
- Playlist management
- Advanced search
- Advanced search (requires mpd >= 0.21.x and libmpdclient >= 2.17)
- Jukebox mode (add's random songs / albums from database or playlists to queue)
- Smart playlists and saved searches
- Play statistics and song voting (uses mpd stickers)
- Play statistics and song voting (requires mpd stickers)
- Local coverart support (Albums and Streams)
- HTTP stream support
- Local playback of mpd http stream (html5 audio api)

View File

@ -4,7 +4,7 @@
# (c) 2018 Juergen Mang <mail@jcgames.de>
Name: myMPD
Version: 4.6.1
Version: 4.7.0
Release: 0
License: GPL-2.0
Group: Productivity/Multimedia/Sound/Players

View File

@ -1,5 +1,12 @@
#myMPD config file
#Loglevel
#0 = quiet
#1 = default
#2 = verbose
#3 = debug
loglevel = 1
#Connection to mpd
mpdhost = 127.0.0.1
mpdport = 6600

4
debian/changelog vendored
View File

@ -1,5 +1,5 @@
mympd (4.6.1-1) stable; urgency=medium
mympd (4.7.0-1) stable; urgency=medium
* Release from master
-- Juergen Mang <mail@jcgames.de> Mon, 19 Nov 2018 19:11:29 +0000
-- Juergen Mang <mail@jcgames.de> Mon, 19 Nov 2018 22:11:43 +0000

View File

@ -1 +1 @@
html{position:relative;min-height:100%}body{margin-bottom:60px;padding-top:50px;padding-bottom:50px;background-color:#888}main{padding-top:20px}footer{position:absolute;bottom:0}button{overflow:hidden}#BrowseBreadrumb{overflow:auto;white-space:nowrap}#BrowseBreadcrumb>li>a{cursor:pointer}@media only screen and (max-width:576px){.header-logo{display:none!important}}.clickable{cursor:pointer}[data-col=Pos],[data-col=Type],[data-col=Track],[data-col=Action]{width:30px}small{color:#aaa}.card{min-height:300px}.cardFooterPlayback{padding:0}.album-cover{background-size:cover;background-image:url("/assets/coverimage-loading.png");border:1px solid black;border-radius:5px;overflow:hidden;width:240px;height:240px;background-color:#eee;float:left;margin-right:20px;margin-bottom:20px}.album-desc{min-width:240px;float:left}.hide{display:none!important}.pull-right{float:right!important}.card-toolbar{margin-bottom:10px}.card-toolbar>div,.card-toolbar>form{margin-bottom:5px}@font-face{font-family:'Material Icons';font-style:normal;font-weight:400;src:url(/assets/MaterialIcons-Regular.woff2) format('woff2'),url(/assets/MaterialIcons-Regular.woff) format('woff'),url(/assets/MaterialIcons-Regular.ttf) format('truetype')}.material-icons{font-family:'Material Icons';font-weight:normal;font-style:normal;font-size:18px;display:inline-block;line-height:1;text-transform:none;letter-spacing:normal;word-wrap:normal;white-space:nowrap;direction:ltr;vertical-align:top;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;font-feature-settings:'liga'}.material-icons-left{font-size:1rem;margin-left:-1em;vertical-align:middle}.material-icons-small{font-size:16px}.material-icons-small-left{font-size:1rem;margin-left:-1em}.color-darkgrey,.color-darkgrey:hover{color:#6c757d!important}#btn-outputs-block>button{margin-bottom:10px}#btn-outputs-block>button:last-child{margin-bottom:0}.card-body{overflow-x:hidden}.progressBarPlay{font-size:22px}#counter{cursor:text}#volumeBar{margin-top:2px;width:160px}.title-icon{float:left;margin-right:5px;font-size:1.8rem}.header-logo{font-size:2rem;float:left;margin-right:5px}.letters>button{width:28px;height:28px}.col-md{max-width:250px;min-width:250px}a.card-img-left{overflow:hidden;display:block;width:250px;height:250px;border-radius:5px;background-size:cover;background-image:url(/assets/coverimage-loading.png);margin-bottom:5px;cursor:pointer}button.active{color:#fff;background-color:#28a745!important;border-color:#28a745!important}button.active-fg-green{color:#28a745!important}button.active-fg-red{color:#bd2130!important}div#alertBox{position:fixed;top:50px;right:10px;width:80%;max-width:400px;z-index:1000;opacity:0;visibility:visible;transition:opacity .5s ease-in}div.alertBoxActive{opacity:1!important;visibility:visible!important;transition:opacity .5s ease-in}.popover-content{padding-top:4px;padding-bottom:4px}.opacity05{opacity:.5}caption{caption-side:top;font-size:120%;font-weight:bold;color:black}.dragover>td{border-top:2px solid #28a745}.dragover-th{border-left:2px solid #28a745}[draggable]{-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;user-select:none;-khtml-user-drag:element;-webkit-user-drag:element;cursor:pointer}@keyframes changewidth{0%{margin-left:-20px}to{margin-left:100%}}#updateDBprogress{width:20px}.updateDBprogressAnimate{animation-duration:2s;animation-name:changewidth;animation-iteration-count:infinite}.modal-body{overflow-x:hidden}.modal-body .album-cover{float:none}#BrowseDatabaseAlbumListCaption{margin-left:15px;margin-right:15px;width:100%}#BrowseDatabaseAlbumListCaption h2{display:inline}#BrowseDatabaseAlbumListCaption small{padding-top:.8rem}#menu-dbupdate{padding-left:1rem}div.key{border:1px solid #bbb;background-color:#eee;border-radius:2px;width:20px;heigth:20px;text-align:center}
html{position:relative;min-height:100%}body{margin-bottom:60px;padding-top:50px;padding-bottom:50px;background-color:#888}main{padding-top:20px}footer{position:absolute;bottom:0}button{overflow:hidden}#BrowseBreadrumb{overflow:auto;white-space:nowrap}#BrowseBreadcrumb>li>a{cursor:pointer}@media only screen and (max-width:576px){.header-logo{display:none!important}}.clickable{cursor:pointer}[data-col=Pos],[data-col=Type],[data-col=Track],[data-col=Action]{width:30px}small{color:#aaa}.card{min-height:300px}.cardFooterPlayback{padding:0}.album-cover{background-size:cover;background-image:url("/assets/coverimage-loading.png");border:1px solid black;border-radius:5px;overflow:hidden;width:240px;height:240px;background-color:#eee;float:left;margin-right:20px;margin-bottom:20px}.album-desc{min-width:240px;float:left}.hide{display:none!important}.pull-right{float:right!important}.card-toolbar{margin-bottom:10px}.card-toolbar>div,.card-toolbar>form{margin-bottom:5px}@font-face{font-family:'Material Icons';font-style:normal;font-weight:400;src:url(/assets/MaterialIcons-Regular.woff2) format('woff2'),url(/assets/MaterialIcons-Regular.woff) format('woff'),url(/assets/MaterialIcons-Regular.ttf) format('truetype')}.material-icons{font-family:'Material Icons';font-weight:normal;font-style:normal;font-size:18px;display:inline-block;line-height:1;text-transform:none;letter-spacing:normal;word-wrap:normal;white-space:nowrap;direction:ltr;vertical-align:top;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;font-feature-settings:'liga'}.material-icons-left{font-size:1rem;margin-left:-1em;vertical-align:middle}.material-icons-small{font-size:16px}.material-icons-small-left{font-size:1rem;margin-left:-1em}.color-darkgrey,.color-darkgrey:hover{color:#6c757d!important}#btn-outputs-block>button{margin-bottom:10px}#btn-outputs-block>button:last-child{margin-bottom:0}.card-body{overflow-x:hidden}.progressBarPlay{font-size:22px}#counter{cursor:text}#volumeBar{margin-top:2px;width:160px}.title-icon{float:left;margin-right:5px;font-size:1.8rem}.header-logo{font-size:2rem;float:left;margin-right:5px}.letters>button{width:28px;height:28px}.col-md{max-width:250px;min-width:250px}a.card-img-left{overflow:hidden;display:block;width:250px;height:250px;border-radius:5px;background-size:cover;background-image:url(/assets/coverimage-loading.png);margin-bottom:5px;cursor:pointer}button.active{color:#fff;background-color:#28a745!important;border-color:#28a745!important}button.active-fg-green{color:#28a745!important}button.active-fg-red{color:#bd2130!important}div#alertBox{position:fixed;top:50px;right:10px;width:80%;max-width:400px;z-index:1000;opacity:0;visibility:visible;transition:opacity .5s ease-in}div.alertBoxActive{opacity:1!important;visibility:visible!important;transition:opacity .5s ease-in}.popover-content{padding-top:4px;padding-bottom:4px}.opacity05{opacity:.5}caption{caption-side:top;font-size:120%;font-weight:bold;color:black}.dragover>td{border-top:2px solid #28a745}.dragover-th{border-left:2px solid #28a745}[draggable]{-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;user-select:none;-khtml-user-drag:element;-webkit-user-drag:element;cursor:pointer}@keyframes changewidth{0%{margin-left:-20px}to{margin-left:100%}}#updateDBprogress{width:20px}.updateDBprogressAnimate{animation-duration:2s;animation-name:changewidth;animation-iteration-count:infinite}.modal-body{overflow-x:hidden}.modal-body .album-cover{float:none}#BrowseDatabaseAlbumListCaption{margin-left:15px;margin-right:15px;width:100%}#BrowseDatabaseAlbumListCaption h2{display:inline}#BrowseDatabaseAlbumListCaption small{padding-top:.8rem}#menu-dbupdate{padding-left:1rem}div.key{border:1px solid #bbb;background-color:#eee;border-radius:2px;width:20px;heigth:20px;text-align:center}ol#searchCrumb{padding:.5rem}.nodropdown::after{content:none}

File diff suppressed because one or more lines are too long

View File

@ -3,14 +3,14 @@ $jscomp.initSymbol=function(){$jscomp.initSymbol=function(){};$jscomp.global.Sym
$jscomp.initSymbolIterator=function(){$jscomp.initSymbol();var a=$jscomp.global.Symbol.iterator;a||(a=$jscomp.global.Symbol.iterator=$jscomp.global.Symbol("iterator"));"function"!=typeof Array.prototype[a]&&$jscomp.defineProperty(Array.prototype,a,{configurable:!0,writable:!0,value:function(){return $jscomp.arrayIterator(this)}});$jscomp.initSymbolIterator=function(){}};$jscomp.arrayIterator=function(a){var b=0;return $jscomp.iteratorPrototype(function(){return b<a.length?{done:!1,value:a[b++]}:{done:!0}})};
$jscomp.iteratorPrototype=function(a){$jscomp.initSymbolIterator();a={next:a};a[$jscomp.global.Symbol.iterator]=function(){return this};return a};$jscomp.makeIterator=function(a){$jscomp.initSymbolIterator();var b=a[Symbol.iterator];return b?b.call(a):$jscomp.arrayIterator(a)};$jscomp.arrayFromIterator=function(a){for(var b,c=[];!(b=a.next()).done;)c.push(b.value);return c};$jscomp.arrayFromIterable=function(a){return a instanceof Array?a:$jscomp.arrayFromIterator($jscomp.makeIterator(a))};
$jscomp.polyfill=function(a,b,c,d){if(b){c=$jscomp.global;a=a.split(".");for(d=0;d<a.length-1;d++){var e=a[d];e in c||(c[e]={});c=c[e]}a=a[a.length-1];d=c[a];b=b(d);b!=d&&null!=b&&$jscomp.defineProperty(c,a,{configurable:!0,writable:!0,value:b})}};$jscomp.polyfill("Object.is",function(a){return a?a:function(a,c){return a===c?0!==a||1/a===1/c:a!==a&&c!==c}},"es6","es3");
$jscomp.polyfill("Array.prototype.includes",function(a){return a?a:function(a,c){var b=this;b instanceof String&&(b=String(b));var e=b.length;c=c||0;for(0>c&&(c=Math.max(c+e,0));c<e;c++){var f=b[c];if(f===a||Object.is(f,a))return!0}return!1}},"es7","es3");
$jscomp.polyfill("Array.prototype.includes",function(a){return a?a:function(a,c){var d=this;d instanceof String&&(d=String(d));var b=d.length;c=c||0;for(0>c&&(c=Math.max(c+b,0));c<b;c++){var f=d[c];if(f===a||Object.is(f,a))return!0}return!1}},"es7","es3");
$jscomp.checkStringArgs=function(a,b,c){if(null==a)throw new TypeError("The 'this' value for String.prototype."+c+" must not be null or undefined");if(b instanceof RegExp)throw new TypeError("First argument to String.prototype."+c+" must not be a regular expression");return a+""};$jscomp.polyfill("String.prototype.includes",function(a){return a?a:function(a,c){return-1!==$jscomp.checkStringArgs(this,a,"includes").indexOf(a,c||0)}},"es6","es3");
$jscomp.polyfill("String.prototype.repeat",function(a){return a?a:function(a){var b=$jscomp.checkStringArgs(this,null,"repeat");if(0>a||1342177279<a)throw new RangeError("Invalid count value");a|=0;for(var d="";a;)if(a&1&&(d+=b),a>>>=1)b+=b;return d}},"es6","es3");
var socket,lastSong="",lastSongObj={},lastState,currentSong={},playstate="",settings={},alertTimeout,progressTimer,deferredPrompt,dragEl,playlistEl,app={apps:{Playback:{state:"0/-/",scrollPos:0},Queue:{active:"Current",tabs:{Current:{state:"0/any/",scrollPos:0},LastPlayed:{state:"0/any/",scrollPos:0}}},Browse:{active:"Database",tabs:{Filesystem:{state:"0/-/",scrollPos:0},Playlists:{active:"All",views:{All:{state:"0/-/",scrollPos:0},Detail:{state:"0/-/",scrollPos:0}}},Database:{active:"AlbumArtist",
views:{}}}},Search:{state:"0/any/",scrollPos:0}},current:{app:"Playback",tab:void 0,view:void 0,page:0,filter:"",search:"",scrollPos:0},last:{app:void 0,tab:void 0,view:void 0,filter:"",search:"",scrollPos:0}},domCache={};domCache.navbarBottomBtns=document.getElementById("navbar-bottom").getElementsByTagName("div");domCache.navbarBottomBtnsLen=domCache.navbarBottomBtns.length;domCache.cardHeaderBrowse=document.getElementById("cardHeaderBrowse").getElementsByTagName("a");
domCache.cardHeaderBrowseLen=domCache.cardHeaderBrowse.length;domCache.cardHeaderQueue=document.getElementById("cardHeaderQueue").getElementsByTagName("a");domCache.cardHeaderQueueLen=domCache.cardHeaderQueue.length;domCache.counter=document.getElementById("counter");domCache.volumePrct=document.getElementById("volumePrct");domCache.volumeControl=document.getElementById("volumeControl");domCache.volumeMenu=document.getElementById("volumeMenu");domCache.btnsPlay=document.getElementsByClassName("btnPlay");
domCache.btnsPlayLen=domCache.btnsPlay.length;domCache.btnPrev=document.getElementById("btnPrev");domCache.btnNext=document.getElementById("btnNext");domCache.progressBar=document.getElementById("progressBar");domCache.volumeBar=document.getElementById("volumeBar");domCache.outputs=document.getElementById("outputs");domCache.btnAdd=document.getElementById("nav-add2homescreen");domCache.currentCover=document.getElementById("currentCover");domCache.currentTitle=document.getElementById("currentTitle");
domCache.btnVoteUp=document.getElementById("btnVoteUp");domCache.btnVoteDown=document.getElementById("btnVoteDown");domCache.badgeQueueItems=document.getElementById("badgeQueueItems");
domCache.btnVoteUp=document.getElementById("btnVoteUp");domCache.btnVoteDown=document.getElementById("btnVoteDown");domCache.badgeQueueItems=document.getElementById("badgeQueueItems");domCache.searchstr=document.getElementById("searchstr");domCache.searchCrumb=document.getElementById("searchCrumb");
var modalConnectionError=new Modal(document.getElementById("modalConnectionError"),{backdrop:"static",keyboard:!1}),modalSettings=new Modal(document.getElementById("modalSettings")),modalAbout=new Modal(document.getElementById("modalAbout")),modalSavequeue=new Modal(document.getElementById("modalSaveQueue")),modalSongDetails=new Modal(document.getElementById("modalSongDetails")),modalAddToPlaylist=new Modal(document.getElementById("modalAddToPlaylist")),modalRenamePlaylist=new Modal(document.getElementById("modalRenamePlaylist")),
modalUpdateDB=new Modal(document.getElementById("modalUpdateDB")),modalSaveSmartPlaylist=new Modal(document.getElementById("modalSaveSmartPlaylist")),modalDeletePlaylist=new Modal(document.getElementById("modalDeletePlaylist")),modalHelp=new Modal(document.getElementById("modalHelp")),dropdownMainMenu,dropdownVolumeMenu=new Dropdown(document.getElementById("volumeMenu")),collapseDBupdate=new Collapse(document.getElementById("navDBupdate"));
function appPrepare(a){if(app.current.app!=app.last.app||app.current.tab!=app.last.tab||app.current.view!=app.last.view){for(var b=0;b<domCache.navbarBottomBtnsLen;b++)domCache.navbarBottomBtns[b].classList.remove("active");document.getElementById("cardPlayback").classList.add("hide");document.getElementById("cardQueue").classList.add("hide");document.getElementById("cardBrowse").classList.add("hide");document.getElementById("cardSearch").classList.add("hide");for(b=0;b<domCache.cardHeaderBrowseLen;b++)domCache.cardHeaderBrowse[b].classList.remove("active");
@ -25,10 +25,13 @@ data:{offset:app.current.page,filter:app.current.filter}},parsePlaylists),doSetF
data:{offset:app.current.page,filter:app.current.filter,search:app.current.search,tag:app.current.view}},parseListDBtags),doSetFilterLetter("BrowseDatabaseFilter")):(sendAPI({cmd:"MPD_API_DATABASE_TAG_LIST",data:{offset:app.current.page,filter:app.current.filter,tag:app.current.view}},parseListDBtags),doSetFilterLetter("BrowseDatabaseFilter"),selectTag("BrowseDatabaseByTagDropdown","btnBrowseDatabaseByTag",app.current.view));else if("Browse"==app.current.app&&"Filesystem"==app.current.tab){sendAPI({cmd:"MPD_API_DATABASE_FILESYSTEM_LIST",
data:{offset:app.current.page,path:app.current.search?app.current.search:"/",filter:app.current.filter}},parseFilesystem);app.current.search?(document.getElementById("BrowseFilesystemAddAllSongs").removeAttribute("disabled"),document.getElementById("BrowseFilesystemAddAllSongsBtn").removeAttribute("disabled")):(document.getElementById("BrowseFilesystemAddAllSongs").setAttribute("disabled","disabled"),document.getElementById("BrowseFilesystemAddAllSongsBtn").setAttribute("disabled","disabled"));var b=
'<li class="breadcrumb-item"><a data-uri="">root</a></li>',c=app.current.search.split("/"),d=c.length,e="";for(a=0;a<d;a++){if(d-1==a){b+='<li class="breadcrumb-item active">'+c[a]+"</li>";break}e+=c[a];b+='<li class="breadcrumb-item"><a data-uri="'+e+'">'+c[a]+"</a></li>";e+="/"}a=document.getElementById("BrowseBreadcrumb");a.innerHTML=b;b=a.getElementsByTagName("a");c=b.length;for(a=0;a<c;a++)b[a].addEventListener("click",function(){appGoto("Browse","Filesystem",void 0,"0/"+app.current.filter+"/"+
this.getAttribute("data-uri"))},!1);doSetFilterLetter("BrowseFilesystemFilter")}else"Search"==app.current.app?(a=document.getElementById("searchstr"),a.focus(),""==a.value&&""!=app.current.search&&(a.value=app.current.search),app.last.app!=app.current.app&&""!=app.current.search&&(a=settings["cols"+app.current.app].length,a--,document.getElementById("SearchList").getElementsByTagName("tbody")[0].innerHTML='<tr><td><span class="material-icons">search</span></td><td colspan="'+a+'">Searching...</td></tr>'),
2<=app.current.search.length?sendAPI({cmd:"MPD_API_DATABASE_SEARCH",data:{plist:"",offset:app.current.page,filter:app.current.filter,searchstr:app.current.search}},parseSearch):(document.getElementById("SearchList").getElementsByTagName("tbody")[0].innerHTML="",document.getElementById("searchAddAllSongs").setAttribute("disabled","disabled"),document.getElementById("searchAddAllSongsBtn").setAttribute("disabled","disabled"),document.getElementById("panel-heading-search").innerText="",document.getElementById("SearchList").classList.remove("opacity05"),
setPagination(0)),selectTag("searchtags","searchtagsdesc",app.current.filter)):appGoto("Playback");app.last.app=app.current.app;app.last.tab=app.current.tab;app.last.view=app.current.view}else appGoto("Playback")}
function appInit(){getSettings();sendAPI({cmd:"MPD_API_PLAYER_STATE"},parseState);webSocketConnect();domCache.volumeBar.value=0;document.getElementById("btnChVolumeDown").addEventListener("click",function(a){a.stopPropagation()},!1);document.getElementById("btnChVolumeUp").addEventListener("click",function(a){a.stopPropagation()},!1);domCache.volumeBar.addEventListener("click",function(a){a.stopPropagation()},!1);domCache.volumeBar.addEventListener("change",function(a){sendAPI({cmd:"MPD_API_PLAYER_VOLUME_SET",
this.getAttribute("data-uri"))},!1);doSetFilterLetter("BrowseFilesystemFilter")}else if("Search"==app.current.app){domCache.searchstr.focus();if(settings.featAdvsearch){b="";c=app.current.search.substring(1,app.current.search.length-1).split(" AND ");for(a=0;a<c.length-1;a++)d=c[a].substring(1,c[a].length-1),b+='<button data-filter="'+encodeURI(d)+'" class="btn btn-light mr-2">'+d+'<span class="ml-2 badge badge-secondary">&times</span></button>';domCache.searchCrumb.innerHTML=b;""==domCache.searchstr.value&&
1<=c.length&&(a=c[c.length-1].substring(1,c[c.length-1].length-1),b=a.substring(a.indexOf("'")+1,a.length-1),domCache.searchstr.value!=b&&(domCache.searchCrumb.innerHTML+='<button data-filter="'+encodeURI(a)+'" class="btn btn-light mr-2">'+a+'<span href="#" class="ml-2 badge badge-secondary">&times;</span></button>'),a=a.substring(a.indexOf(" ")+1),a=a.substring(0,a.indexOf(" ")),""==a&&(a="contains"),document.getElementById("searchMatch").value=a)}else""==domCache.searchstr.value&&""!=app.current.search&&
(domCache.searchstr.value=app.current.search);app.last.app!=app.current.app&&""!=app.current.search&&(a=settings["cols"+app.current.app].length,a--,document.getElementById("SearchList").getElementsByTagName("tbody")[0].innerHTML='<tr><td><span class="material-icons">search</span></td><td colspan="'+a+'">Searching...</td></tr>');2<=domCache.searchstr.value.length||0<domCache.searchCrumb.children.length?settings.featAdvsearch?(a=document.getElementById("SearchList").getAttribute("data-sort"),b=!1,""==
a?(a=settings.tags.includes("Title")?"Title":"Filename",document.getElementById("SearchList").setAttribute("data-sort",a)):0==a.indexOf("-")&&(b=!0,a=a.substring(1)),sendAPI({cmd:"MPD_API_DATABASE_SEARCH_ADV",data:{plist:"",offset:app.current.page,sort:a,sortdesc:b,expression:app.current.search}},parseSearch)):sendAPI({cmd:"MPD_API_DATABASE_SEARCH",data:{plist:"",offset:app.current.page,filter:app.current.filter,searchstr:app.current.search}},parseSearch):(document.getElementById("SearchList").getElementsByTagName("tbody")[0].innerHTML=
"",document.getElementById("searchAddAllSongs").setAttribute("disabled","disabled"),document.getElementById("searchAddAllSongsBtn").setAttribute("disabled","disabled"),document.getElementById("panel-heading-search").innerText="",document.getElementById("cardFooterSearch").innerText="",document.getElementById("SearchList").classList.remove("opacity05"),setPagination(0,0));selectTag("searchtags","searchtagsdesc",app.current.filter)}else appGoto("Playback");app.last.app=app.current.app;app.last.tab=
app.current.tab;app.last.view=app.current.view}else appGoto("Playback")}
function appInit(){webSocketConnect();getSettings();sendAPI({cmd:"MPD_API_PLAYER_STATE"},parseState);domCache.volumeBar.value=0;document.getElementById("btnChVolumeDown").addEventListener("click",function(a){a.stopPropagation()},!1);document.getElementById("btnChVolumeUp").addEventListener("click",function(a){a.stopPropagation()},!1);domCache.volumeBar.addEventListener("click",function(a){a.stopPropagation()},!1);domCache.volumeBar.addEventListener("change",function(a){sendAPI({cmd:"MPD_API_PLAYER_VOLUME_SET",
data:{volume:domCache.volumeBar.value}})},!1);domCache.progressBar.value=0;domCache.progressBar.addEventListener("change",function(a){currentSong&&0<=currentSong.currentSongId&&sendAPI({cmd:"MPD_API_PLAYER_SEEK",data:{songid:currentSong.currentSongId,seek:Math.ceil(domCache.progressBar.value/100*currentSong.totalTime)}})},!1);document.getElementById("navDBupdate").addEventListener("click",function(a){a.stopPropagation();a.preventDefault();a=this.getElementsByTagName("span")[0];a.innerText="keyboard_arrow_right"==
a.innerText?"keyboard_arrow_down":"keyboard_arrow_right"},!1);document.getElementById("volumeMenu").parentNode.addEventListener("show.bs.dropdown",function(){sendAPI({cmd:"MPD_API_PLAYER_OUTPUT_LIST"},parseOutputs)});document.getElementById("modalAbout").addEventListener("shown.bs.modal",function(){sendAPI({cmd:"MPD_API_DATABASE_STATS"},parseStats)});document.getElementById("modalAddToPlaylist").addEventListener("shown.bs.modal",function(){document.getElementById("addStreamFrm").classList.contains("hide")?
document.getElementById("addToPlaylistPlaylist").focus():document.getElementById("streamUrl").focus()});document.getElementById("modalHelp").addEventListener("show.bs.modal",function(){var a="",b;for(b in keymap)if(void 0==keymap[b].req||1==settings[keymap[b].req])a+='<tr><td><div class="key'+(keymap[b].key&&1<keymap[b].key.length?" material-icons material-icons-small":"")+'">'+(void 0!=keymap[b].key?keymap[b].key:b)+"</div></td><td>"+keymap[b].desc+"</td></tr>";document.getElementById("tbodyShortcuts").innerHTML=
@ -44,13 +47,18 @@ a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("BrowsePlay
a.target.parentNode.getAttribute("data-name")):"A"==a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("BrowseDatabaseTagList").addEventListener("click",function(a){"TD"==a.target.nodeName&&appGoto("Browse","Database",app.current.view,"0/-/"+a.target.parentNode.getAttribute("data-uri"))},!1);document.getElementById("SearchList").addEventListener("click",function(a){"TD"==a.target.nodeName?appendQueue("song",decodeURI(a.target.parentNode.getAttribute("data-uri")),a.target.parentNode.getAttribute("data-name")):
"A"==a.target.nodeName&&showMenu(a.target,a)},!1);document.getElementById("BrowseFilesystemAddAllSongsDropdown").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&("Add all to queue"==a.target.innerText?addAllFromBrowse():"Add all to playlist"==a.target.innerText&&showAddToPlaylist(app.current.search))},!1);document.getElementById("searchAddAllSongsDropdown").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&("Add all to queue"==a.target.innerText?addAllFromSearchPlist("queue"):
"Add all to playlist"==a.target.innerText?showAddToPlaylist("SEARCH"):"Save as smart playlist"==a.target.innerText&&saveSearchAsSmartPlaylist())},!1);document.getElementById("BrowseDatabaseAddAllSongsDropdown").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&("Add all to queue"==a.target.innerText?addAllFromBrowseDatabasePlist("queue"):"Add all to playlist"==a.target.innerText&&showAddToPlaylist("DATABASE"))},!1);document.getElementById("searchtags").addEventListener("click",function(a){"BUTTON"==
a.target.nodeName&&appGoto(app.current.app,app.current.tab,app.current.view,"0/"+a.target.getAttribute("data-tag")+"/"+app.current.search)},!1);document.getElementById("searchqueuestr").addEventListener("keyup",function(a){"Escape"==a.key?this.blur():appGoto(app.current.app,app.current.tab,app.current.view,"0/"+app.current.filter+"/"+this.value)},!1);document.getElementById("searchqueuetags").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&appGoto(app.current.app,app.current.tab,
app.current.view,app.current.page+"/"+a.target.getAttribute("data-tag")+"/"+app.current.search)},!1);a="QueueCurrentColsDropdown BrowseFilesystemColsDropdown SearchColsDropdown BrowsePlaylistsDetailColsDropdown BrowseDatabaseColsDropdown PlaybackColsDropdown QueueLastPlayedColsDropdown".split(" ");for(c=0;c<a.length;c++)document.getElementById(a[c]).addEventListener("click",function(a){"INPUT"==a.target.nodeName&&a.stopPropagation()},!1);document.getElementById("search").addEventListener("submit",
function(){return!1},!1);document.getElementById("searchqueue").addEventListener("submit",function(){return!1},!1);document.getElementById("searchstr").addEventListener("keyup",function(a){"Escape"==a.key?this.blur():appGoto("Search",void 0,void 0,"0/"+app.current.filter+"/"+this.value)},!1);document.getElementById("BrowseDatabaseByTagDropdown").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&appGoto(app.current.app,app.current.tab,a.target.getAttribute("data-tag"),"0/"+app.current.filter+
"/"+app.current.search)},!1);document.getElementsByTagName("body")[0].addEventListener("click",function(a){hideMenu()},!1);dragAndDropTable("QueueCurrentList");dragAndDropTable("BrowsePlaylistsDetailList");dragAndDropTableHeader("QueueCurrent");dragAndDropTableHeader("QueueLastPlayed");dragAndDropTableHeader("Search");dragAndDropTableHeader("BrowseFilesystem");dragAndDropTableHeader("BrowsePlaylistsDetail");window.addEventListener("hashchange",appRoute,!1);window.addEventListener("focus",function(){sendAPI({cmd:"MPD_API_PLAYER_STATE"},
parseState)},!1);document.addEventListener("keydown",function(a){if("INPUT"!=a.target.tagName&&"SELECT"!=a.target.tagName&&!a.ctrlKey&&!a.altKey){var b=keymap[a.key];b&&"function"===typeof window[b.cmd]&&(void 0==keymap[a.key].req||1==settings[keymap[a.key].req])&&parseCmd(a,b)}},!1);"serviceWorker"in navigator&&"https"==document.URL.substring(0,5)&&window.addEventListener("load",function(){navigator.serviceWorker.register("/sw.min.js",{scope:"/"}).then(function(a){console.log("ServiceWorker registration successful with scope: ",
a.scope);a.update()},function(a){console.log("ServiceWorker registration failed: ",a)})});window.addEventListener("beforeinstallprompt",function(a){a.preventDefault();deferredPrompt=a});window.addEventListener("beforeinstallprompt",function(a){a.preventDefault();deferredPrompt=a;domCache.btnAdd.classList.remove("hide")});domCache.btnAdd.addEventListener("click",function(a){domCache.btnAdd.classList.add("hide");deferredPrompt.prompt();deferredPrompt.userChoice.then(function(a){"accepted"===a.outcome?
console.log("User accepted the A2HS prompt"):console.log("User dismissed the A2HS prompt");deferredPrompt=null})});window.addEventListener("appinstalled",function(a){console.log("myMPD installed as app")})}function parseCmd(a,b){a.preventDefault();a=b;"string"==typeof b&&(a=JSON.parse(b));if("function"===typeof window[a.cmd])switch(a.cmd){case "sendAPI":sendAPI.apply(null,$jscomp.arrayFromIterable(a.options));break;default:window[a.cmd].apply(null,$jscomp.arrayFromIterable(a.options))}}
a.target.nodeName&&(app.current.filter=a.target.getAttribute("data-tag"),search(domCache.searchstr.value))},!1);document.getElementById("searchqueuestr").addEventListener("keyup",function(a){"Escape"==a.key?this.blur():appGoto(app.current.app,app.current.tab,app.current.view,"0/"+app.current.filter+"/"+this.value)},!1);document.getElementById("searchqueuetags").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&appGoto(app.current.app,app.current.tab,app.current.view,app.current.page+
"/"+a.target.getAttribute("data-tag")+"/"+app.current.search)},!1);a="QueueCurrentColsDropdown BrowseFilesystemColsDropdown SearchColsDropdown BrowsePlaylistsDetailColsDropdown BrowseDatabaseColsDropdown PlaybackColsDropdown QueueLastPlayedColsDropdown".split(" ");for(c=0;c<a.length;c++)document.getElementById(a[c]).addEventListener("click",function(a){"INPUT"==a.target.nodeName&&a.stopPropagation()},!1);document.getElementById("search").addEventListener("submit",function(){return!1},!1);document.getElementById("searchqueue").addEventListener("submit",
function(){return!1},!1);domCache.searchstr.addEventListener("keyup",function(a){if("Escape"==a.key)this.blur();else if("Enter"==a.key&&settings.featAdvsearch)if(""!=this.value){a=document.getElementById("searchMatch");var b=document.createElement("button");b.classList.add("btn","btn-light","mr-2");b.setAttribute("data-filter",encodeURI(app.current.filter+" "+a.options[a.selectedIndex].value+" '"+this.value+"'"));b.innerHTML=app.current.filter+" "+a.options[a.selectedIndex].value+" '"+this.value+
'\'<span class="ml-2 badge badge-secondary">&times;</span>';this.value="";domCache.searchCrumb.appendChild(b)}else search(this.value);else search(this.value)},!1);domCache.searchCrumb.addEventListener("click",function(a){a.preventDefault();a.stopPropagation();if("SPAN"==a.target.nodeName)a.target.parentNode.remove(),search("");else if("BUTTON"==a.target.nodeName){var b=decodeURI(a.target.getAttribute("data-filter"));domCache.searchstr.value=b.substring(b.indexOf("'")+1,b.length-1);var c=b.substring(0,
b.indexOf(" "));selectTag("searchtags","searchtagsdesc",c);b=b.substring(b.indexOf(" ")+1);b=b.substring(0,b.indexOf(" "));document.getElementById("searchMatch").value=b;a.target.remove();search(domCache.searchstr.value)}},!1);document.getElementById("searchMatch").addEventListener("change",function(a){search(domCache.searchstr.value)},!1);document.getElementById("SearchList").getElementsByTagName("tr")[0].addEventListener("click",function(a){if(settings.featAdvsearch&&"TH"==a.target.nodeName){var b=
a.target.getAttribute("data-col");if("Duration"!=b){var c=document.getElementById("SearchList").getAttribute("data-sort"),d=!0;if(c==b||c=="-"+b)0==c.indexOf("-")?(d=!0,c.substring(1)):d=!1;0==d?(c="-"+b,d=!0):(d=!1,c=b);for(var h=document.getElementById("SearchList").getElementsByClassName("sort-dir"),l=0;l<h.length;l++)h[l].remove();document.getElementById("SearchList").setAttribute("data-sort",c);a.target.innerHTML=b+'<span class="sort-dir material-icons pull-right">'+(1==d?"arrow_drop_up":"arrow_drop_down")+
"</span>";appRoute()}}},!1);document.getElementById("BrowseDatabaseByTagDropdown").addEventListener("click",function(a){"BUTTON"==a.target.nodeName&&appGoto(app.current.app,app.current.tab,a.target.getAttribute("data-tag"),"0/"+app.current.filter+"/"+app.current.search)},!1);document.getElementsByTagName("body")[0].addEventListener("click",function(a){hideMenu()},!1);dragAndDropTable("QueueCurrentList");dragAndDropTable("BrowsePlaylistsDetailList");dragAndDropTableHeader("QueueCurrent");dragAndDropTableHeader("QueueLastPlayed");
dragAndDropTableHeader("Search");dragAndDropTableHeader("BrowseFilesystem");dragAndDropTableHeader("BrowsePlaylistsDetail");window.addEventListener("hashchange",appRoute,!1);window.addEventListener("focus",function(){sendAPI({cmd:"MPD_API_PLAYER_STATE"},parseState)},!1);document.addEventListener("keydown",function(a){if("INPUT"!=a.target.tagName&&"SELECT"!=a.target.tagName&&!a.ctrlKey&&!a.altKey){var b=keymap[a.key];b&&"function"===typeof window[b.cmd]&&(void 0==keymap[a.key].req||1==settings[keymap[a.key].req])&&
parseCmd(a,b)}},!1);"serviceWorker"in navigator&&"https"==document.URL.substring(0,5)&&window.addEventListener("load",function(){navigator.serviceWorker.register("/sw.min.js",{scope:"/"}).then(function(a){console.log("ServiceWorker registration successful with scope: ",a.scope);a.update()},function(a){console.log("ServiceWorker registration failed: ",a)})});window.addEventListener("beforeinstallprompt",function(a){a.preventDefault();deferredPrompt=a});window.addEventListener("beforeinstallprompt",
function(a){a.preventDefault();deferredPrompt=a;domCache.btnAdd.classList.remove("hide")});domCache.btnAdd.addEventListener("click",function(a){domCache.btnAdd.classList.add("hide");deferredPrompt.prompt();deferredPrompt.userChoice.then(function(a){"accepted"===a.outcome?console.log("User accepted the A2HS prompt"):console.log("User dismissed the A2HS prompt");deferredPrompt=null})});window.addEventListener("appinstalled",function(a){console.log("myMPD installed as app")})}
function parseCmd(a,b){a.preventDefault();a=b;"string"==typeof b&&(a=JSON.parse(b));if("function"===typeof window[a.cmd])switch(a.cmd){case "sendAPI":sendAPI.apply(null,$jscomp.arrayFromIterable(a.options));break;default:window[a.cmd].apply(null,$jscomp.arrayFromIterable(a.options))}}
function search(a){if(settings.featAdvsearch){for(var b="(",c=domCache.searchCrumb.children,d=0;d<c.length;d++)b+="("+decodeURI(c[d].getAttribute("data-filter"))+")",""!=a&&(b+=" AND ");""!=a?(c=document.getElementById("searchMatch"),b+="("+app.current.filter+" "+c.options[c.selectedIndex].value+" '"+a+"'))"):b+=")";2>=b.length&&(b="");appGoto("Search",void 0,void 0,"0/"+app.current.filter+"/"+encodeURI(b))}else appGoto("Search",void 0,void 0,"0/"+app.current.filter+"/"+a)}
function dragAndDropTable(a){var b=document.getElementById(a).getElementsByTagName("tbody")[0];b.addEventListener("dragstart",function(a){"TR"==a.target.nodeName&&(a.target.classList.add("opacity05"),a.dataTransfer.setDragImage(a.target,0,0),a.dataTransfer.effectAllowed="move",a.dataTransfer.setData("Text",a.target.getAttribute("id")),dragEl=a.target.cloneNode(!0))},!1);b.addEventListener("dragleave",function(a){a.preventDefault();if("TR"==dragEl.nodeName){var b=a.target;"TD"==a.target.nodeName&&
(b=a.target.parentNode);"TR"==b.nodeName&&b.classList.remove("dragover")}},!1);b.addEventListener("dragover",function(a){a.preventDefault();if("TR"==dragEl.nodeName){for(var c=b.getElementsByClassName("dragover"),e=c.length,f=0;f<e;f++)c[f].classList.remove("dragover");c=a.target;"TD"==a.target.nodeName&&(c=a.target.parentNode);"TR"==c.nodeName&&c.classList.add("dragover");a.dataTransfer.dropEffect="move"}},!1);b.addEventListener("dragend",function(a){a.preventDefault();if("TR"==dragEl.nodeName){for(var c=
b.getElementsByClassName("dragover"),e=c.length,f=0;f<e;f++)c[f].classList.remove("dragover");document.getElementById(a.dataTransfer.getData("Text"))&&document.getElementById(a.dataTransfer.getData("Text")).classList.remove("opacity05")}},!1);b.addEventListener("drop",function(c){c.stopPropagation();c.preventDefault();if("TR"==dragEl.nodeName){var d=c.target;"TD"==c.target.nodeName&&(d=c.target.parentNode);var e=document.getElementById(c.dataTransfer.getData("Text")).getAttribute("data-songpos"),
@ -65,11 +73,11 @@ a,"","","danger");break;case "update_queue":"Queue"===app.current.app&&getQueue(
app.current.tab&&"All"==app.current.view?sendAPI({cmd:"MPD_API_PLAYLIST_LIST",data:{offset:app.current.page,filter:app.current.filter}},parsePlaylists):"Browse"==app.current.app&&"Playlists"==app.current.tab&&"Detail"==app.current.view&&sendAPI({cmd:"MPD_API_PLAYLIST_CONTENT_LIST",data:{offset:app.current.page,filter:app.current.filter,uri:app.current.search}},parsePlaylists);break;case "error":showNotification(c.data,"","","danger")}}},socket.onclose=function(){console.log("disconnected");modalConnectionError.show();
setTimeout(function(){console.log("reconnect");webSocketConnect()},3E3)}}catch(b){alert("Error: "+b)}}function getWsUrl(){var a=window.location.hostname,b=window.location.protocol,c=window.location.port;a=("https:"==b?"wss://":"ws://")+a+(""!=c?":"+c:"")+"/ws";return document.getElementById("wsUrl").innerText=a}
function parseStats(a){document.getElementById("mpdstats_artists").innerText=a.data.artists;document.getElementById("mpdstats_albums").innerText=a.data.albums;document.getElementById("mpdstats_songs").innerText=a.data.songs;document.getElementById("mpdstats_dbPlaytime").innerText=beautifyDuration(a.data.dbPlaytime);document.getElementById("mpdstats_playtime").innerText=beautifyDuration(a.data.playtime);document.getElementById("mpdstats_uptime").innerText=beautifyDuration(a.data.uptime);var b=new Date(1E3*
a.data.dbUpdated);document.getElementById("mpdstats_dbUpdated").innerText=b.toUTCString();document.getElementById("mympdVersion").innerText=a.data.mympdVersion;document.getElementById("mpdVersion").innerText=a.data.mpdVersion}function toggleBtn(a,b){if(a=document.getElementById(a))void 0==b&&(b=a.classList.contains("active")?0:1),1==b||1==b?a.classList.add("active"):a.classList.remove("active")}
a.data.dbUpdated);document.getElementById("mpdstats_dbUpdated").innerText=b.toUTCString();document.getElementById("mympdVersion").innerText=a.data.mympdVersion;document.getElementById("mpdVersion").innerText=a.data.mpdVersion;document.getElementById("libmpdclientVersion").innerText=a.data.libmpdclientVersion}function toggleBtn(a,b){if(a=document.getElementById(a))void 0==b&&(b=a.classList.contains("active")?0:1),1==b||1==b?a.classList.add("active"):a.classList.remove("active")}
function filterCols(a){var b=settings.tags.slice();0==settings.featTags&&b.push("Title");b.push("Duration");"colsQueueCurrent"==a||"colsBrowsePlaylistsDetail"==a||"colsQueueLastPlayed"==a?b.push("Pos"):"colsBrowseFilesystem"==a&&b.push("Type");"colsQueueLastPlayed"==a&&b.push("LastPlayed");for(var c=[],d=0;d<settings[a].length;d++)b.includes(settings[a][d])&&c.push(settings[a][d]);settings[a]=c}
function parseSettings(a){settings=a.data;toggleBtn("btnRandom",settings.random);toggleBtn("btnConsume",settings.consume);toggleBtn("btnSingle",settings.single);toggleBtn("btnRepeat",settings.repeat);void 0!=settings.crossfade?(document.getElementById("inputCrossfade").removeAttribute("disabled"),document.getElementById("inputCrossfade").value=settings.crossfade):document.getElementById("inputCrossfade").setAttribute("disabled","disabled");void 0!=settings.mixrampdb?(document.getElementById("inputMixrampdb").removeAttribute("disabled"),
document.getElementById("inputMixrampdb").value=settings.mixrampdb):document.getElementById("inputMixrampdb").setAttribute("disabled","disabled");void 0!=settings.mixrampdelay?(document.getElementById("inputMixrampdelay").removeAttribute("disabled"),document.getElementById("inputMixrampdelay").value=settings.mixrampdelay):document.getElementById("inputMixrampdelay").setAttribute("disabled","disabled");document.getElementById("selectReplaygain").value=settings.replaygain;a=document.getElementById("btnnotifyWeb");
notificationsSupported()?settings.notificationWeb?(toggleBtn("btnnotifyWeb",settings.notificationWeb),Notification.requestPermission(function(a){"permission"in Notification||(Notification.permission=a);"granted"===a?toggleBtn("btnnotifyWeb",1):(toggleBtn("btnnotifyWeb",0),settings.notificationWeb=!0)})):toggleBtn("btnnotifyWeb",0):(a.setAttribute("disabled","disabled"),toggleBtn("btnnotifyWeb",0));toggleBtn("btnnotifyPage",settings.notificationPage);for(var b="featStickers featSmartpls featPlaylists featTags featLocalplayer featSyscmds featCoverimage".split(" "),
notificationsSupported()?settings.notificationWeb?(toggleBtn("btnnotifyWeb",settings.notificationWeb),Notification.requestPermission(function(a){"permission"in Notification||(Notification.permission=a);"granted"===a?toggleBtn("btnnotifyWeb",1):(toggleBtn("btnnotifyWeb",0),settings.notificationWeb=!0)})):toggleBtn("btnnotifyWeb",0):(a.setAttribute("disabled","disabled"),toggleBtn("btnnotifyWeb",0));toggleBtn("btnnotifyPage",settings.notificationPage);for(var b="featStickers featSmartpls featPlaylists featTags featLocalplayer featSyscmds featCoverimage featAdvsearch".split(" "),
c=0;c<b.length;c++){var d=document.getElementsByClassName(b[c]),e=d.length,f=1==settings[b[c]]?"":"none";for(a=0;a<e;a++)d[a].style.display=f}if(0==settings.featTags)app.apps.Browse.active="Filesystem",app.apps.Search.state="0/filename/",app.apps.Queue.state="0/filename/",settings.colsQueueCurrent=["Pos","Title","Duration"],settings.colsQueueLastPlayed=["Pos","Title","LastPlayed"],settings.colsSearch=["Title","Duration"],settings.colsBrowseFilesystem=["Type","Title","Duration"],settings.colsBrowseDatabase=
["Track","Title","Duration"],settings.colsPlayback=[];else{b="";for(a=0;a<settings.colsPlayback.length;a++)b+='<div id="current'+settings.colsPlayback[a]+'" data-tag="'+settings.colsPlayback[a]+'" data-name="'+encodeURI(lastSongObj.data?lastSongObj.data[settings.colsPlayback[a]]:"")+'"><small>'+settings.colsPlayback[a]+"</small><h4",settings.browsetags.includes(settings.colsPlayback[a])&&(b+=' class="clickable"'),b+=">"+(lastSongObj.data?lastSongObj.data[settings.colsPlayback[a]]:"")+"</h4></div>";
document.getElementById("cardPlaybackTags").innerHTML=b}1==settings.mixramp?document.getElementsByClassName("mixramp")[0].style.display="":document.getElementsByClassName("mixramp")[0].style.display="none";!settings.tags.includes("AlbumArtist")&&settings.featTags&&(settings.tags.includes("Artist")?app.apps.Browse.tabs.Database.active="Artist":app.apps.Browse.tabs.Database.active=settings.tags[0]);document.getElementById("selectJukeboxMode").value=settings.jukeboxMode;document.getElementById("inputJukeboxQueueLength").value=
@ -80,59 +88,59 @@ if(settings.featSyscmds){document.getElementById("mainMenuDropdown");b="";c=sett
setCols("Search");setCols("QueueLastPlayed");setCols("BrowseFilesystem");setCols("BrowsePlaylistsDetail");setCols("BrowseDatabase",".tblAlbumTitles");setCols("Playback");"Queue"==app.current.app&&"Current"==app.current.tab?getQueue():"Queue"==app.current.app&&"LastPlayed"==app.current.tab?appRoute():"Search"==app.current.app?appRoute():"Browse"==app.current.app&&"Filesystem"==app.current.tab?appRoute():"Browse"==app.current.app&&"Playlists"==app.current.tab&&"Detail"==app.current.view?appRoute():
"Browse"==app.current.app&&"Database"==app.current.tab&&""!=app.current.search&&appRoute()}
function setCols(a,b){var c="",d=settings.tags.slice();0==settings.featTags&&d.push("Title");d.push("Duration");"QueueCurrent"!=a&&"BrowsePlaylistsDetail"!=a&&"QueueLastPlayed"!=a||d.push("Pos");"BrowseFilesystem"==a&&d.push("Type");"QueueLastPlayed"==a&&d.push("LastPlayed");d.sort();for(var e=0;e<d.length;e++)if("Playback"!=a||"Title"!=d[e])c+='<div class="form-check"><input class="form-check-input" type="checkbox" value="1" name="'+d[e]+'"',settings["cols"+a].includes(d[e])&&(c+="checked"),c+='><label class="form-check-label text-light" for="'+
d[e]+'">&nbsp;&nbsp;'+d[e]+"</label></div>";document.getElementById(a+"ColsDropdown").firstChild.innerHTML=c;if("Playback"!=a){c="";for(e=0;e<settings["cols"+a].length;e++){d=settings["cols"+a][e];c+='<th draggable="true" data-col="'+d+'">';if("Track"==d||"Pos"==d)d="#";c+=d+"</th>"}c+="<th></th>";if(void 0==b)document.getElementById(a+"List").getElementsByTagName("tr")[0].innerHTML=c;else for(a=document.querySelectorAll(b),e=0;e<a.length;e++)a[e].getElementsByTagName("tr")[0].innerHTML=c}}
function getSettings(){sendAPI({cmd:"MPD_API_SETTINGS_GET"},parseSettings)}
d[e]+'">&nbsp;&nbsp;'+d[e]+"</label></div>";document.getElementById(a+"ColsDropdown").firstChild.innerHTML=c;d=document.getElementById("SearchList").getAttribute("data-sort");""==d&&(d=settings.featTags?"Title":"Filename");if("Playback"!=a){c="";for(e=0;e<settings["cols"+a].length;e++){var f=settings["cols"+a][e];c+='<th draggable="true" data-col="'+f+'">';if("Track"==f||"Pos"==f)f="#";c+=f;"Search"==a&&f==d&&(f=!1,0==d.indexOf("-")&&(f=!0,d=d.substring(1)),c+='<span class="sort-dir material-icons pull-right">'+
(1==f?"arrow_drop_up":"arrow_drop_down")+"</span>");c+="</th>"}c+="<th></th>";if(void 0==b)document.getElementById(a+"List").getElementsByTagName("tr")[0].innerHTML=c;else for(a=document.querySelectorAll(b),e=0;e<a.length;e++)a[e].getElementsByTagName("tr")[0].innerHTML=c}}function getSettings(){sendAPI({cmd:"MPD_API_SETTINGS_GET"},parseSettings)}
function saveCols(a,b){var c=document.getElementById(a+"ColsDropdown").firstChild.getElementsByTagName("input");var d=void 0==b?document.getElementById(a+"List").getElementsByTagName("tr")[0]:"string"==typeof b?document.querySelector(b).getElementsByTagName("tr")[0]:b.getElementsByTagName("tr")[0];for(b=0;b<c.length;b++){var e=d.querySelector("[data-col="+c[b].name+"]");0==c[b].checked?e&&e.remove():e||(e=document.createElement("th"),e.innerText=c[b].name,e.setAttribute("data-col",c[b].name),d.appendChild(e))}a=
{cmd:"MPD_API_COLS_SAVE",data:{table:"cols"+a,cols:[]}};c=d.getElementsByTagName("th");for(b=0;b<c.length;b++)(d=c[b].getAttribute("data-col"))&&a.data.cols.push(d);sendAPI(a,getSettings)}
function saveColsPlayback(a){for(var b=document.getElementById(a+"ColsDropdown").firstChild.getElementsByTagName("input"),c=document.getElementById("cardPlaybackTags"),d=0;d<b.length;d++){var e=document.getElementById("current"+b[d].name);0==b[d].checked?e&&e.remove():e||(e=document.createElement("div"),e.innerHTML="<small>"+b[d].name+"</small><h4></h4>",e.setAttribute("id","current"+b[d].name),e.setAttribute("data-tag",b[d].name),c.appendChild(e))}a={cmd:"MPD_API_COLS_SAVE",data:{table:"cols"+a,
cols:[]}};c=c.getElementsByTagName("div");for(d=0;d<c.length;d++)(b=c[d].getAttribute("data-tag"))&&a.data.cols.push(b);sendAPI(a,getSettings)}
function parseOutputs(a){for(var b="",c=a.data.outputs.length,d=0;d<c;d++)b+='<button id="btnOutput'+a.data.outputs[d].id+'" data-output-id="'+a.data.outputs[d].id+'" class="btn btn-secondary btn-block',1==a.data.outputs[d].state&&(b+=" active"),b+='"><span class="material-icons float-left">volume_up</span> '+a.data.outputs[d].name+"</button>";domCache.outputs.innerHTML=b}
function setCounter(a,b,c){currentSong.totalTime=b;currentSong.elapsedTime=c;currentSong.currentSongId=a;var d=Math.floor(b/60),e=b-60*d,f=Math.floor(c/60),g=c-60*f;domCache.progressBar.value=Math.floor(100*c/b);b=f+":"+(10>g?"0":"")+g+" / "+d+":"+(10>e?"0":"")+e;domCache.counter.innerText=b;if(lastState&&lastState.data.currentSongId!=a&&(c=document.getElementById("queueTrackId"+lastState.data.currentSongId))){if(d=c.querySelector("[data-col=Duration]"))d.innerText=c.getAttribute("data-duration");
function setCounter(a,b,c){currentSong.totalTime=b;currentSong.elapsedTime=c;currentSong.currentSongId=a;var d=Math.floor(b/60),e=b-60*d,f=Math.floor(c/60),g=c-60*f;domCache.progressBar.value=Math.floor(1E3*c/b);b=f+":"+(10>g?"0":"")+g+" / "+d+":"+(10>e?"0":"")+e;domCache.counter.innerText=b;if(lastState&&lastState.data.currentSongId!=a&&(c=document.getElementById("queueTrackId"+lastState.data.currentSongId))){if(d=c.querySelector("[data-col=Duration]"))d.innerText=c.getAttribute("data-duration");
if(d=c.querySelector("[data-col=Pos]"))d.classList.remove("material-icons"),d.innerText=c.getAttribute("data-songpos");c.classList.remove("font-weight-bold")}if(c=document.getElementById("queueTrackId"+a)){if(d=c.querySelector("[data-col=Duration]"))d.innerText=b;(d=c.querySelector("[data-col=Pos]"))&&!d.classList.contains("material-icons")&&(d.classList.add("material-icons"),d.innerText="play_arrow");c.classList.add("font-weight-bold")}progressTimer&&clearTimeout(progressTimer);"play"==playstate&&
(progressTimer=setTimeout(function(){currentSong.elapsedTime++;setCounter(currentSong.currentSongId,currentSong.totalTime,currentSong.elapsedTime)},1E3))}
function parseState(a){if(JSON.stringify(a)!==JSON.stringify(lastState)){if(1==a.data.state){for(var b=0;b<domCache.btnsPlayLen;b++)domCache.btnsPlay[b].innerText="play_arrow";playstate="stop"}else if(2==a.data.state){for(b=0;b<domCache.btnsPlayLen;b++)domCache.btnsPlay[b].innerText="pause";playstate="play"}else{for(b=0;b<domCache.btnsPlayLen;b++)domCache.btnsPlay[b].innerText="play_arrow";playstate="pause"}-1==a.data.nextSongPos&&0==settings.jukeboxMode?domCache.btnNext.setAttribute("disabled","disabled"):
domCache.btnNext.removeAttribute("disabled");0>=a.data.songPos?domCache.btnPrev.setAttribute("disabled","disabled"):domCache.btnPrev.removeAttribute("disabled");if(0==a.data.queueLength)for(b=0;b<domCache.btnsPlayLen;b++)domCache.btnsPlay[b].setAttribute("disabled","disabled");else for(b=0;b<domCache.btnsPlayLen;b++)domCache.btnsPlay[b].removeAttribute("disabled");domCache.badgeQueueItems.innerText=a.data.queueLength;parseVolume(a);setCounter(a.data.currentSongId,a.data.totalTime,a.data.elapsedTime);
lastState&&lastState.data.currentSongId!=a.data.currentSongId&&sendAPI({cmd:"MPD_API_PLAYER_CURRENT_SONG"},songChange);if("-1"==a.data.songPos){domCache.currentTitle.innerText="Not playing";domCache.currentCover.style.backgroundImage="";var c=document.getElementById("cardPlaybackTags").getElementsByTagName("h4");for(b=0;b<c.length;b++)c[b].innerText=""}"LastPlayed"==app.current.app&&sendAPI({cmd:"MPD_API_QUEUE_LAST_PLAYED",data:{offset:app.current.page}},parseLastPlayed);lastState=a}}
function parseVolume(a){-1==a.data.volume?(domCache.volumePrct.innerText="Volumecontrol disabled",domCache.volumeControl.classList.add("hide")):(domCache.volumeControl.classList.remove("hide"),domCache.volumePrct.innerText=a.data.volume+" %",domCache.volumeMenu.innerText=0==a.data.volume?"volume_off":50>a.data.volume?"volume_down":"volume_up");domCache.volumeBar.value=a.data.volume}
lastState&&lastState.data.currentSongId!=a.data.currentSongId&&sendAPI({cmd:"MPD_API_PLAYER_CURRENT_SONG"},songChange);if("-1"==a.data.songPos){domCache.currentTitle.innerText="Not playing";domCache.currentCover.style.backgroundImage="";var c=document.getElementById("cardPlaybackTags").getElementsByTagName("h4");for(b=0;b<c.length;b++)c[b].innerText=""}"Queue"==app.current.app&&"LastPlayed"==app.current.tab&&sendAPI({cmd:"MPD_API_QUEUE_LAST_PLAYED",data:{offset:app.current.page}},parseLastPlayed);
lastState=a}}function parseVolume(a){-1==a.data.volume?(domCache.volumePrct.innerText="Volumecontrol disabled",domCache.volumeControl.classList.add("hide")):(domCache.volumeControl.classList.remove("hide"),domCache.volumePrct.innerText=a.data.volume+" %",domCache.volumeMenu.innerText=0==a.data.volume?"volume_off":50>a.data.volume?"volume_down":"volume_up");domCache.volumeBar.value=a.data.volume}
function getQueue(){2<=app.current.search.length?sendAPI({cmd:"MPD_API_QUEUE_SEARCH",data:{filter:app.current.filter,offset:app.current.page,searchstr:app.current.search}},parseQueue):sendAPI({cmd:"MPD_API_QUEUE_LIST",data:{offset:app.current.page}},parseQueue)}
function parseQueue(a){0<a.totalTime&&a.totalEntities<=settings.maxElementsPerPage?document.getElementById("cardFooterQueue").innerText=a.totalEntities+" "+(1<a.totalEntities?"Songs":"Song")+" \u2013 "+beautifyDuration(a.totalTime):0<a.totalEntities?document.getElementById("cardFooterQueue").innerText=a.totalEntities+" "+(1<a.totalEntities?"Songs":"Song"):document.getElementById("cardFooterQueue").innerText="";var b=a.data.length,c=document.getElementById("QueueCurrentList");c.setAttribute("data-version",
a.queueVersion);c=c.getElementsByTagName("tbody")[0];for(var d=c.getElementsByTagName("tr"),e=0;e<b;e++){var f=Math.floor(a.data[e].Duration/60),g=a.data[e].Duration-60*f;a.data[e].Duration=f+":"+(10>g?"0":"")+g;a.data[e].Pos++;f=document.createElement("tr");f.setAttribute("draggable","true");f.setAttribute("data-trackid",a.data[e].id);f.setAttribute("id","queueTrackId"+a.data[e].id);f.setAttribute("data-songpos",a.data[e].Pos);f.setAttribute("data-duration",a.data[e].Duration);f.setAttribute("data-uri",
a.data[e].uri);g="";for(var h=0;h<settings.colsQueueCurrent.length;h++)g+='<td data-col="'+settings.colsQueueCurrent[h]+'">'+a.data[e][settings.colsQueueCurrent[h]]+"</td>";g+='<td data-col="Action"><a href="#" class="material-icons color-darkgrey">playlist_add</a></td>';f.innerHTML=g;e<d.length?d[e].replaceWith(f):c.append(f)}for(e=d.length-1;e>=b;e--)d[e].remove();d=settings.colsQueueCurrent.length;d--;"queuesearch"==a.type&&0==b?c.innerHTML='<tr><td><span class="material-icons">error_outline</span></td><td colspan="'+
d+'">No results, please refine your search!</td></tr>':"queue"==a.type&&0==b&&(c.innerHTML='<tr><td><span class="material-icons">error_outline</span></td><td colspan="'+d+'">Empty queue</td></tr>');setPagination(a.totalEntities);document.getElementById("QueueCurrentList").classList.remove("opacity05")}
d+'">No results, please refine your search!</td></tr>':"queue"==a.type&&0==b&&(c.innerHTML='<tr><td><span class="material-icons">error_outline</span></td><td colspan="'+d+'">Empty queue</td></tr>');setPagination(a.totalEntities,a.returnedEntities);document.getElementById("QueueCurrentList").classList.remove("opacity05")}
function parseLastPlayed(a){document.getElementById("cardFooterQueue").innerText=a.totalEntities+" Songs";var b=a.data.length,c=document.getElementById("QueueLastPlayedList");c.setAttribute("data-version",a.queueVersion);c=c.getElementsByTagName("tbody")[0];for(var d=c.getElementsByTagName("tr"),e=0;e<b;e++){var f=Math.floor(a.data[e].Duration/60),g=a.data[e].Duration-60*f;a.data[e].Duration=f+":"+(10>g?"0":"")+g;a.data[e].LastPlayed=(new Date(1E3*a.data[e].LastPlayed)).toUTCString();f=document.createElement("tr");
f.setAttribute("data-songpos",a.data[e].Pos+1);f.setAttribute("data-uri",a.data[e].uri);f.setAttribute("data-name",a.data[e].Title);f.setAttribute("data-type","song");g="";for(var h=0;h<settings.colsQueueLastPlayed.length;h++)g+='<td data-col="'+settings.colsQueueLastPlayed[h]+'">'+a.data[e][settings.colsQueueLastPlayed[h]]+"</td>";g+='<td data-col="Action"><a href="#" class="material-icons color-darkgrey">playlist_add</a></td>';f.innerHTML=g;e<d.length?d[e].replaceWith(f):c.append(f)}for(e=d.length-
1;e>=b;e--)d[e].remove();d=settings.colsQueueLastPlayed.length;d--;0==b&&(c.innerHTML='<tr><td><span class="material-icons">error_outline</span></td><td colspan="'+d+'">Empty list</td></tr>');setPagination(a.totalEntities);document.getElementById("QueueLastPlayedList").classList.remove("opacity05")}
function parseSearch(a){document.getElementById("panel-heading-search").innerHTML=a.totalEntities+" Songs found";document.getElementById("cardFooterSearch").innerHTML=a.totalEntities+" Songs found";0<a.totalEntities?(document.getElementById("searchAddAllSongs").removeAttribute("disabled"),document.getElementById("searchAddAllSongsBtn").removeAttribute("disabled")):(document.getElementById("searchAddAllSongs").setAttribute("disabled","disabled"),document.getElementById("searchAddAllSongsBtn").setAttribute("disabled",
"disabled"));parseFilesystem(a)}
1;e>=b;e--)d[e].remove();d=settings.colsQueueLastPlayed.length;d--;0==b&&(c.innerHTML='<tr><td><span class="material-icons">error_outline</span></td><td colspan="'+d+'">Empty list</td></tr>');setPagination(a.totalEntities,a.returnedEntities);document.getElementById("QueueLastPlayedList").classList.remove("opacity05")}
function parseSearch(a){-1<a.totalEntities?(document.getElementById("panel-heading-search").innerText=a.totalEntities+" Songs found",document.getElementById("cardFooterSearch").innerText=a.totalEntities+" Songs found"):a.returnedEntities+app.current.page<settings.maxElementsPerPage?(document.getElementById("panel-heading-search").innerText=a.returnedEntities+" Songs found",document.getElementById("cardFooterSearch").innerText=a.returnedEntities+" Songs found"):(document.getElementById("panel-heading-search").innerHTML=
"&ge; "+settings.maxElementsPerPage+" Songs found",document.getElementById("cardFooterSearch").innerHTML="&ge; "+settings.maxElementsPerPage+" Songs found");0<a.returnedEntities?(document.getElementById("searchAddAllSongs").removeAttribute("disabled"),document.getElementById("searchAddAllSongsBtn").removeAttribute("disabled")):(document.getElementById("searchAddAllSongs").setAttribute("disabled","disabled"),document.getElementById("searchAddAllSongsBtn").setAttribute("disabled","disabled"));parseFilesystem(a)}
function parseFilesystem(a){var b=app.current.app+("Filesystem"==app.current.tab?app.current.tab:""),c=settings["cols"+b].length;c--;for(var d=a.data.length,e=document.getElementById(app.current.app+(void 0==app.current.tab?"":app.current.tab)+"List").getElementsByTagName("tbody")[0],f=e.getElementsByTagName("tr"),g=0;g<d;g++){var h=encodeURI(a.data[g].uri),l=document.createElement("tr");l.setAttribute("data-type",a.data[g].Type);l.setAttribute("data-uri",h);"song"==a.data[g].Type?l.setAttribute("data-name",
a.data[g].Title):l.setAttribute("data-name",a.data[g].name);switch(a.data[g].Type){case "dir":case "smartpls":case "plist":h="";for(var k=0;k<settings["cols"+b].length;k++)h+='<td data-col="'+settings["cols"+b][k]+'">',"Type"==settings["cols"+b][k]?h="dir"==a.data[g].Type?h+'<span class="material-icons">folder_open</span>':h+'<span class="material-icons">list</span>':"Title"==settings["cols"+b][k]&&(h+=a.data[g].name),h+="</td>";h+='<td data-col="Action"><a href="#" class="material-icons color-darkgrey">playlist_add</a></td>';
a.data[g].Title):l.setAttribute("data-name",a.data[g].name);switch(a.data[g].Type){case "dir":case "smartpls":case "plist":h="";for(var k=0;k<settings["cols"+b].length;k++)h+='<td data-col="'+settings["cols"+b][k]+'">',"Type"==settings["cols"+b][k]?h="dir"==a.data[g].Type?h+'<span class="material-icons">folder_open</span>':h+('<span class="material-icons">'+("smartpls"==a.data[g].Type?"queue_music":"list")+"</span>"):"Title"==settings["cols"+b][k]&&(h+=a.data[g].name),h+="</td>";h+='<td data-col="Action"><a href="#" class="material-icons color-darkgrey">playlist_add</a></td>';
l.innerHTML=h;break;case "song":h=Math.floor(a.data[g].Duration/60);k=a.data[g].Duration-60*h;a.data[g].Duration=h+":"+(10>k?"0":"")+k;h="";for(k=0;k<settings["cols"+b].length;k++)h+='<td data-col="'+settings["cols"+b][k]+'">',h="Type"==settings["cols"+b][k]?h+'<span class="material-icons">music_note</span>':h+a.data[g][settings["cols"+b][k]],h+="</td>";h+='<td data-col="Action"><a href="#" class="material-icons color-darkgrey">playlist_add</a></td>';l.innerHTML=h}g<f.length?f[g].replaceWith(l):e.append(l)}for(g=
f.length-1;g>=d;g--)f[g].remove();setPagination(a.totalEntities);0==d&&(e.innerHTML='<tr><td><span class="material-icons">error_outline</span></td><td colspan="'+c+'">No results</td></tr>');document.getElementById(app.current.app+(void 0==app.current.tab?"":app.current.tab)+"List").classList.remove("opacity05");document.getElementById("cardFooterBrowse").innerText=a.totalEntities+" Entries"}
f.length-1;g>=d;g--)f[g].remove();setPagination(a.totalEntities,a.returnedEntities);0==d&&(e.innerHTML='<tr><td><span class="material-icons">error_outline</span></td><td colspan="'+c+'">No results</td></tr>');document.getElementById(app.current.app+(void 0==app.current.tab?"":app.current.tab)+"List").classList.remove("opacity05");document.getElementById("cardFooterBrowse").innerText=a.totalEntities+" Entries"}
function parsePlaylists(a){"All"==app.current.view?(document.getElementById("BrowsePlaylistsAllList").classList.remove("hide"),document.getElementById("BrowsePlaylistsDetailList").classList.add("hide"),document.getElementById("btnBrowsePlaylistsAll").parentNode.classList.add("hide"),document.getElementById("btnPlaylistClear").parentNode.classList.add("hide"),document.getElementById("BrowsePlaylistsDetailColsBtn").parentNode.classList.add("hide")):(-1<a.uri.indexOf(".")||1==a.smartpls?(document.getElementById("BrowsePlaylistsDetailList").setAttribute("data-ro",
"true"),document.getElementById("btnPlaylistClear").parentNode.classList.add("hide")):(document.getElementById("BrowsePlaylistsDetailList").setAttribute("data-ro","false"),document.getElementById("btnPlaylistClear").parentNode.classList.remove("hide")),document.getElementById("BrowsePlaylistsDetailList").setAttribute("data-uri",a.uri),1==a.smartpls?document.getElementById("BrowsePlaylistsDetailList").getElementsByTagName("caption")[0].innerHTML="Smart playlist: "+a.uri+'<small class="pull-right">'+
a.totalEntities+" Songs </small>":document.getElementById("BrowsePlaylistsDetailList").getElementsByTagName("caption")[0].innerHTML="Playlist: "+a.uri+'<small class="pull-right">'+a.totalEntities+" Songs </small>",document.getElementById("BrowsePlaylistsDetailList").classList.remove("hide"),document.getElementById("BrowsePlaylistsAllList").classList.add("hide"),document.getElementById("btnBrowsePlaylistsAll").parentNode.classList.remove("hide"),settings.featTags&&document.getElementById("BrowsePlaylistsDetailColsBtn").parentNode.classList.remove("hide"));
var b=a.data.length,c=document.getElementById(app.current.app+app.current.tab+app.current.view+"List").getElementsByTagName("tbody")[0],d=c.getElementsByTagName("tr");if("All"==app.current.view){for(var e=0;e<b;e++){var f=encodeURI(a.data[e].uri),g=new Date(1E3*a.data[e].last_modified),h=document.createElement("tr");h.setAttribute("data-uri",f);h.setAttribute("data-type",a.data[e].Type);h.setAttribute("data-name",a.data[e].name);h.innerHTML='<td data-col="Type"><span class="material-icons">list</span></td><td>'+
a.data[e].name+"</td><td>"+g.toUTCString()+'</td><td data-col="Action"><a href="#" class="material-icons color-darkgrey">playlist_add</a></td>';e<d.length?d[e].replaceWith(h):c.append(h)}document.getElementById("cardFooterBrowse").innerText=a.totalEntities+" Playlists"}else if("Detail"==app.current.view){for(e=0;e<b;e++){f=encodeURI(a.data[e].uri);h=document.createElement("tr");0==a.smartpls&&h.setAttribute("draggable","true");h.setAttribute("id","playlistTrackId"+a.data[e].Pos);h.setAttribute("data-type",
a.data[e].Type);h.setAttribute("data-uri",f);h.setAttribute("data-name",a.data[e].Title);h.setAttribute("data-songpos",a.data[e].Pos);f=Math.floor(a.data[e].Duration/60);g=a.data[e].Duration-60*f;a.data[e].Duration=f+":"+(10>g?"0":"")+g;f="";for(g=0;g<settings.colsBrowsePlaylistsDetail.length;g++)f+='<td data-col="'+settings.colsBrowsePlaylistsDetail[g]+'">'+a.data[e][settings.colsBrowsePlaylistsDetail[g]]+"</td>";f+='<td data-col="Action"><a href="#" class="material-icons color-darkgrey">playlist_add</a></td>';
h.innerHTML=f;e<d.length?d[e].replaceWith(h):c.append(h)}document.getElementById("cardFooterBrowse").innerText=a.totalEntities+" Songs"}for(e=d.length-1;e>=b;e--)d[e].remove();setPagination(a.totalEntities);0==b&&(a=settings["cols"+list].length,a--,c.innerHTML="All"==app.current.view?'<tr><td><span class="material-icons">error_outline</span></td><td colspan="'+a+'">No playlists found.</td></tr>':'<tr><td><span class="material-icons">error_outline</span></td><td colspan="'+a+'">Empty playlist.</td></tr>');
var b=a.data.length,c=document.getElementById(app.current.app+app.current.tab+app.current.view+"List").getElementsByTagName("tbody")[0],d=c.getElementsByTagName("tr");if("All"==app.current.view){for(var e=0;e<b;e++){var f=encodeURI(a.data[e].uri),g=new Date(1E3*a.data[e].last_modified),h=document.createElement("tr");h.setAttribute("data-uri",f);h.setAttribute("data-type",a.data[e].Type);h.setAttribute("data-name",a.data[e].name);h.innerHTML='<td data-col="Type"><span class="material-icons">'+("smartpls"==
a.data[e].Type?"queue_music":"list")+"</span></td><td>"+a.data[e].name+"</td><td>"+g.toUTCString()+'</td><td data-col="Action"><a href="#" class="material-icons color-darkgrey">playlist_add</a></td>';e<d.length?d[e].replaceWith(h):c.append(h)}document.getElementById("cardFooterBrowse").innerText=a.totalEntities+" Playlists"}else if("Detail"==app.current.view){for(e=0;e<b;e++){f=encodeURI(a.data[e].uri);h=document.createElement("tr");0==a.smartpls&&h.setAttribute("draggable","true");h.setAttribute("id",
"playlistTrackId"+a.data[e].Pos);h.setAttribute("data-type",a.data[e].Type);h.setAttribute("data-uri",f);h.setAttribute("data-name",a.data[e].Title);h.setAttribute("data-songpos",a.data[e].Pos);f=Math.floor(a.data[e].Duration/60);g=a.data[e].Duration-60*f;a.data[e].Duration=f+":"+(10>g?"0":"")+g;f="";for(g=0;g<settings.colsBrowsePlaylistsDetail.length;g++)f+='<td data-col="'+settings.colsBrowsePlaylistsDetail[g]+'">'+a.data[e][settings.colsBrowsePlaylistsDetail[g]]+"</td>";f+='<td data-col="Action"><a href="#" class="material-icons color-darkgrey">playlist_add</a></td>';
h.innerHTML=f;e<d.length?d[e].replaceWith(h):c.append(h)}document.getElementById("cardFooterBrowse").innerText=a.totalEntities+" Songs"}for(e=d.length-1;e>=b;e--)d[e].remove();setPagination(a.totalEntities,a.returnedEntities);0==b&&(a=settings["cols"+list].length,a--,c.innerHTML="All"==app.current.view?'<tr><td><span class="material-icons">error_outline</span></td><td colspan="'+a+'">No playlists found.</td></tr>':'<tr><td><span class="material-icons">error_outline</span></td><td colspan="'+a+'">Empty playlist.</td></tr>');
document.getElementById(app.current.app+app.current.tab+app.current.view+"List").classList.remove("opacity05")}
function parseListDBtags(a){scrollTo(0);if(""!=app.current.search){document.getElementById("BrowseDatabaseAlbumList").classList.remove("hide");document.getElementById("BrowseDatabaseTagList").classList.add("hide");document.getElementById("btnBrowseDatabaseByTag").parentNode.classList.add("hide");document.getElementById("btnBrowseDatabaseTag").parentNode.classList.remove("hide");document.getElementById("BrowseDatabaseAddAllSongs").parentNode.parentNode.classList.remove("hide");document.getElementById("BrowseDatabaseColsBtn").parentNode.classList.remove("hide");
document.getElementById("btnBrowseDatabaseTag").innerHTML="&laquo; "+app.current.view;document.getElementById("BrowseDatabaseAlbumListCaption").innerHTML="<h2>"+a.searchtagtype+": "+a.searchstr+"</h2><hr/>";document.getElementById("cardFooterBrowse").innerText=a.totalEntities+" Entries";for(var b=a.data.length,c=document.getElementById("BrowseDatabaseAlbumList"),d=c.getElementsByClassName("card"),e=0;e<b;e++){var f=genId(a.data[e].value),g=document.createElement("div");g.classList.add("card","ml-4",
"mr-4","mb-4","w-100");g.setAttribute("id","card"+f);g.setAttribute("data-album",encodeURI(a.data[e].value));var h='<div class="card-header"><span id="albumartist'+f+'"></span> &ndash; '+a.data[e].value+'</div> <div class="card-body"><div class="row">';settings.featCoverimage&&(h+=' <div class="col-md-auto"><a class="card-img-left"></a></div>');h+=' <div class="col"><table class="tblAlbumTitles table table-sm table-hover" id="tbl'+f+'"><thead><tr></tr></thead><tbody></tbody></table></div> </div></div></div>';
g.innerHTML=h;e<d.length?d[e].replaceWith(g):c.append(g);"IntersectionObserver"in window?createListTitleObserver(document.getElementById("card"+f)):sendAPI({cmd:"MPD_API_DATABASE_TAG_ALBUM_TITLE_LIST",data:{album:a.data[e].value,search:app.current.search,tag:app.current.view}},parseListTitles)}for(e=d.length-1;e>=b;e--)d[e].remove();setPagination(a.totalEntities);setCols("BrowseDatabase",".tblAlbumTitles");a=document.querySelectorAll(".tblAlbumTitles");for(e=0;e<a.length;e++)dragAndDropTableHeader(a[e]);
g.innerHTML=h;e<d.length?d[e].replaceWith(g):c.append(g);"IntersectionObserver"in window?createListTitleObserver(document.getElementById("card"+f)):sendAPI({cmd:"MPD_API_DATABASE_TAG_ALBUM_TITLE_LIST",data:{album:a.data[e].value,search:app.current.search,tag:app.current.view}},parseListTitles)}for(e=d.length-1;e>=b;e--)d[e].remove();setPagination(a.totalEntities,a.returnedEntities);setCols("BrowseDatabase",".tblAlbumTitles");a=document.querySelectorAll(".tblAlbumTitles");for(e=0;e<a.length;e++)dragAndDropTableHeader(a[e]);
document.getElementById("BrowseDatabaseAlbumList").classList.remove("opacity05")}else{document.getElementById("BrowseDatabaseAlbumList").classList.add("hide");document.getElementById("BrowseDatabaseTagList").classList.remove("hide");document.getElementById("btnBrowseDatabaseByTag").parentNode.classList.remove("hide");document.getElementById("BrowseDatabaseAddAllSongs").parentNode.parentNode.classList.add("hide");document.getElementById("BrowseDatabaseColsBtn").parentNode.classList.add("hide");document.getElementById("btnBrowseDatabaseTag").parentNode.classList.add("hide");
document.getElementById("BrowseDatabaseTagListCaption").innerText=app.current.view;document.getElementById("cardFooterBrowse").innerText=a.totalEntities+" Tags";b=a.data.length;c=document.getElementById(app.current.app+app.current.tab+"TagList").getElementsByTagName("tbody")[0];d=c.getElementsByTagName("tr");for(e=0;e<b;e++)f=encodeURI(a.data[e].value),g=document.createElement("tr"),g.setAttribute("data-uri",f),g.innerHTML='<td data-col="Type"><span class="material-icons">album</span></td><td>'+a.data[e].value+
"</td>",e<d.length?d[e].replaceWith(g):c.append(g);for(e=d.length-1;e>=b;e--)d[e].remove();setPagination(a.totalEntities);0==b&&(c.innerHTML='<tr><td><span class="material-icons">error_outline</span></td><td>No entries found.</td></tr>');document.getElementById("BrowseDatabaseTagList").classList.remove("opacity05")}}function createListTitleObserver(a){(new IntersectionObserver(getListTitles,{root:null,rootMargin:"0px"})).observe(a)}
"</td>",e<d.length?d[e].replaceWith(g):c.append(g);for(e=d.length-1;e>=b;e--)d[e].remove();setPagination(a.totalEntities,a.returnedEntities);0==b&&(c.innerHTML='<tr><td><span class="material-icons">error_outline</span></td><td>No entries found.</td></tr>');document.getElementById("BrowseDatabaseTagList").classList.remove("opacity05")}}function createListTitleObserver(a){(new IntersectionObserver(getListTitles,{root:null,rootMargin:"0px"})).observe(a)}
function getListTitles(a,b){a.forEach(function(a){0<a.intersectionRatio&&(b.unobserve(a.target),a=decodeURI(a.target.getAttribute("data-album")),sendAPI({cmd:"MPD_API_DATABASE_TAG_ALBUM_TITLE_LIST",data:{album:a,search:app.current.search,tag:app.current.view}},parseListTitles))})}
function parseListTitles(a){var b=genId(a.Album),c=document.getElementById("card"+b),d=c.getElementsByTagName("tbody")[0],e=c.querySelector(".card-header");e.setAttribute("data-uri",encodeURI(a.data[0].uri.replace(/\/[^\/]+$/,"")));e.setAttribute("data-name",a.Album);e.setAttribute("data-type","dir");e.addEventListener("click",function(a){showMenu(this,a)},!1);e.classList.add("clickable");if(c=c.getElementsByTagName("a")[0])c.style.backgroundImage='url("'+a.cover+'")',c.setAttribute("data-uri",encodeURI(a.data[0].uri.replace(/\/[^\/]+$/,
""))),c.setAttribute("data-name",a.Album),c.setAttribute("data-type","dir"),c.addEventListener("click",function(a){showMenu(this,a)},!1);document.getElementById("albumartist"+b).innerText=a.AlbumArtist;b="";c=a.data.length;for(e=0;e<c;e++){if(a.data[e].Duration){var f=Math.floor(a.data[e].Duration/60),g=a.data[e].Duration-60*f;a.data[e].Duration=f+":"+(10>g?"0":"")+g}b+='<tr data-type="song" data-name="'+a.data[e].Title+'" data-uri="'+encodeURI(a.data[e].uri)+'">';for(f=0;f<settings.colsBrowseDatabase.length;f++)b+=
'<td data-col="'+settings.colsBrowseDatabase[f]+'">'+a.data[e][settings.colsBrowseDatabase[f]]+"</td>";b+='<td data-col="Action"><a href="#" class="material-icons color-darkgrey">playlist_add</a></td></tr>'}d.innerHTML=b;d.parentNode.addEventListener("click",function(a){"TD"==a.target.nodeName?appendQueue("song",decodeURI(a.target.parentNode.getAttribute("data-uri")),a.target.parentNode.getAttribute("data-name")):"A"==a.target.nodeName&&showMenu(a.target,a)},!1)}
function setPagination(a){var b=Math.ceil(a/settings.maxElementsPerPage),c=app.current.app+(void 0==app.current.tab?"":app.current.tab);0==b&&(b=1);for(var d=["PaginationTop","PaginationBottom"],e=0;2>e;e++){document.getElementById(c+d[e]+"Page").innerText=app.current.page/settings.maxElementsPerPage+1+" / "+b;if(1<b){document.getElementById(c+d[e]+"Page").removeAttribute("disabled");for(var f="",g=0;g<b;g++)f+='<button data-page="'+g*settings.maxElementsPerPage+'" type="button" class="mr-1 mb-1 btn-sm btn btn-secondary">'+
(g+1)+"</button>";document.getElementById(c+d[e]+"Pages").innerHTML=f}else document.getElementById(c+d[e]+"Page").setAttribute("disabled","disabled");a>app.current.page+settings.maxElementsPerPage?(document.getElementById(c+d[e]+"Next").removeAttribute("disabled"),document.getElementById(c+d[e]).classList.remove("hide"),document.getElementById(c+"ButtonsBottom").classList.remove("hide")):(document.getElementById(c+d[e]+"Next").setAttribute("disabled","disabled"),document.getElementById(c+d[e]).classList.add("hide"),
document.getElementById(c+"ButtonsBottom").classList.add("hide"));0<app.current.page?(document.getElementById(c+d[e]+"Prev").removeAttribute("disabled"),document.getElementById(c+d[e]).classList.remove("hide"),document.getElementById(c+"ButtonsBottom").classList.remove("hide")):document.getElementById(c+d[e]+"Prev").setAttribute("disabled","disabled")}}
function appendQueue(a,b,c){switch(a){case "song":case "dir":sendAPI({cmd:"MPD_API_QUEUE_ADD_TRACK",data:{uri:b}});showNotification('"'+c+'" added',"","","success");break;case "plist":sendAPI({cmd:"MPD_API_QUEUE_ADD_PLAYLIST",data:{plist:b}}),showNotification('"'+c+'" added',"","","success")}}function appendAfterQueue(a,b,c,d){switch(a){case "song":sendAPI({cmd:"MPD_API_QUEUE_ADD_TRACK_AFTER",data:{uri:b,to:c}}),showNotification('"'+d+'" added to pos '+c,"","","success")}}
function replaceQueue(a,b,c){switch(a){case "song":case "dir":sendAPI({cmd:"MPD_API_QUEUE_REPLACE_TRACK",data:{uri:b}});showNotification('"'+c+'" replaced',"","","success");break;case "plist":sendAPI({cmd:"MPD_API_QUEUE_REPLACE_PLAYLIST",data:{plist:b}}),showNotification('"'+c+'" replaced',"","","success")}}function clickTitle(){var a=decodeURI(domCache.currentTitle.getAttribute("data-uri"));""!=a&&songDetails(a)}
function gotoBrowse(a){var b=a.parentNode.getAttribute("data-tag");a=decodeURI(a.parentNode.getAttribute("data-name"));""!=b&&""!=a&&"-"!=a&&settings.browsetags.includes(b)&&appGoto("Browse","Database",b,"0/-/"+a)}function songDetails(a){sendAPI({cmd:"MPD_API_DATABASE_SONGDETAILS",data:{uri:a}},parseSongDetails);modalSongDetails.show()}
function setPagination(a,b){var c=app.current.app+(void 0==app.current.tab?"":app.current.tab),d=Math.ceil(a/settings.maxElementsPerPage);0==d&&(d=1);for(var e=["PaginationTop","PaginationBottom"],f=0;2>f;f++){document.getElementById(c+e[f]+"Page").innerText=app.current.page/settings.maxElementsPerPage+1+" / "+d;if(1<d){document.getElementById(c+e[f]+"Page").removeAttribute("disabled");for(var g="",h=0;h<d;h++)g+='<button data-page="'+h*settings.maxElementsPerPage+'" type="button" class="mr-1 mb-1 btn-sm btn btn-secondary">'+
(h+1)+"</button>";document.getElementById(c+e[f]+"Pages").innerHTML=g;document.getElementById(c+e[f]+"Page").classList.remove("nodropdown")}else-1==a?(document.getElementById(c+e[f]+"Page").setAttribute("disabled","disabled"),document.getElementById(c+e[f]+"Page").innerText=app.current.page/settings.maxElementsPerPage+1):document.getElementById(c+e[f]+"Page").setAttribute("disabled","disabled"),document.getElementById(c+e[f]+"Page").classList.add("nodropdown");a>app.current.page+settings.maxElementsPerPage||
-1==a&&b>=settings.maxElementsPerPage?(document.getElementById(c+e[f]+"Next").removeAttribute("disabled"),document.getElementById(c+e[f]).classList.remove("hide"),document.getElementById(c+"ButtonsBottom").classList.remove("hide")):(document.getElementById(c+e[f]+"Next").setAttribute("disabled","disabled"),document.getElementById(c+e[f]).classList.add("hide"),document.getElementById(c+"ButtonsBottom").classList.add("hide"));0<app.current.page?(document.getElementById(c+e[f]+"Prev").removeAttribute("disabled"),
document.getElementById(c+e[f]).classList.remove("hide"),document.getElementById(c+"ButtonsBottom").classList.remove("hide")):document.getElementById(c+e[f]+"Prev").setAttribute("disabled","disabled")}}function appendQueue(a,b,c){switch(a){case "song":case "dir":sendAPI({cmd:"MPD_API_QUEUE_ADD_TRACK",data:{uri:b}});showNotification('"'+c+'" added',"","","success");break;case "plist":sendAPI({cmd:"MPD_API_QUEUE_ADD_PLAYLIST",data:{plist:b}}),showNotification('"'+c+'" added',"","","success")}}
function appendAfterQueue(a,b,c,d){switch(a){case "song":sendAPI({cmd:"MPD_API_QUEUE_ADD_TRACK_AFTER",data:{uri:b,to:c}}),showNotification('"'+d+'" added to pos '+c,"","","success")}}function replaceQueue(a,b,c){switch(a){case "song":case "dir":sendAPI({cmd:"MPD_API_QUEUE_REPLACE_TRACK",data:{uri:b}});showNotification('"'+c+'" replaced',"","","success");break;case "plist":sendAPI({cmd:"MPD_API_QUEUE_REPLACE_PLAYLIST",data:{plist:b}}),showNotification('"'+c+'" replaced',"","","success")}}
function clickTitle(){var a=decodeURI(domCache.currentTitle.getAttribute("data-uri"));""!=a&&songDetails(a)}function gotoBrowse(a){var b=a.parentNode.getAttribute("data-tag");a=decodeURI(a.parentNode.getAttribute("data-name"));""!=b&&""!=a&&"-"!=a&&settings.browsetags.includes(b)&&appGoto("Browse","Database",b,"0/-/"+a)}function songDetails(a){sendAPI({cmd:"MPD_API_DATABASE_SONGDETAILS",data:{uri:a}},parseSongDetails);modalSongDetails.show()}
function parseSongDetails(a){var b=document.getElementById("modalSongDetails");b.getElementsByClassName("album-cover")[0].style.backgroundImage='url("'+a.data.cover+'")';b.getElementsByTagName("h1")[0].innerText=a.data.Title;for(var c="",d=0;d<settings.tags.length;d++)c+="<tr><th>"+settings.tags[d]+'</th><td data-tag="'+settings.tags[d]+'" data-name="'+encodeURI(a.data[settings.tags[d]])+'">',c=settings.browsetags.includes(settings.tags[d])?c+('<a class="text-success" href="#">'+a.data[settings.tags[d]]+
"</a>"):c+a.data[settings.tags[d]],c+="</td></tr>";var e=a.data.Duration;d=Math.floor(e/60);e-=60*d;c+="<tr><th>Duration</th><td>"+(d+":"+(10>e?"0":"")+e)+"</td></tr>";c=settings.featLibrary?c+('<tr><th>Filename</th><td><a class="text-success" href="/library/'+encodeURI(a.data.uri)+'">'+a.data.uri+"</a></td></tr>"):c+("<tr><th>Filename</th><td>"+a.data.uri+"</td></tr>");1==settings.featStickers&&(c+='<tr><th colspan="2">Statistics</th></tr><tr><th>Play count</th><td>'+a.data.playCount+"</td></tr><tr><th>Skip count</th><td>"+
a.data.skipCount+"</td></tr><tr><th>Last played</th><td>"+(0==a.data.lastPlayed?"never":(new Date(1E3*a.data.lastPlayed)).toUTCString())+'</td></tr><tr><th>Like</th><td><div class="btn-group btn-group-sm"><button title="Dislike song" id="btnVoteDown2" data-href=\'{"cmd": "voteSong", "options": [0]}\' class="btn btn-sm btn-light material-icons">thumb_down</button><button title="Like song" id="btnVoteUp2" data-href=\'{"cmd": "voteSong", "options": [2]}\' class="btn btn-sm btn-light material-icons">thumb_up</button></div></td></tr>');
@ -140,7 +148,7 @@ b.getElementsByTagName("tbody")[0].innerHTML=c;setVoteSongBtns(a.data.like,a.dat
function playlistClear(){var a=document.getElementById("BrowsePlaylistsDetailList").getAttribute("data-uri");sendAPI({cmd:"MPD_API_PLAYLIST_CLEAR_AND_LIST",data:{uri:a}});document.getElementById("BrowsePlaylistsDetailList").classList.add("opacity05")}
function getAllPlaylists(a){var b=a.data.length,c="";0==a.offset&&("addToPlaylistPlaylist"==playlistEl?c="<option></option><option>New Playlist</option>":"selectJukeboxPlaylist"==playlistEl&&(c="<option>Database</option>"));for(var d=0;d<b;d++)c+="<option","selectJukeboxPlaylist"==playlistEl&&a.data[d].uri==settings.jukeboxPlaylist&&(c+=" selected"),c+=">"+a.data[d].uri+"</option>";0==a.offset?document.getElementById(playlistEl).innerHTML=c:document.getElementById(playlistEl).innerHTML+=c;a.totalEntities>
a.returnedEntities&&(a.offset+=settings.maxElementsPerPage,sendAPI({cmd:"MPD_API_PLAYLIST_LIST",data:{offset:a.offset,filter:"-"}},getAllPlaylists))}function updateSmartPlaylists(){sendAPI({cmd:"MPD_API_SMARTPLS_UPDATE_ALL"})}
function voteSong(a){var b=domCache.currentTitle.getAttribute("data-uri");""!=b&&(2==a&&domCache.btnVoteUp.classList.contains("active-fg-green")?a=1:0==a&&domCache.btnVoteDown.classList.contains("active-fg-red")&&(a=1),sendAPI({cmd:"MPD_API_LIKE",data:{uri:b,like:a}}),setVoteSongBtns(a,b))}
function voteSong(a){var b=decodeURI(domCache.currentTitle.getAttribute("data-uri"));""!=b&&(2==a&&domCache.btnVoteUp.classList.contains("active-fg-green")?a=1:0==a&&domCache.btnVoteDown.classList.contains("active-fg-red")&&(a=1),sendAPI({cmd:"MPD_API_LIKE",data:{uri:b,like:a}}),setVoteSongBtns(a,b))}
function setVoteSongBtns(a,b){domCache.btnVoteUp2=document.getElementById("btnVoteUp2");domCache.btnVoteDown2=document.getElementById("btnVoteDown2");""==b||0==b.indexOf("http://")||0==b.indexOf("https://")?(domCache.btnVoteUp.setAttribute("disabled","disabled"),domCache.btnVoteDown.setAttribute("disabled","disabled"),domCache.btnVoteUp2&&(domCache.btnVoteUp2.setAttribute("disabled","disabled"),domCache.btnVoteDown2.setAttribute("disabled","disabled"))):(domCache.btnVoteUp.removeAttribute("disabled"),
domCache.btnVoteDown.removeAttribute("disabled"),domCache.btnVoteUp2&&(domCache.btnVoteUp2.removeAttribute("disabled"),domCache.btnVoteDown2.removeAttribute("disabled")));0==a?(domCache.btnVoteUp.classList.remove("active-fg-green"),domCache.btnVoteDown.classList.add("active-fg-red"),domCache.btnVoteUp2&&(domCache.btnVoteUp2.classList.remove("active-fg-green"),domCache.btnVoteDown2.classList.add("active-fg-red"))):1==a?(domCache.btnVoteUp.classList.remove("active-fg-green"),domCache.btnVoteDown.classList.remove("active-fg-red"),
domCache.btnVoteUp2&&(domCache.btnVoteUp2.classList.remove("active-fg-green"),domCache.btnVoteDown2.classList.remove("active-fg-red"))):2==a&&(domCache.btnVoteUp.classList.add("active-fg-green"),domCache.btnVoteDown.classList.remove("active-fg-red"),domCache.btnVoteUp2&&(domCache.btnVoteUp2.classList.add("active-fg-green"),domCache.btnVoteDown2.classList.remove("active-fg-red")))}
@ -148,11 +156,12 @@ function toggleAddToPlaylistFrm(){var a=document.getElementById("toggleAddToPlay
function saveSearchAsSmartPlaylist(){parseSmartPlaylist({type:"smartpls",data:{playlist:"",type:"search",tag:app.current.filter,searchstr:app.current.search}})}
function parseSmartPlaylist(a){var b=document.getElementById("saveSmartPlaylistName");b.value=a.data.playlist;b.classList.remove("is-invalid");document.getElementById("saveSmartPlaylistType").value=a.data.type;document.getElementById("saveSmartPlaylistFrm").classList.remove("was-validated");document.getElementById("saveSmartPlaylistSearch").classList.add("hide");document.getElementById("saveSmartPlaylistSticker").classList.add("hide");document.getElementById("saveSmartPlaylistNewest").classList.add("hide");
var c;settings.featTags&&(c='<option value="any">Any Tag</option>');c+='<option value="filename">Filename</option>';for(var d=0;d<settings.searchtags.length;d++)c+='<option value="'+settings.searchtags[d]+'">'+settings.searchtags[d]+"</option>";document.getElementById("selectSaveSmartPlaylistTag").innerHTML=c;"search"==a.data.type?(document.getElementById("saveSmartPlaylistSearch").classList.remove("hide"),document.getElementById("selectSaveSmartPlaylistTag").value=a.data.tag,document.getElementById("inputSaveSmartPlaylistSearchstr").value=
a.data.searchstr):"sticker"==a.data.type?(document.getElementById("saveSmartPlaylistSticker").classList.remove("hide"),document.getElementById("selectSaveSmartPlaylistSticker").value=a.data.feat_sticker,document.getElementById("inputSaveSmartPlaylistStickerMaxentries").value=a.data.maxentries):"newest"==a.data.type&&(document.getElementById("saveSmartPlaylistNewest").classList.remove("hide"),a=a.data.timerange/24/60/60,document.getElementById("inputSaveSmartPlaylistNewestTimerange").value=a);modalSaveSmartPlaylist.show();
b.focus()}function chkInt(a,b){if(""!=a.value.replace(/\d/g,""))return a.classList.add("is-invalid"),b.classList.add("was-validated"),!1;a.classList.remove("is-invalid");return!0}
function saveSmartPlaylist(){var a=document.getElementById("saveSmartPlaylistName").value,b=document.getElementById("saveSmartPlaylistType").value,c=a.replace(/[\w\-]/g,""),d=document.getElementById("saveSmartPlaylistFrm");if(""!=a&&""==c){if("search"==b)d=document.getElementById("selectSaveSmartPlaylistTag"),d=d.options[d.selectedIndex].value,c=document.getElementById("inputSaveSmartPlaylistSearchstr").value,sendAPI({cmd:"MPD_API_SMARTPLS_SAVE",data:{type:b,playlist:a,tag:d,searchstr:c}});else if("sticker"==
b){c=document.getElementById("selectSaveSmartPlaylistSticker");c=c.options[c.selectedIndex].value;var e=document.getElementById("inputSaveSmartPlaylistStickerMaxentries");if(!chkInt(e,d))return;sendAPI({cmd:"MPD_API_SMARTPLS_SAVE",data:{type:b,playlist:a,sticker:c,maxentries:e.value}})}else if("newest"==b){c=document.getElementById("inputSaveSmartPlaylistNewestTimerange");if(!chkInt(c,d))return;d=86400*parseInt(c.value);sendAPI({cmd:"MPD_API_SMARTPLS_SAVE",data:{type:b,playlist:a,timerange:d}})}else{document.getElementById("saveSmartPlaylistType").classList.add("is-invalid");
return}modalSaveSmartPlaylist.hide();showNotification("Saved smart playlist "+a,"","","success")}else document.getElementById("saveSmartPlaylistName").classList.add("is-invalid"),d.classList.add("was-validated")}
a.data.searchstr,settings.featAdvsearch?(document.getElementById("selectSaveSmartPlaylistTag").parentNode.classList.add("hide"),document.getElementById("inputSaveSmartPlaylistSearchstr").parentNode.classList.replace("col-md-6","col-md-12")):(document.getElementById("selectSaveSmartPlaylistTag").parentNode.classList.remove("hide"),document.getElementById("inputSaveSmartPlaylistSearchstr").parentNode.classList.replace("col-md-12","col-md-6"))):"sticker"==a.data.type?(document.getElementById("saveSmartPlaylistSticker").classList.remove("hide"),
document.getElementById("selectSaveSmartPlaylistSticker").value=a.data.sticker,document.getElementById("inputSaveSmartPlaylistStickerMaxentries").value=a.data.maxentries):"newest"==a.data.type&&(document.getElementById("saveSmartPlaylistNewest").classList.remove("hide"),a=a.data.timerange/24/60/60,document.getElementById("inputSaveSmartPlaylistNewestTimerange").value=a);modalSaveSmartPlaylist.show();b.focus()}
function chkInt(a,b){if(""!=a.value.replace(/\d/g,""))return a.classList.add("is-invalid"),b.classList.add("was-validated"),!1;a.classList.remove("is-invalid");return!0}
function saveSmartPlaylist(){var a=document.getElementById("saveSmartPlaylistName").value,b=document.getElementById("saveSmartPlaylistType").value,c=a.replace(/[\w\-]/g,""),d=document.getElementById("saveSmartPlaylistFrm");if(""!=a&&""==c){if("search"==b)d=document.getElementById("selectSaveSmartPlaylistTag"),d=d.options[d.selectedIndex].value,settings.featAdvsearch&&(d="expression"),c=document.getElementById("inputSaveSmartPlaylistSearchstr").value,sendAPI({cmd:"MPD_API_SMARTPLS_SAVE",data:{type:b,
playlist:a,tag:d,searchstr:c}});else if("sticker"==b){c=document.getElementById("selectSaveSmartPlaylistSticker");c=c.options[c.selectedIndex].value;var e=document.getElementById("inputSaveSmartPlaylistStickerMaxentries");if(!chkInt(e,d))return;sendAPI({cmd:"MPD_API_SMARTPLS_SAVE",data:{type:b,playlist:a,sticker:c,maxentries:e.value}})}else if("newest"==b){c=document.getElementById("inputSaveSmartPlaylistNewestTimerange");if(!chkInt(c,d))return;d=86400*parseInt(c.value);sendAPI({cmd:"MPD_API_SMARTPLS_SAVE",
data:{type:b,playlist:a,timerange:d}})}else{document.getElementById("saveSmartPlaylistType").classList.add("is-invalid");return}modalSaveSmartPlaylist.hide();showNotification("Saved smart playlist "+a,"","","success")}else document.getElementById("saveSmartPlaylistName").classList.add("is-invalid"),d.classList.add("was-validated")}
function showAddToPlaylist(a){document.getElementById("addToPlaylistUri").value=a;document.getElementById("addToPlaylistPlaylist").innerHTML="";document.getElementById("addToPlaylistNewPlaylist").value="";document.getElementById("addToPlaylistNewPlaylistDiv").classList.add("hide");document.getElementById("addToPlaylistFrm").classList.remove("was-validated");document.getElementById("addToPlaylistNewPlaylist").classList.remove("is-invalid");toggleBtn("toggleAddToPlaylistBtn",0);var b=document.getElementById("streamUrl");
b.focus();b.value="";b.classList.remove("is-invalid");document.getElementById("addStreamFrm").classList.remove("was-validated");"stream"!=a?(document.getElementById("addStreamFooter").classList.add("hide"),document.getElementById("addStreamFrm").classList.add("hide"),document.getElementById("addToPlaylistFooter").classList.remove("hide"),document.getElementById("addToPlaylistFrm").classList.remove("hide"),document.getElementById("addToPlaylistLabel").innerText="Add to playlist"):(document.getElementById("addStreamFooter").classList.remove("hide"),
document.getElementById("addStreamFrm").classList.remove("hide"),document.getElementById("addToPlaylistFooter").classList.add("hide"),document.getElementById("addToPlaylistFrm").classList.add("hide"),document.getElementById("addToPlaylistLabel").innerText="Add Stream");modalAddToPlaylist.show();settings.featPlaylists&&(playlistEl="addToPlaylistPlaylist",sendAPI({cmd:"MPD_API_PLAYLIST_LIST",data:{offset:0,filter:"-"}},getAllPlaylists))}
@ -167,8 +176,8 @@ function showMenu(a,b){b.preventDefault();b.stopPropagation();hideMenu();if(!a.g
"Search"==app.current.app||"Browse"==app.current.app&&"Database"==app.current.tab?(f+=addMenuItem({cmd:"appendQueue",options:[b,c,d]},"Append to queue")+("song"==b?addMenuItem({cmd:"appendAfterQueue",options:[b,c,e,d]},"Add after current playing song"):"")+addMenuItem({cmd:"replaceQueue",options:[b,c,d]},"Replace queue")+("plist"!=b&&"smartpls"!=b&&settings.featPlaylists?addMenuItem({cmd:"showAddToPlaylist",options:[c]},"Add to playlist"):"")+("song"==b?addMenuItem({cmd:"songDetails",options:[c]},
"Songdetails"):"")+("plist"==b||"smartpls"==b?addMenuItem({cmd:"playlistDetails",options:[c]},"View playlist"):""),"Search"==app.current.app&&(c=dirname(c),f+='<div class="dropdown-divider"></div><a class="dropdown-item" id="advancedMenuLink" data-toggle="collapse" href="#advancedMenu"><span class="material-icons material-icons-small-left">keyboard_arrow_right</span>Album actions</a><div class="collapse" id="advancedMenu">'+addMenuItem({cmd:"appendQueue",options:[b,c,d]},"Append to queue")+addMenuItem({cmd:"appendAfterQueue",
options:[b,c,e,d]},"Add after current playing song")+addMenuItem({cmd:"replaceQueue",options:[b,c,d]},"Replace queue")+(settings.featPlaylists?addMenuItem({cmd:"showAddToPlaylist",options:[c]},"Add to playlist"):"")+"</div>")):"Browse"==app.current.app&&"Playlists"==app.current.tab&&"All"==app.current.view?f+=addMenuItem({cmd:"appendQueue",options:[b,c,d]},"Append to queue")+addMenuItem({cmd:"replaceQueue",options:[b,c,d]},"Replace queue")+("smartpls"==b?addMenuItem({cmd:"playlistDetails",options:[c]},
"View playlist"):addMenuItem({cmd:"playlistDetails",options:[c]},"Edit playlist"))+("smartpls"==b?addMenuItem({cmd:"showSmartPlaylist",options:[c]},"Edit smart playlist"):"")+(0!=c.indexOf("myMPDsmart")?addMenuItem({cmd:"showRenamePlaylist",options:[c]},"Rename playlist")+addMenuItem({cmd:"showDelPlaylist",options:[c]},"Delete playlist"):""):"Browse"==app.current.app&&"Playlists"==app.current.tab&&"Detail"==app.current.view?(e=document.getElementById("BrowsePlaylistsDetailList"),f+=addMenuItem({cmd:"appendQueue",
options:[b,c,d]},"Append to queue")+addMenuItem({cmd:"replaceQueue",options:[b,c,d]},"Replace queue")+("false"==e.getAttribute("data-ro")?addMenuItem({cmd:"removeFromPlaylist",options:[e.getAttribute("data-uri"),a.parentNode.parentNode.getAttribute("data-songpos")]},"Remove"):"")+addMenuItem({cmd:"showAddToPlaylist",options:[c]},"Add to playlist")+(-1==c.indexOf("http")?addMenuItem({cmd:"songDetails",options:[c]},"Songdetails"):"")):"Queue"==app.current.app&&"Current"==app.current.tab?f+=addMenuItem({cmd:"delQueueSong",
"View playlist"):addMenuItem({cmd:"playlistDetails",options:[c]},"Edit playlist"))+("smartpls"==b?addMenuItem({cmd:"showSmartPlaylist",options:[c]},"Edit smart playlist"):"")+addMenuItem({cmd:"showRenamePlaylist",options:[c]},"Rename playlist")+addMenuItem({cmd:"showDelPlaylist",options:[c]},"Delete playlist"):"Browse"==app.current.app&&"Playlists"==app.current.tab&&"Detail"==app.current.view?(e=document.getElementById("BrowsePlaylistsDetailList"),f+=addMenuItem({cmd:"appendQueue",options:[b,c,d]},
"Append to queue")+addMenuItem({cmd:"replaceQueue",options:[b,c,d]},"Replace queue")+("false"==e.getAttribute("data-ro")?addMenuItem({cmd:"removeFromPlaylist",options:[e.getAttribute("data-uri"),a.parentNode.parentNode.getAttribute("data-songpos")]},"Remove"):"")+addMenuItem({cmd:"showAddToPlaylist",options:[c]},"Add to playlist")+(-1==c.indexOf("http")?addMenuItem({cmd:"songDetails",options:[c]},"Songdetails"):"")):"Queue"==app.current.app&&"Current"==app.current.tab?f+=addMenuItem({cmd:"delQueueSong",
options:["single",a.parentNode.parentNode.getAttribute("data-trackid")]},"Remove")+addMenuItem({cmd:"delQueueSong",options:["range",0,a.parentNode.parentNode.getAttribute("data-songpos")]},"Remove all upwards")+addMenuItem({cmd:"delQueueSong",options:["range",parseInt(a.parentNode.parentNode.getAttribute("data-songpos"))-1,-1]},"Remove all downwards")+(-1==c.indexOf("http")?addMenuItem({cmd:"songDetails",options:[c]},"Songdetails"):""):"Queue"==app.current.app&&"LastPlayed"==app.current.tab&&(f+=
addMenuItem({cmd:"appendQueue",options:[b,c,d]},"Append to queue")+addMenuItem({cmd:"replaceQueue",options:[b,c,d]},"Replace queue")+addMenuItem({cmd:"showAddToPlaylist",options:[c]},"Add to playlist")+(-1==c.indexOf("http")?addMenuItem({cmd:"songDetails",options:[c]},"Songdetails"):""));new Popover(a,{trigger:"click",delay:0,dismissible:!0,template:'<div class="popover" role="tooltip"><div class="arrow"></div><div class="popover-content">'+f+"</div></div>"});b=a.Popover;a.setAttribute("data-init",
"true");a.addEventListener("shown.bs.popover",function(a){a.target.setAttribute("data-popover","true");document.getElementsByClassName("popover-content")[0].addEventListener("click",function(a){a.preventDefault();a.stopPropagation();if("A"==a.target.nodeName){var b=a.target.getAttribute("data-href");b&&(b=JSON.parse(b64DecodeUnicode(b)),parseCmd(a,b),hideMenu())}},!1);if(a=document.getElementById("advancedMenuLink"))a.addEventListener("click",function(a){a=this.getElementsByTagName("span")[0];a.innerText=
@ -183,7 +192,8 @@ function confirmSettings(){var a=!0,b=document.getElementById("inputCrossfade");
a=!1):b.value=c),b=document.getElementById("inputMixrampdelay"),b.getAttribute("disabled")||("nan"==b.value&&(b.value="-1"),c=parseFloat(b.value),isNaN(c)?(b.classList.add("is-invalid"),a=!1):b.value=c));1==a?(a=document.getElementById("selectReplaygain"),c=document.getElementById("selectJukeboxPlaylist"),b=document.getElementById("selectJukeboxMode"),sendAPI({cmd:"MPD_API_SETTINGS_SET",data:{consume:document.getElementById("btnConsume").classList.contains("active")?1:0,random:document.getElementById("btnRandom").classList.contains("active")?
1:0,single:document.getElementById("btnSingle").classList.contains("active")?1:0,repeat:document.getElementById("btnRepeat").classList.contains("active")?1:0,replaygain:a.options[a.selectedIndex].value,crossfade:document.getElementById("inputCrossfade").value,mixrampdb:1==settings.mixramp?document.getElementById("inputMixrampdb").value:settings.mixrampdb,mixrampdelay:1==settings.mixramp?document.getElementById("inputMixrampdelay").value:settings.mixrampdelay,notificationWeb:document.getElementById("btnnotifyWeb").classList.contains("active")?
!0:!1,notificationPage:document.getElementById("btnnotifyPage").classList.contains("active")?!0:!1,jukeboxMode:b.options[b.selectedIndex].value,jukeboxPlaylist:c.options[c.selectedIndex].value,jukeboxQueueLength:document.getElementById("inputJukeboxQueueLength").value}},getSettings),modalSettings.hide()):document.getElementById("settingsFrm").classList.add("was-validated")}
function addAllFromBrowseFilesystem(){sendAPI({cmd:"MPD_API_QUEUE_ADD_TRACK",data:{uri:app.current.search}});showNotification("Added all songs","","","success")}function addAllFromSearchPlist(a){2<=app.current.search.length&&(sendAPI({cmd:"MPD_API_DATABASE_SEARCH",data:{plist:a,filter:app.current.filter,searchstr:app.current.search,offset:0}}),showNotification("Added "+parseInt(document.getElementById("panel-heading-search").innerText)+" songs from search to "+a,"","","success"))}
function addAllFromBrowseFilesystem(){sendAPI({cmd:"MPD_API_QUEUE_ADD_TRACK",data:{uri:app.current.search}});showNotification("Added all songs","","","success")}
function addAllFromSearchPlist(a){var b=parseInt(document.getElementById("panel-heading-search").innerText);!isNaN(b)&&0<b&&(settings.featAdvsearch?sendAPI({cmd:"MPD_API_DATABASE_SEARCH_ADV",data:{plist:a,sort:"",sortdesc:!1,expression:app.current.search,offset:0}}):sendAPI({cmd:"MPD_API_DATABASE_SEARCH",data:{plist:a,filter:app.current.filter,searchstr:app.current.search,offset:0}}),showNotification("Added "+b+" songs from search to "+a,"","","success"))}
function addAllFromBrowseDatabasePlist(a){2<=app.current.search.length&&(sendAPI({cmd:"MPD_API_DATABASE_SEARCH",data:{plist:a,filter:app.current.view,searchstr:app.current.search,offset:0}}),showNotification("Added songs from database selection to "+a,"","","success"))}function scrollTo(a){document.body.scrollTop=a;document.documentElement.scrollTop=a}
function gotoPage(a){switch(a){case "next":app.current.page+=settings.maxElementsPerPage;break;case "prev":app.current.page-=settings.maxElementsPerPage;0>app.current.page&&(app.current.page=0);break;default:app.current.page=a}appGoto(app.current.app,app.current.tab,app.current.view,app.current.page+"/"+app.current.filter+"/"+app.current.search)}
function saveQueue(){var a=document.getElementById("saveQueueName").value,b=a.replace(/[\w\-]/g,"");""!=a&&""==b?(sendAPI({cmd:"MPD_API_QUEUE_SAVE",data:{plist:a}}),modalSavequeue.hide()):(alert(b),document.getElementById("saveQueueName").classList.add("is-invalid"),document.getElementById("saveQueueFrm").classList.add("was-validated"))}
@ -197,5 +207,5 @@ function doSetFilterLetter(a){var b=document.getElementById(a+"Letters").getElem
function addFilterLetter(a){for(var b='<button class="mr-1 mb-1 btn btn-sm btn-secondary material-icons material-icons-small">delete</button><button class="mr-1 mb-1 btn btn-sm btn-secondary">#</button>',c=65;90>=c;c++)b+='<button class="mr-1 mb-1 btn-sm btn btn-secondary">'+String.fromCharCode(c)+"</button>";a=document.getElementById(a);a.innerHTML=b;a.addEventListener("click",function(a){switch(a.target.innerText){case "delete":b="-";break;case "#":b="0";break;default:b=a.target.innerText}appGoto(app.current.app,
app.current.tab,app.current.view,"0/"+b+"/"+app.current.search)},!1)}function selectTag(a,b,c){a=document.getElementById(a);var d=a.querySelector(".active");d&&d.classList.remove("active");if(d=a.querySelector("[data-tag="+c+"]"))d.classList.add("active"),document.getElementById(b).innerText=d.innerText}
function addTagList(a,b){var c="";"searchtags"==b&&(1==settings.featTags&&(c+='<button type="button" class="btn btn-secondary btn-sm btn-block" data-tag="any">Any Tag</button>'),c+='<button type="button" class="btn btn-secondary btn-sm btn-block" data-tag="filename">Filename</button>');for(var d=0;d<settings[b].length;d++)c+='<button type="button" class="btn btn-secondary btn-sm btn-block" data-tag="'+settings[b][d]+'">'+settings[b][d]+"</button>";document.getElementById(a).innerHTML=c}
function gotoTagList(){appGoto(app.current.app,app.current.tab,app.current.view,"0/-/")}function openModal(a){window[a].show()}function openDropdown(a){window[a].toggle()}function focusSearch(){"Queue"==app.current.app?document.getElementById("searchqueuestr").focus():"Search"==app.current.app?document.getElementById("searchstr").focus():appGoto("Search")}
function gotoTagList(){appGoto(app.current.app,app.current.tab,app.current.view,"0/-/")}function openModal(a){window[a].show()}function openDropdown(a){window[a].toggle()}function focusSearch(){"Queue"==app.current.app?document.getElementById("searchqueuestr").focus():"Search"==app.current.app?domCache.searchstr.focus():appGoto("Search")}
function chVolume(a){a=parseInt(domCache.volumeBar.value)+a;0>a?a=0:100<a&&(a=100);domCache.volumeBar.value=a;sendAPI({cmd:"MPD_API_PLAYER_VOLUME_SET",data:{volume:a}})}function beautifyDuration(a){var b=Math.floor(a/86400),c=Math.floor(a/3600)-24*b,d=Math.floor(a/60)-60*c-1440*b;a=a-86400*b-3600*c-60*d;return(0<b?b+"\u2009d ":"")+(0<c?c+"\u2009h "+(10>d?"0":""):"")+d+"\u2009m "+(10>a?"0":"")+a+"\u2009s"}function genId(a){return"id"+a.replace(/[^\w\-]/g,"")}appInit();

View File

@ -1 +1 @@
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="description" content="myMPD - fast and lightweight MPD webclient"><meta name="author" content="mail@jcgames.de"><title>myMPD: Local Player</title><link href="css/bootstrap.min.css" rel="stylesheet"><link href="css/mympd.min.css" rel="stylesheet"><link href="assets/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon"><meta name="apple-mobile-web-app-capable" content="yes" /><meta name="apple-mobile-web-app-status-bar-style" content="black"/><link rel="apple-touch-icon" href="assets/appicon.png"/></head><body><header><nav class="navbar navbar-expand navbar-dark fixed-top bg-dark"><a class="navbar-brand" data-toggle="dropdown" href="#"><span class="material-icons header-logo">play_circle_outline</span>myMPD</a></nav></header><main role="main" class="container"><div class="card"><div class="card-header">Local Player</div><div class="card-body"><audio id="player" preload="none" controls=""></audio></div></div></main><script type="text/javascript" src="js/player.min.js"></script></body></html>
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="description" content="myMPD - fast and lightweight MPD webclient"><meta name="author" content="mail@jcgames.de"><title>myMPD: Local Player</title><link href="css/bootstrap.min.css" rel="stylesheet"><link href="css/mympd.min.css" rel="stylesheet"><link href="assets/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon"><meta name="apple-mobile-web-app-capable" content="yes" /><meta name="apple-mobile-web-app-status-bar-style" content="black"/><link rel="apple-touch-icon" href="assets/appicon.png"/></head><body><header><nav class="navbar navbar-expand navbar-dark fixed-top bg-dark"><a class="navbar-brand" href="#"><span class="material-icons header-logo">play_circle_outline</span>myMPD</a></nav></header><main role="main" class="container"><div class="card"><div class="card-header">Local Player</div><div class="card-body"><audio id="player" preload="none" controls=""></audio></div></div></main><script src="js/player.min.js"></script></body></html>

View File

@ -10,5 +10,5 @@ function(){function a(a){return function(d){c||(c=!0,a.call(b,d))}}var b=this,c=
void 0;try{d=a.then}catch(h){this.reject_(h);return}"function"==typeof d?this.settleSameAsThenable_(d,a):this.fulfill_(a)};c.prototype.reject_=function(a){this.settle_(2,a)};c.prototype.fulfill_=function(a){this.settle_(1,a)};c.prototype.settle_=function(a,b){if(0!=this.state_)throw Error("Cannot settle("+a+", "+b+"): Promise already settled in state"+this.state_);this.state_=a;this.result_=b;this.executeOnSettledCallbacks_()};c.prototype.executeOnSettledCallbacks_=function(){if(null!=this.onSettledCallbacks_){for(var a=
0;a<this.onSettledCallbacks_.length;++a)l.asyncExecute(this.onSettledCallbacks_[a]);this.onSettledCallbacks_=null}};var l=new b;c.prototype.settleSameAsPromise_=function(a){var b=this.createResolveAndReject_();a.callWhenSettled_(b.resolve,b.reject)};c.prototype.settleSameAsThenable_=function(a,b){var c=this.createResolveAndReject_();try{a.call(b,c.resolve,c.reject)}catch(k){c.reject(k)}};c.prototype.then=function(a,b){function d(a,b){return"function"==typeof a?function(b){try{f(a(b))}catch(m){e(m)}}:
b}var f,e,g=new c(function(a,b){f=a;e=b});this.callWhenSettled_(d(a,f),d(b,e));return g};c.prototype.catch=function(a){return this.then(void 0,a)};c.prototype.callWhenSettled_=function(a,b){function c(){switch(d.state_){case 1:a(d.result_);break;case 2:b(d.result_);break;default:throw Error("Unexpected state: "+d.state_);}}var d=this;null==this.onSettledCallbacks_?l.asyncExecute(c):this.onSettledCallbacks_.push(c)};c.resolve=f;c.reject=function(a){return new c(function(b,c){c(a)})};c.race=function(a){return new c(function(b,
c){for(var d=$jscomp.makeIterator(a),e=d.next();!e.done;e=d.next())f(e.value).callWhenSettled_(b,c)})};c.all=function(a){var b=$jscomp.makeIterator(a),d=b.next();return d.done?f([]):new c(function(a,c){function e(b){return function(c){g[b]=c;h--;0==h&&a(g)}}var g=[],h=0;do g.push(void 0),h++,f(d.value).callWhenSettled_(e(g.length-1),c),d=b.next();while(!d.done)})};return c},"es6","es3");var CACHE="myMPD-cache-v4.6.0",urlsToCache="/ /player.html /css/bootstrap.min.css /css/mympd.min.css /js/bootstrap-native-v4.min.js /js/mympd.min.js /js/player.min.js /js/keymap.min.js /assets/appicon-167.png /assets/appicon-192.png /assets/appicon-512.png /assets/coverimage-httpstream.png /assets/coverimage-notavailable.png /assets/coverimage-loading.png /assets/favicon.ico /assets/MaterialIcons-Regular.ttf /assets/MaterialIcons-Regular.woff /assets/MaterialIcons-Regular.woff2".split(" ");
c){for(var d=$jscomp.makeIterator(a),e=d.next();!e.done;e=d.next())f(e.value).callWhenSettled_(b,c)})};c.all=function(a){var b=$jscomp.makeIterator(a),d=b.next();return d.done?f([]):new c(function(a,c){function e(b){return function(c){g[b]=c;h--;0==h&&a(g)}}var g=[],h=0;do g.push(void 0),h++,f(d.value).callWhenSettled_(e(g.length-1),c),d=b.next();while(!d.done)})};return c},"es6","es3");var CACHE="myMPD-cache-v4.7.0",urlsToCache="/ /player.html /css/bootstrap.min.css /css/mympd.min.css /js/bootstrap-native-v4.min.js /js/mympd.min.js /js/player.min.js /js/keymap.min.js /assets/appicon-167.png /assets/appicon-192.png /assets/appicon-512.png /assets/coverimage-httpstream.png /assets/coverimage-notavailable.png /assets/coverimage-loading.png /assets/favicon.ico /assets/MaterialIcons-Regular.ttf /assets/MaterialIcons-Regular.woff /assets/MaterialIcons-Regular.woff2".split(" ");
self.addEventListener("install",function(a){a.waitUntil(caches.open(CACHE).then(function(a){return a.addAll(urlsToCache)}))});self.addEventListener("fetch",function(a){if(a.request.url.match("^http://"))return!1;a.respondWith(caches.match(a.request).then(function(b){return b?b:fetch(a.request)}))});self.addEventListener("activate",function(a){a.waitUntil(caches.keys().then(function(a){return Promise.all(a.map(function(a){if(a!=CACHE)return caches.delete(a)}))}))});

42
htdocs/apitest.html Normal file
View File

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="myMPD - fast and lightweight MPD webclient">
<meta name="author" content="mail@jcgames.de">
<title>myMPD: API Test</title>
<link href="css/bootstrap.min.css" rel="stylesheet">
<link href="css/mympd.min.css" rel="stylesheet">
<link href="assets/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon">
<style>
.td-ok { background-color: green; }
.td-error { background-color: red; }
</style>
</head>
<body>
<header>
<nav class="navbar navbar-expand navbar-dark fixed-top bg-dark">
<a class="navbar-brand" href="#">
<span class="material-icons header-logo">play_circle_outline</span>myMPD
</a>
</nav>
</header>
<main role="main" class="container">
<div class="card">
<div class="card-header">API Test<span class="pull-right" id="testCount"></span></div>
<div class="card-body">
<h2></h2>
<table class="table table-sm">
<thead>
<tr><th>#</th><th>Test</th><th>State</th><th>Response</th></tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</main>
<script src="js/apitest.js"></script>
</body>
</html>

View File

@ -321,3 +321,11 @@ div.key {
heigth: 20px;
text-align: center;
}
ol#searchCrumb {
padding: .5rem;
}
.nodropdown::after {
content: none;
}

View File

@ -102,7 +102,7 @@
<div class="card-footer cardFooterPlayback">
<div class="d-flex align-items-center">
<button data-href='{"cmd": "clickPlay", "options": []}' class="mr-1 ml-1 btn btn-light material-icons btnPlay progressBarPlay">pause</button>
<input type="range" min="0" max="100" step="1" class="mr-1 ml-1 form-control-range flex-grow-1" id="progressBar">
<input type="range" min="0" max="1000" step="1" class="mr-1 ml-1 form-control-range flex-grow-1" id="progressBar">
<div class="btn ml-1 mr-1" id="counter">&nbsp;&nbsp;</div>
</div>
</div>
@ -136,23 +136,24 @@
<div class="input-group-prepend">
<button type="button" class="btn btn-secondary material-icons" data-href='{"cmd": "sendAPI", "options": [{"cmd": "MPD_API_QUEUE_CLEAR"}]}' title="Clear queue">clear_all</button>
<button id="clearQueueBtn" class="btn btn-secondary dropdown-toggle dropdown-toggle-split rounded-right" type="button" data-toggle="dropdown"></button>
<div class="dropdown-menu bg-dark dropdown-menu-right px-2" id="clearQueueDropdown">
<div class="dropdown-menu bg-dark px-2" id="clearQueueDropdown">
<button type="button" class="btn btn-secondary btn-sm btn-block" data-href='{"cmd": "sendAPI", "options": [{"cmd": "MPD_API_QUEUE_CLEAR"}]}' >Clear queue</button>
<button type="button" class="btn btn-secondary btn-sm btn-block" data-href='{"cmd": "sendAPI", "options": [{"cmd": "MPD_API_QUEUE_CROP"}]}' >Crop queue</button>
</div>
</div>
</div>
<form id="searchqueue">
<form id="searchqueue" class="flex-grow-1">
<div class="input-group mr-2">
<input type="text" class="form-control" placeholder="Search Queue" id="searchqueuestr"/>
<div class="input-group-append">
<button title="Select tags to search" class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown">
<div class="input-group-prepend">
<button title="Select tag to search" class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown">
<span class="material-icons">search</span>
<span id="searchqueuetagsdesc">Any Tag</span>
</button>
<div class="dropdown-menu bg-dark dropdown-menu-right px-2" id="searchqueuetags">
<div class="dropdown-menu bg-dark px-2" id="searchqueuetags">
</div>
</div>
<input type="text" class="form-control" placeholder="Search Queue" id="searchqueuestr"/>
</div>
</form>
<div id="QueueCurrentPaginationTop" class="btn-group mr-2 hide">
@ -164,9 +165,9 @@
</div>
<button data-href='{"cmd": "gotoPage", "options": ["next"]}' id="QueueCurrentPaginationTopNext" title="Next Page" type="button" class="btn btn-secondary input-group-append">&raquo;</button>
</div>
<div class="btn-group mr-2 featTags">
<div class="btn-group featTags">
<button id="QueueCurrentColsBtn" class="btn btn-secondary dropdown-toggle material-icons" type="button" data-toggle="dropdown">settings</button>
<div class="dropdown-menu bg-dark px-2" id="QueueCurrentColsDropdown"><form></form>
<div class="dropdown-menu bg-dark px-2 dropdown-menu-right" id="QueueCurrentColsDropdown"><form></form>
<button data-href='{"cmd": "saveCols", "options": ["QueueCurrent"]}' class="btn btn-success btn-block btn-sm mt-2">Apply</button>
</div>
</div>
@ -373,9 +374,9 @@
</div>
<div class="input-group mr-2 hide">
<div class="input-group-prepend">
<button data-href='{"cmd": "addAllFromBrowseDatabase", "options": []}' id="BrowseDatabaseAddAllSongs" class="btn btn-secondary">Add all</button>
<button data-href='{"cmd": "addAllFromBrowseDatabase", "options": []}' id="BrowseDatabaseAddAllSongs" class="btn btn-secondary material-icons">library_add</button>
<button id="BrowseDatabaseAddAllSongsBtn" class="btn btn-secondary dropdown-toggle dropdown-toggle-split rounded-right" type="button" data-toggle="dropdown"></button>
<div class="dropdown-menu bg-dark dropdown-menu-right px-2" id="BrowseDatabaseAddAllSongsDropdown">
<div class="dropdown-menu bg-dark px-2" id="BrowseDatabaseAddAllSongsDropdown">
<button type="button" class="btn btn-secondary btn-sm btn-block">Add all to queue</button>
<button type="button" class="btn btn-secondary btn-sm btn-block featPlaylists">Add all to playlist</button>
</div>
@ -445,9 +446,9 @@
</div>
<div class="input-group mr-2">
<div class="input-group-prepend">
<button data-href='{"cmd": "addAllFromBrowseFilesystem", "options": []}' id="BrowseFilesystemAddAllSongs" class="btn btn-secondary">Add all</button>
<button data-href='{"cmd": "addAllFromBrowseFilesystem", "options": []}' id="BrowseFilesystemAddAllSongs" class="btn btn-secondary material-icons">library_add</button>
<button id="BrowseFilesystemAddAllSongsBtn" class="btn btn-secondary dropdown-toggle dropdown-toggle-split rounded-right" type="button" data-toggle="dropdown"></button>
<div class="dropdown-menu bg-dark dropdown-menu-right px-2" id="BrowseFilesystemAddAllSongsDropdown">
<div class="dropdown-menu bg-dark px-2" id="BrowseFilesystemAddAllSongsDropdown">
<button type="button" class="btn btn-secondary btn-sm btn-block">Add all to queue</button>
<button type="button" class="btn btn-secondary btn-sm btn-block featPlaylists">Add all to playlist</button>
</div>
@ -517,22 +518,31 @@
</div>
<div class="card-body">
<div class="btn-toolbar card-toolbar" id="SearchButtons">
<form id="search">
<form id="search" class="flex-grow-1">
<div class="input-group mr-2">
<input type="text" class="form-control" placeholder="Search" id="searchstr"/>
<div class="input-group-append">
<button title="Select tags to search" class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown">
<div class="input-group-prepend">
<button title="Select tag to search" class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown">
<span class="material-icons">search</span>
<span id="searchtagsdesc">Any Tag</span>
</button>
<div class="dropdown-menu bg-dark dropdown-menu-right px-2" id="searchtags">
<div class="dropdown-menu bg-dark px-2" id="searchtags">
</div>
</div>
<div class="input-group-prepend featAdvsearch">
<select class="form-control" id="searchMatch">
<option value="contains">contains</option>
<option value="==">==</option>
<option value="=~">=~</option>
<option value="!=">!=</option>
<option value="!~">!~</option>
</select>
</div>
<input type="text" class="form-control" placeholder="Search" id="searchstr"/>
</div>
</form>
<div class="input-group mr-2">
<div class="input-group-prepend">
<button id="searchAddAllSongs" class="btn btn-secondary" data-href='{"cmd": "addAllFromSearch", "options": []}'>Add all</button>
<button id="searchAddAllSongs" class="btn btn-secondary material-icons" data-href='{"cmd": "addAllFromSearchPlist", "options": ["queue"]}'>library_add</button>
<button id="searchAddAllSongsBtn" class="btn btn-secondary dropdown-toggle dropdown-toggle-split rounded-right" type="button" data-toggle="dropdown"></button>
<div class="dropdown-menu bg-dark dropdown-menu-right px-2" id="searchAddAllSongsDropdown">
<button type="button" class="btn btn-secondary btn-sm btn-block">Add all to queue</button>
@ -550,15 +560,17 @@
</div>
<button data-href='{"cmd": "gotoPage", "options": ["next"]}' id="SearchPaginationTopNext" title="Next Page" type="button" class="btn btn-secondary input-group-append">&raquo;</button>
</div>
<div class="btn-group mr-2 featTags">
<div class="btn-group featTags">
<button id="SearchColsBtn" class="btn btn-secondary dropdown-toggle material-icons" type="button" data-toggle="dropdown">settings</button>
<div class="dropdown-menu bg-dark px-2" id="SearchColsDropdown"><form></form>
<div class="dropdown-menu bg-dark px-2 dropdown-menu-right" id="SearchColsDropdown"><form></form>
<button data-href='{"cmd": "saveCols", "options": ["Search"]}' class="btn btn-success btn-block btn-sm mt-2">Apply</button>
</div>
</div>
</div>
<ol class="FeatAdvsearch breadcrumb" id="searchCrumb"></ol>
<div class="table-responsive-md">
<table id="SearchList" class="table table-hover table-sm">
<table id="SearchList" class="table table-hover table-sm" data-sort="">
<thead>
<tr>
<th></th>
@ -746,7 +758,7 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><span class="material-icons title-icon">playlist_add</span>Save smart playlist</h5>
<h5 class="modal-title"><span class="material-icons title-icon">queue_music</span>Save smart playlist</h5>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body">
@ -953,6 +965,7 @@
<tr><th>Play Time</th><td id="mpdstats_playtime"></td></tr>
<tr><td colspan="2" class="pt-3"><h5>MPD</h5></td></tr>
<tr><th>Protocol Version</th><td id="mpdVersion"></td></tr>
<tr><th>Libmpdclient Version</th><td id="libmpdclientVersion"></td></tr>
</tbody>
</table>
</div>

181
htdocs/js/apitest.js Normal file
View File

@ -0,0 +1,181 @@
"use strict";
/* myMPD
(c) 2018 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of:
ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: https://www.ympd.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
var i = 0;
var failed = 0;
var ok = 0;
var trackId = 0;
var outputId = 0;
var uri1 = '';
var album1 = '';
var artist1 = '';
var uri2 = '';
var album2 = '';
var artist2 = '';
var searchstr = 'tabula';
var time_start = 0;
var time_end = 0;
var time_all = 0;
var cmds = [
'{"cmd":"MPD_API_WELCOME"}',
'{"cmd":"MPD_API_QUEUE_CLEAR"}',
'{"cmd":"MPD_API_DATABASE_SEARCH","data":{"offset":0,"filter":"any","searchstr":"__SEARCHSTR__","plist":""}}',
'{"cmd":"MPD_API_QUEUE_ADD_TRACK","data":{"uri":"__URI1__"}}',
'{"cmd":"MPD_API_DATABASE_SEARCH","data":{"offset":0,"filter":"any","searchstr":"__SEARCHSTR__","plist":"queue"}}',
'{"cmd":"MPD_API_QUEUE_LIST","data":{"offset":0}}',
'{"cmd":"MPD_API_QUEUE_MOVE_TRACK","data":{"from":1,"to":2}}',
'{"cmd":"MPD_API_QUEUE_RM_RANGE","data":{"start":1,"end":-1}}',
'{"cmd":"MPD_API_PLAYER_PLAY_TRACK","data":{"track":__TRACKID__}}',
'{"cmd":"MPD_API_PLAYER_SEEK","data":{"songid":__TRACKID__,"seek":10}}',
'{"cmd":"MPD_API_QUEUE_RM_TRACK","data":{"track":__TRACKID__}}',
'{"cmd":"MPD_API_QUEUE_SAVE","data":{"plist":"test"}}',
'{"cmd":"MPD_API_QUEUE_SEARCH","data":{"offset":0,"filter":"any","searchstr":"__SEARCHSTR__"}}',
'{"cmd":"MPD_API_QUEUE_ADD_PLAY_TRACK","data":{"uri":"__URI2__"}}',
'{"cmd":"MPD_API_QUEUE_SHUFFLE"}',
'{"cmd":"MPD_API_QUEUE_REPLACE_TRACK","data":{"uri":"__URI1__"}}',
'{"cmd":"MPD_API_QUEUE_ADD_TRACK_AFTER","data":{"uri":"__URI1__","to":1}',
'{"cmd":"MPD_API_PLAYLIST_RENAME","data":{"from":"test","to":"test2"}}',
'{"cmd":"MPD_API_PLAYLIST_CONTENT_LIST","data":{"uri":"test2","offset":0,"filter":""}}',
'{"cmd":"MPD_API_QUEUE_ADD_PLAYLIST","data":{"plist":"test2"}}',
'{"cmd":"MPD_API_QUEUE_REPLACE_PLAYLIST","data":{"plist":"test2"}}',
'{"cmd":"MPD_API_PLAYLIST_LIST","data":{"offset":0,"filter":""}}',
'{"cmd":"MPD_API_PLAYLIST_CLEAR","data":{"uri":"test2"}}',
'{"cmd":"MPD_API_DATABASE_SEARCH","data":{"offset":0,"filter":"any","searchstr":"__SEARCHSTR__","plist":"test2"}}',
'{"cmd":"MPD_API_QUEUE_CROP"}',
'{"cmd":"MPD_API_QUEUE_ADD_TRACK","data":{"uri":"__URI1__"}}',
'{"cmd":"MPD_API_QUEUE_ADD_TRACK","data":{"uri":"__URI2__"}}',
'{"cmd":"MPD_API_PLAYER_PLAY"}',
'{"cmd":"MPD_API_QUEUE_LAST_PLAYED","data":{"offset":0}}',
'{"cmd":"MPD_API_PLAYLIST_ADD_TRACK","data":{"plist":"test2","uri":"__URI1__"}}',
'{"cmd":"MPD_API_PLAYLIST_ADD_TRACK","data":{"plist":"test2","uri":"__URI1__"}}',
'{"cmd":"MPD_API_PLAYLIST_MOVE_TRACK","data":{"plist":"test2","from":1,"to":2}}',
'{"cmd":"MPD_API_PLAYLIST_RM_TRACK","data":{"uri":"test2","track":1}',
'{"cmd":"MPD_API_PLAYLIST_RM","data":{"uri":"test2"}}',
'{"cmd":"MPD_API_DATABASE_SEARCH_ADV","data":{"offset":0,"expression":"(any contains \'__SEARCHSTR__\')","sort":"Title", "sortdesc":false,"plist":""}}',
'{"cmd":"MPD_API_DATABASE_UPDATE"}',
// '{"cmd":"MPD_API_DATABASE_RESCAN"}',
'{"cmd":"MPD_API_DATABASE_FILESYSTEM_LIST","data":{"offset":0,"filter":"","path":""}}',
'{"cmd":"MPD_API_DATABASE_TAG_LIST","data":{"offset":0,"filter":"","tag":"Artist"}}',
'{"cmd":"MPD_API_DATABASE_TAG_ALBUM_LIST","data":{"offset":0,"filter":"","search":"__ARTIST1__","tag":"Artist"}}',
'{"cmd":"MPD_API_DATABASE_TAG_ALBUM_TITLE_LIST","data":{"album":"__ALBUM1__","search":"__ARTIST1__","tag":"Artist"}}',
'{"cmd":"MPD_API_DATABASE_STATS"}',
'{"cmd":"MPD_API_DATABASE_SONGDETAILS","data":{"uri":"__URI1__"}}',
'{"cmd":"MPD_API_PLAYER_VOLUME_SET","data":{"volume":30}}',
'{"cmd":"MPD_API_PLAYER_VOLUME_GET"}',
'{"cmd":"MPD_API_PLAYER_NEXT"}',
'{"cmd":"MPD_API_PLAYER_CURRENT_SONG"}',
'{"cmd":"MPD_API_PLAYER_PREV"}',
'{"cmd":"MPD_API_PLAYER_PAUSE"}',
'{"cmd":"MPD_API_PLAYER_STOP"}',
'{"cmd":"MPD_API_PLAYER_OUTPUT_LIST"}',
'{"cmd":"MPD_API_PLAYER_TOGGLE_OUTPUT","data":{"output":"__OUTPUTID__","state":1}}',
'{"cmd":"MPD_API_PLAYER_STATE"}',
'{"cmd":"MPD_API_SETTINGS_GET"}',
'{"cmd":"MPD_API_SETTINGS_SET","data":{"random": 0}}',
'{"cmd":"MPD_API_LIKE","data":{"uri":"__URI2__","like":2}}',
'{"cmd":"MPD_API_SYSCMD","data":{"cmd": "Echo"}}',
'{"cmd":"MPD_API_COLS_SAVE","data":{"table":"colsPlayback","cols":["Artist","Album","AlbumArtist"]}}',
'{"cmd":"MPD_API_SMARTPLS_UPDATE_ALL"}',
'{"cmd":"MPD_API_SMARTPLS_SAVE","data":{"type":"newest","playlist":"myMPDsmart-newestSongs","timerange":2678400}}',
'{"cmd":"MPD_API_SMARTPLS_GET","data":{"playlist":"myMPDsmart-newestSongs"}}'
];
function setTest(cmd, state, response) {
if (state == "ok")
ok++;
else
failed++;
var duration = time_end - time_start;
time_all += duration;
document.getElementById('testCount').innerText = 'Test ' + (i + 1) + '/' + cmds.length + ' - ' +
ok + ' ok, ' + failed + ' failed, duration: ' + time_all + ' ms';
var tr = document.createElement('tr');
tr.innerHTML = '<td>' + (i + 1) + '</td><td>' + cmd + '</td><td class="td-' + state + '">' + duration + ' ms</td><td>' + response + '</td>';
document.getElementsByTagName('tbody')[0].appendChild(tr);
}
function sendAPI(request, callback) {
var ajaxRequest=new XMLHttpRequest();
ajaxRequest.open('POST', '/api', true);
ajaxRequest.setRequestHeader('Content-type', 'application/json');
ajaxRequest.onreadystatechange = function() {
if (ajaxRequest.readyState == 4) {
if (ajaxRequest.responseText != '') {
var obj;
try {
obj = JSON.parse(ajaxRequest.responseText);
time_end = new Date().getTime();
if (!obj.type)
setTest(request, 'error', 'Invalid response: ' + ajaxRequest.responseText);
else {
if (obj.type == 'search' && obj.data.length > 1) {
uri1 = obj.data[0].uri;
artist1 = obj.data[0].Artist;
album1 = obj.data[0].Album;
uri2 = obj.data[1].uri;
artist2 = obj.data[1].Artist;
album2 = obj.data[1].Album;
}
else if (obj.type == 'queue' && obj.data.length > 0)
trackId = obj.data[0].id;
else if (obj.type == 'outputs' && obj.data.length > 0)
ouputId = obj.data[0].id;
if (obj.type == 'error')
setTest(request, 'error', ajaxRequest.responseText);
else
setTest(request, 'ok', ajaxRequest.responseText);
}
}
catch(e) {
setTest(request, 'error', 'JSON parse error: ' + e);
}
}
else {
setTest(request, 'error', ajaxRequest.responseText);
}
i++;
if (i < cmds.length)
sendAPI(cmds[i]);
else
document.getElementsByTagName('h2')[0].innerText = 'Finished';
}
};
request = request.replace(/__TRACKID__/, trackId);
request = request.replace(/__OUTPUTID__/, outputId);
request = request.replace(/__URI1__/, uri1);
request = request.replace(/__ARTIST1__/, artist1);
request = request.replace(/__ALBUM1__/, album1);
request = request.replace(/__URI2__/, uri2);
request = request.replace(/__ARTIST2__/, artist2);
request = request.replace(/__ALBUM2__/, album2);
request = request.replace(/__SEARCHSTR__/, searchstr);
document.getElementsByTagName('h2')[0].innerText = 'Running ' + request;
time_start = new Date().getTime();
ajaxRequest.send(request);
}
sendAPI(cmds[i]);

View File

@ -90,6 +90,8 @@ domCache.currentTitle = document.getElementById('currentTitle');
domCache.btnVoteUp = document.getElementById('btnVoteUp');
domCache.btnVoteDown = document.getElementById('btnVoteDown');
domCache.badgeQueueItems = document.getElementById('badgeQueueItems');
domCache.searchstr = document.getElementById('searchstr');
domCache.searchCrumb = document.getElementById('searchCrumb');
var modalConnectionError = new Modal(document.getElementById('modalConnectionError'), { backdrop: 'static', keyboard: false});
var modalSettings = new Modal(document.getElementById('modalSettings'));
@ -274,10 +276,31 @@ function appRoute() {
doSetFilterLetter('BrowseFilesystemFilter');
}
else if (app.current.app == 'Search') {
var searchstrEl = document.getElementById('searchstr');
searchstrEl.focus();
if (searchstrEl.value == '' && app.current.search != '')
searchstrEl.value = app.current.search;
domCache.searchstr.focus();
if (settings.featAdvsearch) {
var crumbs = '';
var elements = app.current.search.substring(1, app.current.search.length - 1).split(' AND ');
for (var i = 0; i < elements.length - 1 ; i++) {
var value = elements[i].substring(1, elements[i].length - 1);
crumbs += '<button data-filter="' + encodeURI(value) + '" class="btn btn-light mr-2">' + value + '<span class="ml-2 badge badge-secondary">&times</span></button>';
}
domCache.searchCrumb.innerHTML = crumbs;
if (domCache.searchstr.value == '' && elements.length >= 1) {
var lastEl = elements[elements.length - 1].substring(1, elements[elements.length - 1].length - 1);
var lastElValue = lastEl.substring(lastEl.indexOf('\'') + 1, lastEl.length - 1);
if (domCache.searchstr.value != lastElValue)
domCache.searchCrumb.innerHTML += '<button data-filter="' + encodeURI(lastEl) +'" class="btn btn-light mr-2">' + lastEl + '<span href="#" class="ml-2 badge badge-secondary">&times;</span></button>';
var match = lastEl.substring(lastEl.indexOf(' ') + 1);
match = match.substring(0, match.indexOf(' '));
if (match == '')
match = 'contains';
document.getElementById('searchMatch').value = match;
}
}
else {
if (domCache.searchstr.value == '' && app.current.search != '')
domCache.searchstr.value = app.current.search;
}
if (app.last.app != app.current.app) {
if (app.current.search != '') {
var colspan = settings['cols' + app.current.app].length;
@ -288,15 +311,36 @@ function appRoute() {
}
}
if (app.current.search.length >= 2) {
sendAPI({"cmd": "MPD_API_DATABASE_SEARCH", "data": { "plist": "", "offset": app.current.page, "filter": app.current.filter, "searchstr": app.current.search}}, parseSearch);
if (domCache.searchstr.value.length >= 2 || domCache.searchCrumb.children.length > 0) {
if (settings.featAdvsearch) {
var sort = document.getElementById('SearchList').getAttribute('data-sort');
var sortdesc = false;
if (sort == '') {
if (settings.tags.includes('Title'))
sort = 'Title';
else
sort = 'Filename';
document.getElementById('SearchList').setAttribute('data-sort', sort);
}
else {
if (sort.indexOf('-') == 0) {
sortdesc = true;
sort = sort.substring(1);
}
}
sendAPI({"cmd": "MPD_API_DATABASE_SEARCH_ADV", "data": { "plist": "", "offset": app.current.page, "sort": sort, "sortdesc": sortdesc, "expression": app.current.search}}, parseSearch);
}
else {
sendAPI({"cmd": "MPD_API_DATABASE_SEARCH", "data": { "plist": "", "offset": app.current.page, "filter": app.current.filter, "searchstr": app.current.search}}, parseSearch);
}
} else {
document.getElementById('SearchList').getElementsByTagName('tbody')[0].innerHTML = '';
document.getElementById('searchAddAllSongs').setAttribute('disabled', 'disabled');
document.getElementById('searchAddAllSongsBtn').setAttribute('disabled', 'disabled');
document.getElementById('panel-heading-search').innerText = '';
document.getElementById('cardFooterSearch').innerText = '';
document.getElementById('SearchList').classList.remove('opacity05');
setPagination(0);
setPagination(0, 0);
}
selectTag('searchtags', 'searchtagsdesc', app.current.filter);
}
@ -310,11 +354,10 @@ function appRoute() {
};
function appInit() {
webSocketConnect();
getSettings();
sendAPI({"cmd": "MPD_API_PLAYER_STATE"}, parseState);
webSocketConnect();
domCache.volumeBar.value = 0;
document.getElementById('btnChVolumeDown').addEventListener('click', function(event) {
@ -572,8 +615,10 @@ function appInit() {
}, false);
document.getElementById('searchtags').addEventListener('click', function(event) {
if (event.target.nodeName == 'BUTTON')
appGoto(app.current.app, app.current.tab, app.current.view, '0/' + event.target.getAttribute('data-tag') + '/' + app.current.search);
if (event.target.nodeName == 'BUTTON') {
app.current.filter = event.target.getAttribute('data-tag');
search(domCache.searchstr.value);
}
}, false);
document.getElementById('searchqueuestr').addEventListener('keyup', function(event) {
@ -605,11 +650,84 @@ function appInit() {
return false;
}, false);
document.getElementById('searchstr').addEventListener('keyup', function(event) {
domCache.searchstr.addEventListener('keyup', function(event) {
if (event.key == 'Escape')
this.blur();
else if (event.key == 'Enter' && settings.featAdvsearch) {
if (this.value != '') {
var match = document.getElementById('searchMatch');
var li = document.createElement('button');
li.classList.add('btn', 'btn-light', 'mr-2');
li.setAttribute('data-filter', encodeURI(app.current.filter + ' ' + match.options[match.selectedIndex].value +' \'' + this.value + '\''));
li.innerHTML = app.current.filter + ' ' + match.options[match.selectedIndex].value + ' \'' + this.value + '\'<span class="ml-2 badge badge-secondary">&times;</span>';
this.value = '';
domCache.searchCrumb.appendChild(li);
}
else
search(this.value);
}
else
appGoto('Search', undefined, undefined, '0/' + app.current.filter + '/' + this.value);
search(this.value);
}, false);
domCache.searchCrumb.addEventListener('click', function(event) {
event.preventDefault();
event.stopPropagation();
if (event.target.nodeName == 'SPAN') {
event.target.parentNode.remove();
search('');
}
else if (event.target.nodeName == 'BUTTON') {
var value = decodeURI(event.target.getAttribute('data-filter'));
domCache.searchstr.value = value.substring(value.indexOf('\'') + 1, value.length - 1);
var filter = value.substring(0, value.indexOf(' '));
selectTag('searchtags', 'searchtagsdesc', filter);
var match = value.substring(value.indexOf(' ') + 1);
match = match.substring(0, match.indexOf(' '));
document.getElementById('searchMatch').value = match;
event.target.remove();
search(domCache.searchstr.value);
}
}, false);
document.getElementById('searchMatch').addEventListener('change', function(event) {
search(domCache.searchstr.value);
}, false);
document.getElementById('SearchList').getElementsByTagName('tr')[0].addEventListener('click', function(event) {
if (settings.featAdvsearch) {
if (event.target.nodeName == 'TH') {
var col = event.target.getAttribute('data-col');
if (col == 'Duration')
return;
var sortcol = document.getElementById('SearchList').getAttribute('data-sort');
var sortdesc = true;
if (sortcol == col || sortcol == '-' + col) {
if (sortcol.indexOf('-') == 0) {
sortdesc = true;
sortcol = sortcol.substring(1);
}
else
sortdesc = false;
}
if (sortdesc == false) {
sortcol = '-' + col;
sortdesc = true;
}
else {
sortdesc = false;
sortcol = col;
}
var s = document.getElementById('SearchList').getElementsByClassName('sort-dir');
for (var i = 0; i < s.length; i++)
s[i].remove();
document.getElementById('SearchList').setAttribute('data-sort', sortcol);
event.target.innerHTML = col + '<span class="sort-dir material-icons pull-right">' + (sortdesc == true ? 'arrow_drop_up' : 'arrow_drop_down') + '</span>';
appRoute();
}
}
}, false);
document.getElementById('BrowseDatabaseByTagDropdown').addEventListener('click', function(event) {
@ -634,8 +752,8 @@ function appInit() {
window.addEventListener('focus', function() {
sendAPI({"cmd": "MPD_API_PLAYER_STATE"}, parseState);
}, false);
document.addEventListener('keydown', function(event) {
if (event.target.tagName == 'INPUT' || event.target.tagName == 'SELECT' ||
event.ctrlKey || event.altKey)
@ -712,6 +830,28 @@ function parseCmd(event, href) {
}
}
function search(x) {
if (settings.featAdvsearch) {
var expression = '(';
var crumbs = domCache.searchCrumb.children;
for (var i = 0; i < crumbs.length; i++) {
expression += '(' + decodeURI(crumbs[i].getAttribute('data-filter')) + ')';
if (x != '') expression += ' AND ';
}
if (x != '') {
var match = document.getElementById('searchMatch');
expression += '(' + app.current.filter + ' ' + match.options[match.selectedIndex].value + ' \'' + x +'\'))';
}
else
expression += ')';
if (expression.length <= 2)
expression = '';
appGoto('Search', undefined, undefined, '0/' + app.current.filter + '/' + encodeURI(expression));
}
else
appGoto('Search', undefined, undefined, '0/' + app.current.filter + '/' + x);
}
function dragAndDropTable(table) {
var tableBody=document.getElementById(table).getElementsByTagName('tbody')[0];
tableBody.addEventListener('dragstart', function(event) {
@ -969,6 +1109,7 @@ function parseStats(obj) {
document.getElementById('mpdstats_dbUpdated').innerText = d.toUTCString();
document.getElementById('mympdVersion').innerText = obj.data.mympdVersion;
document.getElementById('mpdVersion').innerText = obj.data.mpdVersion;
document.getElementById('libmpdclientVersion').innerText = obj.data.libmpdclientVersion;
}
function toggleBtn(btn, state) {
@ -1058,7 +1199,7 @@ function parseSettings(obj) {
toggleBtn('btnnotifyPage', settings.notificationPage);
var features = ["featStickers", "featSmartpls", "featPlaylists", "featTags", "featLocalplayer", "featSyscmds", "featCoverimage"];
var features = ["featStickers", "featSmartpls", "featPlaylists", "featTags", "featLocalplayer", "featSyscmds", "featCoverimage", "featAdvsearch"];
for (var j = 0; j < features.length; j++) {
var Els = document.getElementsByClassName(features[j]);
@ -1220,6 +1361,14 @@ function setCols(table, className) {
}
document.getElementById(table + 'ColsDropdown').firstChild.innerHTML = tagChks;
var sort = document.getElementById('SearchList').getAttribute('data-sort');
if (sort == '') {
if (settings.featTags)
sort = 'Title';
else
sort = 'Filename';
}
if (table != 'Playback') {
var heading = '';
for (var i = 0; i < settings['cols' + table].length; i++) {
@ -1227,9 +1376,20 @@ function setCols(table, className) {
heading += '<th draggable="true" data-col="' + h + '">';
if (h == 'Track' || h == 'Pos')
h = '#';
heading += h + '</th>';
heading += h;
if (table == 'Search' && h == sort ) {
var sortdesc = false;
if (sort.indexOf('-') == 0) {
sortdesc = true;
sort = sort.substring(1);
}
heading += '<span class="sort-dir material-icons pull-right">' + (sortdesc == true ? 'arrow_drop_up' : 'arrow_drop_down') + '</span>';
}
heading += '</th>';
}
heading += '<th></th>';
if (className == undefined)
document.getElementById(table + 'List').getElementsByTagName('tr')[0].innerHTML = heading;
else {
@ -1329,7 +1489,7 @@ function setCounter(currentSongId, totalTime, elapsedTime) {
var elapsed_minutes = Math.floor(elapsedTime / 60);
var elapsed_seconds = elapsedTime - elapsed_minutes * 60;
domCache.progressBar.value = Math.floor(100 * elapsedTime / totalTime);
domCache.progressBar.value = Math.floor(1000 * elapsedTime / totalTime);
var counterText = elapsed_minutes + ":" +
(elapsed_seconds < 10 ? '0' : '') + elapsed_seconds + " / " +
@ -1434,7 +1594,7 @@ function parseState(obj) {
pb[i].innerText = '';
}
if (app.current.app == 'LastPlayed')
if (app.current.app == 'Queue' && app.current.tab == 'LastPlayed')
sendAPI({"cmd": "MPD_API_QUEUE_LAST_PLAYED", "data": {"offset": app.current.page}}, parseLastPlayed);
lastState = obj;
@ -1516,7 +1676,7 @@ function parseQueue(obj) {
tbody.innerHTML = '<tr><td><span class="material-icons">error_outline</span></td>' +
'<td colspan="' + colspan + '">Empty queue</td></tr>';
setPagination(obj.totalEntities);
setPagination(obj.totalEntities, obj.returnedEntities);
document.getElementById('QueueCurrentList').classList.remove('opacity05');
}
@ -1560,14 +1720,25 @@ function parseLastPlayed(obj) {
tbody.innerHTML = '<tr><td><span class="material-icons">error_outline</span></td>' +
'<td colspan="' + colspan + '">Empty list</td></tr>';
setPagination(obj.totalEntities);
setPagination(obj.totalEntities, obj.returnedEntities);
document.getElementById('QueueLastPlayedList').classList.remove('opacity05');
}
function parseSearch(obj) {
document.getElementById('panel-heading-search').innerHTML = obj.totalEntities + ' Songs found';
document.getElementById('cardFooterSearch').innerHTML = obj.totalEntities + ' Songs found';
if (obj.totalEntities > 0) {
if (obj.totalEntities > -1) {
document.getElementById('panel-heading-search').innerText = obj.totalEntities + ' Songs found';
document.getElementById('cardFooterSearch').innerText = obj.totalEntities + ' Songs found';
}
else if (obj.returnedEntities + app.current.page < settings.maxElementsPerPage) {
document.getElementById('panel-heading-search').innerText = obj.returnedEntities + ' Songs found';
document.getElementById('cardFooterSearch').innerText = obj.returnedEntities + ' Songs found';
}
else {
document.getElementById('panel-heading-search').innerHTML = '&ge; ' + settings.maxElementsPerPage + ' Songs found';
document.getElementById('cardFooterSearch').innerHTML = '&ge; ' + settings.maxElementsPerPage + ' Songs found';
}
if (obj.returnedEntities > 0) {
document.getElementById('searchAddAllSongs').removeAttribute('disabled');
document.getElementById('searchAddAllSongsBtn').removeAttribute('disabled');
}
@ -1606,7 +1777,7 @@ function parseFilesystem(obj) {
if (obj.data[i].Type == 'dir')
tds += '<span class="material-icons">folder_open</span>';
else
tds += '<span class="material-icons">list</span>';
tds += '<span class="material-icons">' + (obj.data[i].Type == 'smartpls' ? 'queue_music' :'list') + '</span>';
}
else if (settings['cols' + list][c] == 'Title')
tds += obj.data[i].name;
@ -1642,7 +1813,7 @@ function parseFilesystem(obj) {
tr[i].remove();
}
setPagination(obj.totalEntities);
setPagination(obj.totalEntities, obj.returnedEntities);
if (nrItems == 0)
tbody.innerHTML = '<tr><td><span class="material-icons">error_outline</span></td>' +
@ -1692,7 +1863,7 @@ function parsePlaylists(obj) {
row.setAttribute('data-uri', uri);
row.setAttribute('data-type', obj.data[i].Type);
row.setAttribute('data-name', obj.data[i].name);
row.innerHTML = '<td data-col="Type"><span class="material-icons">list</span></td>' +
row.innerHTML = '<td data-col="Type"><span class="material-icons">' + (obj.data[i].Type == 'smartpls' ? 'queue_music' :'list') + '</span></td>' +
'<td>' + obj.data[i].name + '</td>' +
'<td>'+ d.toUTCString() + '</td>' +
'<td data-col="Action"><a href="#" class="material-icons color-darkgrey">playlist_add</a></td>';
@ -1736,7 +1907,7 @@ function parsePlaylists(obj) {
tr[i].remove();
}
setPagination(obj.totalEntities);
setPagination(obj.totalEntities, obj.returnedEntities);
if (nrItems == 0) {
var colspan = settings['cols' + list].length;
@ -1796,7 +1967,7 @@ function parseListDBtags(obj) {
for (var i = cardsLen; i >= nrItems; i --) {
cards[i].remove();
}
setPagination(obj.totalEntities);
setPagination(obj.totalEntities, obj.returnedEntities);
setCols('BrowseDatabase', '.tblAlbumTitles');
var tbls = document.querySelectorAll('.tblAlbumTitles');
for (var i = 0; i < tbls.length; i++)
@ -1833,7 +2004,7 @@ function parseListDBtags(obj) {
tr[i].remove();
}
setPagination(obj.totalEntities);
setPagination(obj.totalEntities, obj.returnedEntities);
if (nrItems == 0)
tbody.innerHTML = '<tr><td><span class="material-icons">error_outline</span></td>' +
@ -1913,9 +2084,9 @@ function parseListTitles(obj) {
}, false);
}
function setPagination(number) {
var totalPages = Math.ceil(number / settings.maxElementsPerPage);
function setPagination(total, returned) {
var cat = app.current.app + (app.current.tab == undefined ? '': app.current.tab);
var totalPages = Math.ceil(total / settings.maxElementsPerPage);
if (totalPages == 0)
totalPages = 1;
var p = ['PaginationTop', 'PaginationBottom'];
@ -1929,15 +2100,24 @@ function setPagination(number) {
( j + 1) + '</button>';
}
document.getElementById(cat + p[i] + 'Pages').innerHTML = pl;
} else {
document.getElementById(cat + p[i] + 'Page').classList.remove('nodropdown');
}
else if (total == -1) {
document.getElementById(cat + p[i] + 'Page').setAttribute('disabled', 'disabled');
document.getElementById(cat + p[i] + 'Page').innerText = (app.current.page / settings.maxElementsPerPage + 1);
document.getElementById(cat + p[i] + 'Page').classList.add('nodropdown');
}
else {
document.getElementById(cat + p[i] + 'Page').setAttribute('disabled', 'disabled');
document.getElementById(cat + p[i] + 'Page').classList.add('nodropdown');
}
if (number > app.current.page + settings.maxElementsPerPage) {
if (total > app.current.page + settings.maxElementsPerPage || total == -1 && returned >= settings.maxElementsPerPage) {
document.getElementById(cat + p[i] + 'Next').removeAttribute('disabled');
document.getElementById(cat + p[i]).classList.remove('hide');
document.getElementById(cat + 'ButtonsBottom').classList.remove('hide');
} else {
}
else {
document.getElementById(cat + p[i] + 'Next').setAttribute('disabled', 'disabled');
document.getElementById(cat + p[i]).classList.add('hide');
document.getElementById(cat + 'ButtonsBottom').classList.add('hide');
@ -2100,7 +2280,7 @@ function updateSmartPlaylists() {
}
function voteSong(vote) {
var uri = domCache.currentTitle.getAttribute('data-uri');
var uri = decodeURI(domCache.currentTitle.getAttribute('data-uri'));
if (uri == '')
return;
@ -2196,10 +2376,18 @@ function parseSmartPlaylist(obj) {
document.getElementById('saveSmartPlaylistSearch').classList.remove('hide');
document.getElementById('selectSaveSmartPlaylistTag').value = obj.data.tag;
document.getElementById('inputSaveSmartPlaylistSearchstr').value = obj.data.searchstr;
if (settings.featAdvsearch) {
document.getElementById('selectSaveSmartPlaylistTag').parentNode.classList.add('hide');
document.getElementById('inputSaveSmartPlaylistSearchstr').parentNode.classList.replace('col-md-6','col-md-12');
}
else {
document.getElementById('selectSaveSmartPlaylistTag').parentNode.classList.remove('hide');
document.getElementById('inputSaveSmartPlaylistSearchstr').parentNode.classList.replace('col-md-12','col-md-6');
}
}
else if (obj.data.type == 'sticker') {
document.getElementById('saveSmartPlaylistSticker').classList.remove('hide');
document.getElementById('selectSaveSmartPlaylistSticker').value = obj.data.feat_sticker;
document.getElementById('selectSaveSmartPlaylistSticker').value = obj.data.sticker;
document.getElementById('inputSaveSmartPlaylistStickerMaxentries').value = obj.data.maxentries;
}
else if (obj.data.type == 'newest') {
@ -2232,6 +2420,8 @@ function saveSmartPlaylist() {
if (type == 'search') {
var tagEl = document.getElementById('selectSaveSmartPlaylistTag');
var tag = tagEl.options[tagEl.selectedIndex].value;
if (settings.featAdvsearch)
tag = 'expression';
var searchstr = document.getElementById('inputSaveSmartPlaylistSearchstr').value;
sendAPI({"cmd": "MPD_API_SMARTPLS_SAVE", "data": {"type": type, "playlist": name, "tag": tag, "searchstr": searchstr}});
} else if (type == 'sticker') {
@ -2448,9 +2638,8 @@ function showMenu(el, event) {
addMenuItem({"cmd": "replaceQueue", "options": [type, uri, name]},'Replace queue') +
(type == 'smartpls' ? addMenuItem({"cmd": "playlistDetails", "options": [uri]}, 'View playlist') : addMenuItem({"cmd": "playlistDetails", "options": [uri]}, 'Edit playlist'))+
(type == 'smartpls' ? addMenuItem({"cmd": "showSmartPlaylist", "options": [uri]}, 'Edit smart playlist') : '') +
(uri.indexOf('myMPDsmart') != 0 ?
addMenuItem({"cmd": "showRenamePlaylist", "options": [uri]}, 'Rename playlist') +
addMenuItem({"cmd": "showDelPlaylist", "options": [uri]}, 'Delete playlist') : '');
addMenuItem({"cmd": "showRenamePlaylist", "options": [uri]}, 'Rename playlist') +
addMenuItem({"cmd": "showDelPlaylist", "options": [uri]}, 'Delete playlist');
}
else if (app.current.app == 'Browse' && app.current.tab == 'Playlists' && app.current.view == 'Detail') {
var x = document.getElementById('BrowsePlaylistsDetailList');
@ -2700,9 +2889,13 @@ function addAllFromBrowseFilesystem() {
}
function addAllFromSearchPlist(plist) {
if (app.current.search.length >= 2) {
sendAPI({"cmd": "MPD_API_DATABASE_SEARCH", "data": {"plist": plist, "filter": app.current.filter, "searchstr": app.current.search, "offset": 0}});
showNotification('Added '+ parseInt(document.getElementById('panel-heading-search').innerText) +' songs from search to ' + plist, '', '', 'success');
var nr = parseInt(document.getElementById('panel-heading-search').innerText);
if (!isNaN(nr) && nr > 0) {
if (settings.featAdvsearch)
sendAPI({"cmd": "MPD_API_DATABASE_SEARCH_ADV", "data": {"plist": plist, "sort": "", "sortdesc": false, "expression": app.current.search, "offset": 0}});
else
sendAPI({"cmd": "MPD_API_DATABASE_SEARCH", "data": {"plist": plist, "filter": app.current.filter, "searchstr": app.current.search, "offset": 0}});
showNotification('Added ' + nr +' songs from search to ' + plist, '', '', 'success');
}
}
@ -2936,7 +3129,7 @@ function focusSearch() {
if (app.current.app == 'Queue')
document.getElementById('searchqueuestr').focus();
else if (app.current.app == 'Search')
document.getElementById('searchstr').focus();
domCache.searchstr.focus();
else
appGoto('Search');
}

View File

@ -16,7 +16,7 @@
<body>
<header>
<nav class="navbar navbar-expand navbar-dark fixed-top bg-dark">
<a class="navbar-brand" data-toggle="dropdown" href="#">
<a class="navbar-brand" href="#">
<span class="material-icons header-logo">play_circle_outline</span>myMPD
</a>
</nav>
@ -29,6 +29,6 @@
</div>
</div>
</main>
<script type="text/javascript" src="js/player.min.js"></script>
<script src="js/player.min.js"></script>
</body>
</html>

View File

@ -1,4 +1,4 @@
var CACHE = 'myMPD-cache-v4.6.1';
var CACHE = 'myMPD-cache-v4.7.0';
var urlsToCache = [
'/',
'/player.html',

View File

@ -1,3 +1,23 @@
/* myMPD
(c) 2018 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
This linked list implementation is based on: https://github.com/joshkunz/ashuffle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdlib.h>
#include <string.h>
#include <time.h>
@ -35,7 +55,6 @@ struct node *list_node_at(const struct list *l, unsigned index) {
return current;
}
int list_swap_item(struct node *n1, struct node *n2) {
if (n1 == n2)
return 1;
@ -44,19 +63,14 @@ int list_swap_item(struct node *n1, struct node *n2) {
return 1;
int value = n2->value;
char *data = strdup(n2->data);
char *data = n2->data;
n2->value = n1->value;
n2->data = realloc(n2->data, strlen(n1->data) + 1);
if (n2->data)
strcpy(n2->data, n1->data);
n2->data = n1->data;
n1->value = value;
n1->data = realloc(n1->data, strlen(data) + 1);
if (n1->data)
strcpy(n1->data, data);
n1->data = data;
free(data);
return 0;
}

View File

@ -1,3 +1,23 @@
/* myMPD
(c) 2018 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
This linked list implementation is based on: https://github.com/joshkunz/ashuffle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
struct node {
char *data;
long value;

File diff suppressed because it is too large Load Diff

View File

@ -30,7 +30,7 @@
#define RETURN_ERROR_AND_RECOVER(X) do { \
printf("MPD %s: %s\n", X, mpd_connection_get_error_message(mpd.conn)); \
len = json_printf(&out, "{ type:error, data : %Q }", mpd_connection_get_error_message(mpd.conn)); \
len = json_printf(&out, "{type: error, data: %Q}", mpd_connection_get_error_message(mpd.conn)); \
if (!mpd_connection_clear_error(mpd.conn)) \
mpd.conn_state = MPD_FAILURE; \
return len; \
@ -65,6 +65,10 @@
} while (0)
#define LOG_INFO() if (config.loglevel >= 1)
#define LOG_VERBOSE() if (config.loglevel >= 2)
#define LOG_DEBUG() if (config.loglevel == 3)
#define MAX_SIZE 2048 * 400
#define MAX_ELEMENTS_PER_PAGE 400
@ -98,6 +102,7 @@
X(MPD_API_SMARTPLS_UPDATE_ALL) \
X(MPD_API_SMARTPLS_SAVE) \
X(MPD_API_SMARTPLS_GET) \
X(MPD_API_DATABASE_SEARCH_ADV) \
X(MPD_API_DATABASE_SEARCH) \
X(MPD_API_DATABASE_UPDATE) \
X(MPD_API_DATABASE_RESCAN) \
@ -122,7 +127,6 @@
X(MPD_API_PLAYER_STATE) \
X(MPD_API_SETTINGS_GET) \
X(MPD_API_SETTINGS_SET) \
X(MPD_API_MESSAGE_SEND) \
X(MPD_API_WELCOME) \
X(MPD_API_LIKE) \
X(MPD_API_SYSCMD) \
@ -167,6 +171,7 @@ struct t_mpd {
bool feat_playlists;
bool feat_tags;
bool feat_library;
bool feat_advsearch;
} mpd;
struct list mpd_tags;
@ -202,6 +207,7 @@ typedef struct {
long streamport;
const char *streamurl;
unsigned long last_played_count;
long loglevel;
} t_config;
t_config config;
@ -239,14 +245,14 @@ void mympd_idle(struct mg_mgr *sm, int timeout);
void mympd_parse_idle(struct mg_mgr *s, int idle_bitmask);
void callback_mympd(struct mg_connection *nc, const struct mg_str msg);
void mympd_notify(struct mg_mgr *s);
void mympd_count_song_id(int song_id, char *name, int value);
void mympd_count_song_uri(const char *uri, char *name, int value);
void mympd_like_song_uri(const char *uri, int value);
void mympd_last_played_song_uri(const char *uri);
void mympd_last_played_song_id(int song_id);
void mympd_get_sticker(const char *uri, t_sticker *sticker);
void mympd_last_played_list(int song_id);
void mympd_jukebox();
bool mympd_count_song_id(int song_id, char *name, int value);
bool mympd_count_song_uri(const char *uri, char *name, int value);
bool mympd_like_song_uri(const char *uri, int value);
bool mympd_last_played_song_uri(const char *uri);
bool mympd_last_played_song_id(int song_id);
bool mympd_get_sticker(const char *uri, t_sticker *sticker);
bool mympd_last_played_list(int song_id);
bool mympd_jukebox();
bool mympd_state_get(char *name, char *value);
bool mympd_state_set(const char *name, const char *value);
int mympd_syscmd(char *buffer, char *cmd, int order);
@ -254,17 +260,17 @@ int mympd_smartpls_save(char *smartpltype, char *playlist, char *tag, char *sear
int mympd_smartpls_put(char *buffer, char *playlist);
int mympd_smartpls_update_all();
int mympd_smartpls_clear(char *playlist);
int mympd_smartpls_update(char *sticker, char *playlist, int maxentries);
int mympd_smartpls_update_sticker(char *playlist, char *sticker, int maxentries);
int mympd_smartpls_update_newest(char *playlist, int timerange);
int mympd_smartpls_update_search(char *playlist, char *tag, char *searchstr);
int mympd_get_updatedb_state(char *buffer);
void mympd_get_song_uri_from_song_id(int song_id, char *uri);
int mympd_put_state(char *buffer, int *current_song_id, int *next_song_id, int *last_song_id, unsigned *queue_version, unsigned *queue_length);
int mympd_put_outputs(char *buffer);
int mympd_put_current_song(char *buffer);
int mympd_put_queue(char *buffer, unsigned int offset, unsigned *queue_version, unsigned *queue_length);
int mympd_put_browse(char *buffer, char *path, unsigned int offset, char *filter);
int mympd_search(char *buffer, char *searchstr, char *filter, char *plist, unsigned int offset);
int mympd_search_adv(char *buffer, char *expression, char *sort, bool sortdesc, char *grouptag, char *plist, unsigned int offset);
int mympd_search_queue(char *buffer, char *mpdtagtype, unsigned int offset, char *searchstr);
int mympd_put_welcome(char *buffer);
int mympd_put_volume(char *buffer);

View File

@ -30,6 +30,7 @@
#include <pwd.h>
#include <grp.h>
#include <libgen.h>
#include <mpd/client.h>
#include "../dist/src/mongoose/mongoose.h"
#include "../dist/src/frozen/frozen.h"
@ -64,29 +65,23 @@ static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
switch(ev) {
case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: {
struct http_message *hm = (struct http_message *) ev_data;
#ifdef DEBUG
fprintf(stderr, "New websocket request: %.*s\n", hm->uri.len, hm->uri.p);
#endif
LOG_VERBOSE() printf("New websocket request: %.*s\n", hm->uri.len, hm->uri.p);
if (mg_vcmp(&hm->uri, "/ws") != 0) {
printf("Websocket request not to /ws, closing connection\n");
printf("ERROR: Websocket request not to /ws, closing connection\n");
mg_printf(nc, "%s", "HTTP/1.1 403 FORBIDDEN\r\n\r\n");
nc->flags |= MG_F_SEND_AND_CLOSE;
}
break;
}
case MG_EV_WEBSOCKET_HANDSHAKE_DONE: {
#ifdef DEBUG
fprintf(stderr, "New Websocket connection established\n");
#endif
struct mg_str d = mg_mk_str("{\"cmd\": \"MPD_API_WELCOME\"}");
LOG_VERBOSE() printf("New Websocket connection established\n");
struct mg_str d = mg_mk_str("{\"cmd\":\"MPD_API_WELCOME\"}");
callback_mympd(nc, d);
break;
}
case MG_EV_HTTP_REQUEST: {
struct http_message *hm = (struct http_message *) ev_data;
#ifdef DEBUG
fprintf(stderr, "HTTP request: %.*s\n", hm->uri.len, hm->uri.p);
#endif
LOG_VERBOSE() printf("HTTP request: %.*s\n", hm->uri.len, hm->uri.p);
if (mg_vcmp(&hm->uri, "/api") == 0)
handle_api(nc, hm);
else
@ -95,14 +90,10 @@ static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
}
case MG_EV_CLOSE: {
if (is_websocket(nc)) {
#ifdef DEBUG
fprintf(stderr, "Websocket connection closed\n");
#endif
LOG_VERBOSE() printf("Websocket connection closed\n");
}
else {
#ifdef DEBUG
fprintf(stderr,"HTTP connection closed\n");
#endif
LOG_VERBOSE() printf("HTTP connection closed\n");
}
break;
}
@ -116,7 +107,7 @@ static void ev_handler_http(struct mg_connection *nc_http, int ev, void *ev_data
struct mg_str *host_hdr = mg_get_http_header(hm, "Host");
char s_redirect[250];
snprintf(s_redirect, 250, "https://%.*s:%s/", host_hdr->len, host_hdr->p, config.sslport);
printf("Redirecting to %s\n", s_redirect);
LOG_VERBOSE() printf("Redirecting to %s\n", s_redirect);
mg_http_send_redirect(nc_http, 301, mg_mk_str(s_redirect), mg_mk_str(NULL));
break;
}
@ -142,6 +133,8 @@ static int inihandler(void* user, const char* section, const char* name, const c
p_config->ssl = true;
else
p_config->ssl = false;
else if (MATCH("sslport"))
p_config->sslport = strdup(value);
else if (MATCH("sslcert"))
p_config->sslcert = strdup(value);
else if (MATCH("sslkey"))
@ -201,8 +194,12 @@ static int inihandler(void* user, const char* section, const char* name, const c
p_config->streamurl = strdup(value);
else if (MATCH("last_played_count"))
p_config->last_played_count = strtol(value, &crap, 10);
else
else if (MATCH("loglevel"))
p_config->loglevel = strtol(value, &crap, 10);
else {
printf("Unkown config line: %s\n", name);
return 0; /* unknown section/name, error */
}
return 1;
}
@ -215,7 +212,7 @@ void read_syscmds() {
long order;
if (config.syscmds == true) {
snprintf(dirname, 400, "%s/syscmds", config.etcdir);
printf("Reading syscmds: %s\n", dirname);
LOG_INFO() printf("Reading syscmds: %s\n", dirname);
if ((dir = opendir (dirname)) != NULL) {
while ((ent = readdir(dir)) != NULL) {
if (strncmp(ent->d_name, ".", 1) == 0)
@ -228,14 +225,14 @@ void read_syscmds() {
}
}
else
printf("Syscmds are disabled\n");
LOG_INFO() printf("Syscmds are disabled\n");
}
void read_statefiles() {
char *crap;
char value[400];
printf("Reading states\n");
LOG_INFO() printf("Reading states\n");
if (mympd_state_get("notificationWeb", value)) {
if (strcmp(value, "true") == 0)
mympd_state.notificationWeb = true;
@ -358,7 +355,7 @@ bool testdir(char *name, char *dirname) {
DIR* dir = opendir(dirname);
if (dir) {
closedir(dir);
printf("%s: \"%s\"\n", name, dirname);
LOG_INFO() printf("%s: \"%s\"\n", name, dirname);
return true;
}
else {
@ -402,6 +399,7 @@ int main(int argc, char **argv) {
config.etcdir = dirname(etcdir);
config.syscmds = false;
config.localplayer = true;
config.loglevel = 1;
mpd.timeout = 3000;
mpd.last_update_sticker_song_id = -1;
@ -410,7 +408,9 @@ int main(int argc, char **argv) {
mpd.feat_library = false;
if (argc == 2) {
printf("Parsing config file: %s\n", argv[1]);
LOG_INFO() printf("Starting myMPD %s\n", MYMPD_VERSION);
LOG_INFO() printf("Libmpdclient %i.%i.%i\n", LIBMPDCLIENT_MAJOR_VERSION, LIBMPDCLIENT_MINOR_VERSION, LIBMPDCLIENT_PATCH_VERSION);
LOG_INFO() printf("Parsing config file: %s\n", argv[1]);
if (ini_parse(argv[1], inihandler, &config) < 0) {
printf("Can't load config file \"%s\"\n", argv[1]);
return EXIT_FAILURE;
@ -427,8 +427,11 @@ int main(int argc, char **argv) {
);
return EXIT_FAILURE;
}
printf("Starting myMPD %s\n", MYMPD_VERSION);
#ifdef DEBUG
printf("Debug flag enabled, setting loglevel to debug\n");
config.loglevel = 3;
#endif
signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler);
@ -465,10 +468,10 @@ int main(int argc, char **argv) {
}
if (config.user != NULL) {
printf("Droping privileges to %s\n", config.user);
LOG_INFO() printf("Droping privileges to %s\n", config.user);
struct passwd *pw;
if ((pw = getpwnam(config.user)) == NULL) {
printf("Unknown user\n");
printf("getpwnam() failed, unknown user\n");
mg_mgr_free(&mgr);
return EXIT_FAILURE;
} else if (setgroups(0, NULL) != 0) {
@ -497,11 +500,11 @@ int main(int argc, char **argv) {
snprintf(testdirname, 400, "%s/library", SRC_PATH);
if (testdir("Link to mpd music_directory", testdirname)) {
LOG_INFO() printf("Enabling featLibrary support\n");
mpd.feat_library = true;
printf("Enabling coverimage support\n");
}
else {
printf("Disabling coverimage support\n");
LOG_INFO() printf("Disabling coverimage support\n");
config.coverimage = false;
}
@ -526,7 +529,7 @@ int main(int argc, char **argv) {
list_init(&mpd_tags);
list_init(&mympd_tags);
list_init(&last_played);
printf("Reading last played songs: %d\n", read_last_played());
LOG_INFO() printf("Reading last played songs: %d\n", read_last_played());
if (config.ssl == true)
mg_set_protocol_http_websocket(nc_http);
@ -535,9 +538,9 @@ int main(int argc, char **argv) {
s_http_server_opts.document_root = SRC_PATH;
s_http_server_opts.enable_directory_listing = "no";
printf("Listening on http port %s\n", config.webport);
LOG_INFO() printf("Listening on http port %s\n", config.webport);
if (config.ssl == true)
printf("Listening on ssl port %s\n", config.sslport);
LOG_INFO() printf("Listening on ssl port %s\n", config.sslport);
while (s_signal_received == 0) {
mg_mgr_poll(&mgr, 100);

55
src/validate.c Normal file
View File

@ -0,0 +1,55 @@
/* myMPD
(c) 2018 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of:
ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <stdio.h>
#include "validate.h"
void sanitize_string(const char *data) {
static char ok_chars[] = "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"1234567890_-. ";
char *cp = data;
const char *end = data + strlen(data);
for (cp += strspn(cp, ok_chars); cp != end; cp += strspn(cp, ok_chars))
*cp = '_';
}
int validate_path(char *path, const char *basepath) {
char *rpath = NULL;
char *ptr;
ptr = realpath(path, rpath);
if (ptr == NULL)
return 1;
if (strncmp(basepath, ptr, strlen(basepath)) == 0) {
free(rpath);
return 0;
}
else {
free(rpath);
return 1;
}
}

26
src/validate.h Normal file
View File

@ -0,0 +1,26 @@
/* myMPD
(c) 2018 Juergen Mang <mail@jcgames.de>
This project's homepage is: https://github.com/jcorporation/mympd
myMPD ist fork of:
ympd
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
This project's homepage is: http://www.ympd.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
void sanitize_string(const char *data);
int validate_path(char *path, const char *basepath);