LUA MPD Web Interface for OpenWRT/LEDE systems
authorrvbglas <rvb@rvb.name>
Tue, 26 Dec 2017 09:12:52 +0000 (12:12 +0300)
committerrvbglas <rvb@rvb.name>
Tue, 26 Dec 2017 09:12:52 +0000 (12:12 +0300)
34 files changed:
ajax/mpd.js [new file with mode: 0644]
css/general.css [new file with mode: 0644]
images/addall.png [new file with mode: 0755]
images/addselected.png [new file with mode: 0755]
images/bg.png [new file with mode: 0755]
images/control_bg.png [new file with mode: 0755]
images/down.png [new file with mode: 0755]
images/highlight.png [new file with mode: 0755]
images/left.png [new file with mode: 0755]
images/lists.png [new file with mode: 0644]
images/minus.png [new file with mode: 0755]
images/next.png [new file with mode: 0755]
images/pause.png [new file with mode: 0755]
images/play.png [new file with mode: 0755]
images/playing_bg.png [new file with mode: 0755]
images/playlist.png [new file with mode: 0755]
images/plus.png [new file with mode: 0755]
images/prev.png [new file with mode: 0755]
images/previous.png [new file with mode: 0755]
images/remall.png [new file with mode: 0755]
images/remove.png [new file with mode: 0755]
images/removeall.png [new file with mode: 0755]
images/removeselected.png [new file with mode: 0755]
images/remselected.png [new file with mode: 0755]
images/repeatoff.png [new file with mode: 0755]
images/repeaton.png [new file with mode: 0755]
images/right.png [new file with mode: 0755]
images/save.png [new file with mode: 0644]
images/songs.png [new file with mode: 0755]
images/stop.png [new file with mode: 0755]
images/up.png [new file with mode: 0755]
images/update.png [new file with mode: 0755]
index.html [new file with mode: 0644]
mpd.lua [new file with mode: 0755]

diff --git a/ajax/mpd.js b/ajax/mpd.js
new file mode 100644 (file)
index 0000000..9cd2667
--- /dev/null
@@ -0,0 +1,392 @@
+urlbase="mpd.lua?"
+
+function GetFilename(url)
+{
+   if (url)
+   return url.split('/').pop().split('#')[0].split('?')[0];
+}
+
+function EscapeStr(str) {
+  res = str.replace(/'/g,"\\'");
+  return res;
+}
+
+function RefreshPageStatus() {
+
+  var req = new XMLHttpRequest();
+
+  req.onreadystatechange = function () {
+    if (this.readyState != 4 || this.status != 200) return;
+    var returnedData = JSON.parse(this.responseText);
+    nowPlaying = GetFilename(returnedData['current_playing'])
+    document.title='MPD Player: '+nowPlaying;
+    nowPlaying = (1+Number(returnedData['song'])) + '/' + returnedData['playlistlength'] + ' '+nowPlaying;
+    if (returnedData['state']=='stop') {
+      nowPlaying = '<font color="gray">' + nowPlaying+ '</font>'
+    }
+    document.getElementById('nowplaying_content').innerHTML=nowPlaying;
+    if (returnedData["state"]=="play") {
+      document.getElementById('playpausebutton').innerHTML="<span onclick=\"Command('pause')\"><img class=\"button\" src=\"images/pause.png\"></span>";
+    } else {
+      document.getElementById('playpausebutton').innerHTML="<span onclick=\"Command('play')\"><img class=\"button\" src=\"images/play.png\"></span>";
+    }
+    if (returnedData["repeat"]=="1") {
+      document.getElementById('repeatstate').innerHTML="<img src=\"images/repeaton.png\"></a>";
+    } else {
+      document.getElementById('repeatstate').innerHTML="<img src=\"images/repeatoff.png\"></a>";
+    }
+    document.getElementById('volume_total').innerHTML="<div id=\"volume_actual\" style=\"width:"+returnedData["volume"]+"%\">";
+  };
+
+  req.open("GET", urlbase+"status", true);
+  req.send();
+
+
+}
+
+function RefreshPlaylist() {
+
+var req = new XMLHttpRequest();
+
+req.onreadystatechange = function () {
+  if (this.readyState != 4 || this.status != 200) return;
+  var returnedData = JSON.parse(this.responseText);
+
+  var text = "<div id=\"playlist_menu\">\
+  <table>\
+  <tr>\
+  <td><span class=\"button\" onclick=\"EditPlayList()\"><img width=\"20\" src=\"images/songs.png\"></span><td>\
+  <td><span class=\"button\" onclick=\"LoadPlayList()\"><img width=\"20\" src=\"images/lists.png\"></span><td>\
+  <td><span class=\"button\" onclick=\"SavePlayList()\"><img width=\"20\" src=\"images/save.png\"></span><td>\
+  <td><span class=\"button\" onclick=\"return confirm('Clear current playlist, are you sure?') ? PlaylistCommand('clear') : false;\" ><img width=\"20\" src=\"images/removeall.png\"></span><td>\
+  </tr>\
+  </table>\
+  </div>\
+  <div id=\"items\">\
+  <table>\
+  <tr id=\"items_heading\">\
+  <td></td><td>Title</td><th colspan=\"4\">Controls</th>\
+  </tr>";
+
+  var even = 0;
+  for (var key in returnedData) {
+       var rec=returnedData[key];
+       var name=GetFilename(rec["name"]);
+       var id=rec["id"];
+
+       if (even) { 
+         evText="itemEven"; 
+       } else { 
+         evText="itemOdd";
+       };
+
+       even = ! even;
+
+       text = text + "<tr id=\""+evText+"\">\
+  <td id=\"track_number\">\
+  <a name=\""+id+"\"></a></td>\
+  </td>\
+  <td id=\"file\">\
+  <span class=\"button\" onclick=\"PlaylistCommand('playitem',"+id+")\">"+name+"</span>\
+  </td>\
+  <td id=\"move\">\
+  <span class=\"button\" onclick=\"PlaylistCommand('moveup',"+id+")\"><img width=\"15\" src=\"images/up.png\"></span>\
+  </td>\
+  <td id=\"move\">\
+  <span class=\"button\" onclick=\"PlaylistCommand('movedown',"+id+")\"><img width=\"15\" src=\"images/down.png\"></span>\
+  </td>\
+  <td id=\"remove\">\
+  <span class=\"button\" onclick=\"PlaylistCommand('remove',"+id+")\"><img width=\"15\" src=\"images/remove.png\"></span>\
+  </td>\
+  </tr>"; 
+  }
+
+  text = text + "</table>\
+  </div>\
+  <div id=\"playlist_menu\">\
+  <table>\
+  <tr>\
+  <td><span class=\"button\" onclick=\"EditPlayList()\"><img width=\"20\" src=\"images/songs.png\"></span><td>\
+  <td><span class=\"button\" onclick=\"LoadPlayList()\"><img width=\"20\" src=\"images/lists.png\"></span><td>\
+  <td><span class=\"button\" onclick=\"SavePlayList()\"><img width=\"20\" src=\"images/save.png\"></span><td>\
+  <td><span class=\"button\" onclick=\"return confirm('Clear current playlist, are you sure?') ? PlaylistCommand('clear') : false;\" ><img width=\"20\" src=\"images/removeall.png\"></span><td>\
+  </tr>\
+  </table>\
+</div>";
+
+  document.getElementById('playlist').innerHTML=text;
+};
+
+req.open("GET", urlbase+"playlist", true);
+req.send();
+
+}
+
+function EditPlayList(dir) {
+
+var req = new XMLHttpRequest();
+
+req.onreadystatechange = function () {
+  if (this.readyState != 4 || this.status != 200) return;
+  var returnedData = JSON.parse(this.responseText);
+
+  var text = "<div id=\"playlist_menu\">\
+  <table>\
+  <tr>\
+  <td><span class=\"button\" onclick=\"RefreshPlaylist()\"><img width=\"20\" src=\"images/playlist.png\"></span><td>\
+  <td><span class=\"button\" onclick=\"return confirm('Add all to the list, are you sure?') ? PlaylistEditCommand('add','"+EscapeStr(dir)+"') : false;\" ><img width=\"20\" src=\"images/addall.png\"></span><td>\
+  </tr>\
+  </table>\
+  </div>\
+  <div id=\"items\">\
+  <table>\
+  <tr id=\"items_heading\">\
+  <td></td><td>Title</td><th colspan=\"2\">Controls</th>\
+  </tr>";
+
+  if (dir) {
+    var lastSlash=dir.lastIndexOf("/");
+    if (lastSlash>0) {
+      var upperLevel=dir.slice(0,lastSlash);
+    } else {
+      var upperLevel="";
+    }
+    var text = text + "<tr id=\"home\">\
+  <td></td>\
+  <td><span class=\"button\" onclick=\"EditPlayList('"+upperLevel+"')\"><img width=\"20\" src=\"images/left.png\"></span></td>\
+  <td></td><td></td>";
+  } 
+
+  var even = 0;
+  var i = 0;
+  for (var key in returnedData) {
+       var rec=returnedData[key];
+       var type=rec["type"];
+       var name=rec["name"];
+       var lastSlash=name.lastIndexOf("/");
+       if (lastSlash>0) {
+         var tailName=name.slice(lastSlash+1);
+       } else {
+         var tailName=name
+       };
+
+       if (type == "directory" || type == "file") {
+         if (even) { 
+           evText="itemEven"; 
+         } else { 
+           evText="itemOdd";
+         };
+
+         i = i + 1;
+         even = ! even;
+
+         text = text + "<tr id=\""+evText+"\">\
+           <td id=\"track_number\">\
+           <a name=\""+i+"\"></a></td>\
+           </td>";
+      
+         if (type == "directory") {
+           text = text + "<td id=\"file\">\
+             <span class=\"button\" onclick=\"EditPlayList('"+EscapeStr(name)+"')\">"+tailName+"</span></td><td>\
+             <span class=\"button\" onclick=\"PlaylistEditCommand('add','"+EscapeStr(name)+"')\"><img width=\"15\" src=\"images/plus.png\"></span></td>";
+         }; 
+
+         if (type == "file") {
+           text = text + "<td id=\"file\">\
+             <span class=\"button\" onclick=\"PlaylistEditCommand('add','"+EscapeStr(name)+"')\">"+tailName+"</span></td><td>\
+             <span class=\"button\" onclick=\"PlaylistEditCommand('add','"+EscapeStr(name)+"')\"><img width=\"15\" src=\"images/plus.png\"></span></td>";
+         };
+
+         text = text + "</tr>";
+
+       }       
+       
+  }
+
+  var text = text+"</table></div>\
+  <div id=\"playlist_menu\">\
+  <table>\
+  <tr>\
+  <td><span class=\"button\" onclick=\"RefreshPlaylist()\"><img width=\"20\" src=\"images/playlist.png\"></span><td>\
+  <td><span class=\"button\" onclick=\"return confirm('Add all to the list, are you sure?') ? PlaylistEditCommand('addall','"+EscapeStr(dir)+"') : false;\" ><img width=\"20\" src=\"images/addall.png\"></span><td>\
+  </tr>\
+  </table>\
+  </div>";
+  document.getElementById('playlist').innerHTML=text;
+};
+
+if (!dir) { dir = ''; };
+
+req.open("GET", urlbase+"lists|edit|"+dir, true);
+req.send();
+
+}
+
+function LoadPlayList() {
+
+var req = new XMLHttpRequest();
+
+req.onreadystatechange = function () {
+  if (this.readyState != 4 || this.status != 200) return;
+  var returnedData = JSON.parse(this.responseText);
+  text="<div id=\"playlist_menu\">\
+    <table>\
+    <tr>\
+      <td><span class=\"button\" onclick=\"RefreshPlaylist()\"><img width=\"20\" src=\"images/playlist.png\"></span><td>\
+      <td><span class=\"button\" onclick=\"confirm('Clear current playlist, are you sure?') ? PlaylistCommandRefStatus('clear') : false;\" ><img width=\"20\" src=\"images/removeall.png\"></span><td>\
+    </tr>\
+    </table>\
+    </div>\
+    <div id=\"items\">\
+    <table>\
+    <tr id=\"items_heading\">\
+      <td></td><td>Name</td><td>Controls</td>\
+    </tr>";
+
+  var even = 0;
+  for (var key in returnedData) {
+       var name=returnedData[key];
+
+       if (even) { 
+         evText="itemEven"; 
+       } else { 
+         evText="itemOdd";
+       };
+
+       even = ! even;
+
+       text = text + "<tr id=\""+evText+"\">\
+      <td id=\"track_number\"><a name=\"0\"></a></td>\
+      <td id=\"file\"><span class=\"button\" onclick=\"PlaylistEditCommandRefFull('load','"+EscapeStr(name)+"')\">"+name+"</td>\
+      <td id=\"controls\"><span class=\"button\" onclick=\"confirm('Delete playlist "+name+", are you sure?') ? DelPlayList('"+EscapeStr(name)+"') : false;\"><img width=\"20\" src=\"images/minus.png\"></span></td>\
+    </tr>";
+  }
+
+  text=text+"</table></div><div id=\"playlist_menu\">\
+    <table>\
+    <tr>\
+      <td><span class=\"button\" onclick=\"RefreshPlaylist()\"><img width=\"20\" src=\"images/playlist.png\"></span><td>\
+      <td><span class=\"button\" onclick=\"confirm('Clear current playlist, are you sure?') ? PlaylistCommandRefStatus('clear') : false;\" ><img width=\"20\" src=\"images/removeall.png\"></span><td>\
+    </tr>\
+    </table>\
+    </div>";
+
+  document.getElementById('playlist').innerHTML=text;
+};
+
+req.open("GET", urlbase+"lists|load", true);
+req.send();
+
+}
+
+function SavePlayList() {
+
+var name=window.prompt('List name','');
+
+var req = new XMLHttpRequest();
+
+req.onreadystatechange = function () {
+  if (this.readyState != 4 || this.status != 200) return;
+  if (this.responseText != 'OK') {
+    window.alert(this.responseText);
+  }
+};
+
+req.open("GET", urlbase+"lists|save|"+name, true);
+req.send();
+
+}
+
+function DelPlayList(item) {
+
+var req = new XMLHttpRequest();
+
+req.onreadystatechange = function () {
+  if (this.readyState != 4 || this.status != 200) return;
+  RefreshPageStatus();
+  LoadPlayList();
+};
+
+req.open("GET", urlbase+"lists|delete|"+item, true);
+req.send();
+
+}
+
+function RefreshPageContent() {
+
+  RefreshPageStatus();
+  RefreshPlaylist();
+
+}
+
+function Command(cmd) {
+
+var req = new XMLHttpRequest();
+
+req.onreadystatechange = function () {
+  if (this.readyState != 4 || this.status != 200) return;
+  RefreshPageStatus();
+};
+
+req.open("GET", urlbase+cmd, true);
+req.send();
+
+}
+
+function PlaylistCommand(cmd,item) {
+
+var req = new XMLHttpRequest();
+
+req.onreadystatechange = function () {
+  if (this.readyState != 4 || this.status != 200) return;
+  RefreshPageContent();
+};
+
+req.open("GET", urlbase+"cpl|"+cmd+"|"+item, true);
+req.send();
+
+}
+
+function PlaylistCommandRefStatus(cmd,item) {
+
+var req = new XMLHttpRequest();
+
+req.onreadystatechange = function () {
+  if (this.readyState != 4 || this.status != 200) return;
+  RefreshPageStatus();
+};
+
+req.open("GET", urlbase+"cpl|"+cmd+"|"+item, true);
+req.send();
+
+}
+
+function PlaylistEditCommand(cmd,item) {
+
+var req = new XMLHttpRequest();
+
+req.onreadystatechange = function () {
+  if (this.readyState != 4 || this.status != 200) return;
+  RefreshPageStatus();
+};
+
+req.open("GET", urlbase+"lists|"+cmd+"|"+item, true);
+req.send();
+
+}
+
+function PlaylistEditCommandRefFull(cmd,item) {
+
+var req = new XMLHttpRequest();
+
+req.onreadystatechange = function () {
+  if (this.readyState != 4 || this.status != 200) return;
+  RefreshPageContent();
+};
+
+req.open("GET", urlbase+"lists|"+cmd+"|"+item, true);
+req.send();
+
+}
+
+setInterval(RefreshPageStatus, 10000);
diff --git a/css/general.css b/css/general.css
new file mode 100644 (file)
index 0000000..6bb13cc
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+#525356
+*/
+
+
+
+* {
+       margin:0;
+}
+
+body {
+       text-align: center;
+       font-family: arial,sans-serif;
+       margin-top: 15px;
+       margin-bottom: 15px;
+       color: #AEAEAE;
+}
+
+a {
+       text-decoration: none;
+       color: #AEAEAE;
+}
+
+table
+{
+       border-style: none;
+       padding: 0px;
+       border-collapse: collapse;
+       border-spacing: 0;
+}
+
+img{
+       border: 0px;
+       margin: 2px 2px 2px 2px;
+}
+
+button
+{
+       background: none;
+       cursor: pointer;
+       border: 0px;
+       margin: 0px;
+       padding: 0px;
+}
+
+.button
+{
+       cursor: pointer;
+}
+
+.trackno
+{
+       text-align: right;
+       vertical-align: top;
+       padding: 0px 0px 5px 0px;
+}
+
+#heading_tbl {
+       width: 400px
+}
+
+#frame {
+       text-align: left;
+       border: 1px solid #000;
+       width: 400px;
+       background: url(../images/bg.png) no-repeat #251616;
+       margin: 0 auto;
+}
+
+
+#nowplaying_heading {
+       font-size: 1.3em;
+       font-wieght: bold;
+       height: 30px;   
+       background: url(../images/control_bg.png) repeat-x;
+       border-bottom: 1px solid #525356;
+}
+
+#nowplaying_content {
+       margin: 10px 5px 10px 5px;
+       text-align: center;
+       border: 1px solid #525356;
+       width: 380px;
+       height: 60px;
+       padding: 10px 0px 5px 0px;
+       background: url(../images/playing_bg.png);
+       font-weight: bold;
+       color: #fff;    
+}
+
+#control_buttons {
+       text-align: center;
+       width: 390px;
+       margin: 10px 5px 10px 5px;
+}
+
+#control_buttons table {
+       margin-left: auto; 
+       margin-right: auto;
+}
+
+
+#control_volume {
+       text-align: center;
+       width: 390px;
+       margin: 10px 5px 10px 5px;
+}
+
+#control_volume table {
+       margin-left: auto; 
+       margin-right: auto;
+}
+
+#playlist_menu {
+        font-wieght: bold;
+        height: 24px;
+        background: url(../images/control_bg.png) repeat-x;
+        border-top: 2px solid #525356;
+       margin-top: 5px;
+       margin-bottom: 5px;
+}
+
+#items_heading {
+       text-align: center;
+       font-size: 1.0em;
+       font-weight:bold;
+       border-bottom: 1px solid #525356;
+       border-top: 1px solid #525356;
+       background-color: #333;
+}
+
+#home {
+       text-align: left;
+        font-size: 1.0em;
+        font-weight: bold;
+       border-bottom: 3px solid #525356;
+        background-color: #251616;
+}
+
+#items {
+       font-size: 0.9em;
+       text-align: center;
+       min-height: 400px;
+        max-height: 600px;
+       background: url(../images/playing_bg.png);
+       overflow-y: scroll;
+}
+
+#items table {
+       width: 380px;
+       margin: 0px 5px 0px 5px;
+       border: 1px solid  #525356;
+}
+
+#file {
+       text-align: left;
+       width: 100%;
+
+}
+
+
+#itemEven{
+       background-color: #251616;
+
+}
+
+#itemOdd{
+        background-color: #333;
+}
+
+#itemActive {
+       background: url(../images/playing_bg.png);
+       color: #fff;
+}
+
+#itemActive a {
+       color: #fff;
+}
+
+
+#move img {
+       margin: 1px 1px 1px 1px;
+}
+
+#remove img {
+        margin: 1px 1px 1px 1px;
+}
+
+
+#volume_total {
+       width: 100px;
+       border: 1px solid #525356;
+       height: 5px;
+}
+
+
+#volume_actual {
+       background-color: #d9600c;
+       height:5px;
+}
+
diff --git a/images/addall.png b/images/addall.png
new file mode 100755 (executable)
index 0000000..fa3983e
Binary files /dev/null and b/images/addall.png differ
diff --git a/images/addselected.png b/images/addselected.png
new file mode 100755 (executable)
index 0000000..52fbb78
Binary files /dev/null and b/images/addselected.png differ
diff --git a/images/bg.png b/images/bg.png
new file mode 100755 (executable)
index 0000000..5bd1ec8
Binary files /dev/null and b/images/bg.png differ
diff --git a/images/control_bg.png b/images/control_bg.png
new file mode 100755 (executable)
index 0000000..685abbb
Binary files /dev/null and b/images/control_bg.png differ
diff --git a/images/down.png b/images/down.png
new file mode 100755 (executable)
index 0000000..e8f3d6e
Binary files /dev/null and b/images/down.png differ
diff --git a/images/highlight.png b/images/highlight.png
new file mode 100755 (executable)
index 0000000..3710d71
Binary files /dev/null and b/images/highlight.png differ
diff --git a/images/left.png b/images/left.png
new file mode 100755 (executable)
index 0000000..00714b7
Binary files /dev/null and b/images/left.png differ
diff --git a/images/lists.png b/images/lists.png
new file mode 100644 (file)
index 0000000..cbae301
Binary files /dev/null and b/images/lists.png differ
diff --git a/images/minus.png b/images/minus.png
new file mode 100755 (executable)
index 0000000..dff37f6
Binary files /dev/null and b/images/minus.png differ
diff --git a/images/next.png b/images/next.png
new file mode 100755 (executable)
index 0000000..fc2c791
Binary files /dev/null and b/images/next.png differ
diff --git a/images/pause.png b/images/pause.png
new file mode 100755 (executable)
index 0000000..9508f98
Binary files /dev/null and b/images/pause.png differ
diff --git a/images/play.png b/images/play.png
new file mode 100755 (executable)
index 0000000..3591973
Binary files /dev/null and b/images/play.png differ
diff --git a/images/playing_bg.png b/images/playing_bg.png
new file mode 100755 (executable)
index 0000000..f2e2337
Binary files /dev/null and b/images/playing_bg.png differ
diff --git a/images/playlist.png b/images/playlist.png
new file mode 100755 (executable)
index 0000000..ae4af14
Binary files /dev/null and b/images/playlist.png differ
diff --git a/images/plus.png b/images/plus.png
new file mode 100755 (executable)
index 0000000..b72d579
Binary files /dev/null and b/images/plus.png differ
diff --git a/images/prev.png b/images/prev.png
new file mode 100755 (executable)
index 0000000..aedbbdf
Binary files /dev/null and b/images/prev.png differ
diff --git a/images/previous.png b/images/previous.png
new file mode 100755 (executable)
index 0000000..9c49139
Binary files /dev/null and b/images/previous.png differ
diff --git a/images/remall.png b/images/remall.png
new file mode 100755 (executable)
index 0000000..ffb546a
Binary files /dev/null and b/images/remall.png differ
diff --git a/images/remove.png b/images/remove.png
new file mode 100755 (executable)
index 0000000..bf94562
Binary files /dev/null and b/images/remove.png differ
diff --git a/images/removeall.png b/images/removeall.png
new file mode 100755 (executable)
index 0000000..4fe02d1
Binary files /dev/null and b/images/removeall.png differ
diff --git a/images/removeselected.png b/images/removeselected.png
new file mode 100755 (executable)
index 0000000..35294aa
Binary files /dev/null and b/images/removeselected.png differ
diff --git a/images/remselected.png b/images/remselected.png
new file mode 100755 (executable)
index 0000000..6466dce
Binary files /dev/null and b/images/remselected.png differ
diff --git a/images/repeatoff.png b/images/repeatoff.png
new file mode 100755 (executable)
index 0000000..0a17659
Binary files /dev/null and b/images/repeatoff.png differ
diff --git a/images/repeaton.png b/images/repeaton.png
new file mode 100755 (executable)
index 0000000..aaef8ce
Binary files /dev/null and b/images/repeaton.png differ
diff --git a/images/right.png b/images/right.png
new file mode 100755 (executable)
index 0000000..2ce6d36
Binary files /dev/null and b/images/right.png differ
diff --git a/images/save.png b/images/save.png
new file mode 100644 (file)
index 0000000..37ec086
Binary files /dev/null and b/images/save.png differ
diff --git a/images/songs.png b/images/songs.png
new file mode 100755 (executable)
index 0000000..f24c1ef
Binary files /dev/null and b/images/songs.png differ
diff --git a/images/stop.png b/images/stop.png
new file mode 100755 (executable)
index 0000000..1e1dd0f
Binary files /dev/null and b/images/stop.png differ
diff --git a/images/up.png b/images/up.png
new file mode 100755 (executable)
index 0000000..0ada34a
Binary files /dev/null and b/images/up.png differ
diff --git a/images/update.png b/images/update.png
new file mode 100755 (executable)
index 0000000..fa2242b
Binary files /dev/null and b/images/update.png differ
diff --git a/index.html b/index.html
new file mode 100644 (file)
index 0000000..5df6090
--- /dev/null
@@ -0,0 +1,85 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de-de" lang="de-de" >
+<head>
+       <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+       <title>MPD Player</title>
+       <link rel="stylesheet" href="css/general.css" type="text/css" />
+       <script type="text/javascript" src="ajax/mpd.js"></script>
+</head>
+<body onload="RefreshPageContent()">
+
+<div id="frame">
+
+<div id="playerframe">
+
+<div id="nowplaying_heading">
+<table id="heading_tbl">
+<tr>
+<td id="left">Now playing</td>
+<td id="right">
+<span onclick="RefreshPageContent()"><img class="button" align="right" width="20" src="images/update.png"></span>
+</td>
+</tr>
+</table>
+</div>
+
+<div id="nowplaying_content">
+</div>
+
+<div id="control_buttons">
+<table>
+<tr>
+<td><span onclick="Command('previous')"><img class="button" src="images/previous.png"></span></td>
+<td><div id="playpausebutton"><span onclick="Command('pause')"><img class="button" src="images/pause.png"></span></div></td>
+<td><span onclick="Command('stop')"><img class="button" src="images/stop.png"></span></td>
+<td><span onclick="Command('next')"><img class="button" src="images/next.png"></span></td>
+<td><span onclick="Command('repeat')"><div id="repeatstate"><img class="button" src="images/repeaton.png"></div></span></td>
+</tr>
+</table>
+</div>
+
+<div id="control_volume">
+<table>
+<tr>
+<td><span onclick="Command('vold')"><img class="button" src="images/minus.png"></span></td>
+<td id="volume_content">Volume:</td><td><div id="volume_total"></div></div></td>
+<td><span onclick="Command('volu')"><img class="button" src="images/plus.png"></span></td>
+</tr>
+</table>
+</div>
+</div>
+
+<div id="playlist">
+
+<div id="playlist_menu">
+<table>
+<tr>
+<td><span class="button" onclick="EditPlayList()"><img width="20" src="images/songs.png"></span><td>
+<td><span class="button" onclick="LoadPlayList()"><img width="20" src="images/lists.png"></span><td>
+<td><span class="button" onclick="SavePlayList()"><img width="20" src="images/save.png"></span><td>
+<td><span class="button" onclick="return confirm('Clear current playlist, are you sure?') ? PlaylistCommand('clear') : false;" ><img width="20" src="images/removeall.png"></span><td>
+<td><span class="button" onclick="PlaylistItemsCommand('removeselected')"><img width="20" src="images/removeselected.png"></span><td>
+</tr>
+</table>
+</div>
+
+<div id="items">
+</div>
+
+<div id="playlist_menu">
+<table>
+<tr>
+<td><span class="button" onclick="EditPlayList()"><img width="20" src="images/songs.png"></span><td>
+<td><span class="button" onclick="LoadPlayList()"><img width="20" src="images/lists.png"></span><td>
+<td><span class="button" onclick="SavePlayList()"><img width="20" src="images/save.png"></span><td>
+<td><span class="button" onclick="return confirm('Clear current playlist, are you sure?') ? PlaylistCommand('clear') : false;" ><img width="20" src="images/removeall.png"></span><td>
+<td><span class="button" onclick="PlaylistItemsCommand('removeselected')"><img width="20" src="images/removeselected.png"></span><td>
+</tr>
+</table>
+</div>
+
+</div>
+
+</div>
+
+</body>
diff --git a/mpd.lua b/mpd.lua
new file mode 100755 (executable)
index 0000000..0200a8a
--- /dev/null
+++ b/mpd.lua
@@ -0,0 +1,275 @@
+#!/usr/bin/lua
+
+function url_decode(str)
+  if not str then return nil end
+  str = string.gsub (str, "+", " ")
+  str = string.gsub (str, "%%(%x%x)", function(h) return
+    string.char(tonumber(h,16)) end)
+  str = string.gsub (str, "\r\n", "\n")
+  return str
+end
+
+function split(s, delimiter)
+    local result = {};
+    for match in (s..delimiter):gmatch("(.-)"..delimiter) do
+        result[#result+1] = match;
+    end
+    return result;
+end
+
+function string.starts(String,Start)
+   return string.sub(String,1,string.len(Start))==Start
+end
+
+function process_playlist(playlist)
+  local res={}
+  for _,record in pairs(playlist) do
+    local splitted = split(record,": ")
+    local name = splitted[2]
+    local splitted = split(splitted[1],":")
+    local id = splitted[1]
+    local rectype = splitted[2]
+    local rec = {}
+    if not (id == "OK") then
+      rec["id"] = id
+      rec["type"] = rectype
+      rec["name"] = name
+      res[id] = rec
+    end
+  end
+  return res
+end
+
+function process_playlists(playlists)
+  local res={}
+  for _,record in pairs(playlists) do
+    local splitted = split(record,": ")
+    if splitted[1]=="playlist" then
+      res[#res+1] = splitted[2]
+    end
+  end
+  return res
+end
+
+function process_directory(directory)
+  local res={}
+  for _,record in pairs(directory) do
+    local splitted = split(record,": ")
+    if splitted[1]=="directory" or splitted[1]=="file" then
+      local rec={}
+      rec["type"] = splitted[1]
+      rec["name"] = splitted[2]
+      res[#res+1] = rec
+    end
+  end
+  return res
+end
+
+require "uci"
+require("socket")
+
+function mpd_new(settings)
+    local client = {}
+    if settings == nil then settings = {} end
+
+    client.hostname = settings.hostname or "localhost"
+    client.port     = settings.port or 6600
+    client.desc     = settings.desc or client.hostname
+    client.password = settings.password
+    client.timeout  = settings.timeout or 1
+    client.retry    = settings.retry or 60
+
+    return client
+end
+
+
+function mpd_send(mpd,action,raw)
+
+    local command = string.format("%s\n", action)
+    local values = {}
+
+    -- connect to MPD server if not already done.
+    if not mpd.connected then
+        local now = os.time();
+        if not mpd.last_try or (now - mpd.last_try) > mpd.retry then
+            mpd.socket = socket.tcp()
+            mpd.socket:settimeout(mpd.timeout, 't')
+            mpd.last_try = os.time()
+            mpd.connected = mpd.socket:connect(mpd.hostname, mpd.port)
+            if not mpd.connected then
+                return { errormsg = "could not connect" }
+            end
+            mpd.last_error = nil
+
+            -- Read the server's hello message
+            local line = mpd.socket:receive("*l")
+            if not line:match("^OK MPD") then -- Invalid hello message?
+                mpd.connected = false
+                return { errormsg = string.format("invalid hello message: %s", line) }
+            else
+                _, _, mpd.version = string.find(line, "^OK MPD ([0-9.]+)")
+            end
+
+            -- send the password if needed
+            if mpd.password then
+                local rsp = mpd_send(mpd,string.format("password %s", mpd.password))
+                if rsp.errormsg then
+                    return rsp
+                end
+            end
+        else
+            local retry_sec = mpd.retry - (now - mpd.last_try)
+            return { errormsg = string.format("%s (retrying in %d sec)", mpd.last_error, retry_sec) }
+        end
+    end
+
+    mpd.socket:send(command)
+
+    local line = ""; err=0
+    while not line:match("^OK$") do
+        line, err = mpd.socket:receive("*l")
+        if not line then -- closed,timeout (mpd killed?)
+            mpd.last_error = err
+            mpd.connected = false
+            mpd.socket:close()
+            return mpd_send(mpd,action)
+        end
+
+        if line:match("^ACK") then
+            return { errormsg = line }
+        end
+
+        if not raw then
+
+         local pattern = string.format("(%s)", ": ")
+         local i = string.find (line, pattern, 0)
+
+          if i ~= nil then
+              local key=string.sub(line,1,i-1)
+              local value=string.sub(line,i+2,-1)
+              values[string.lower(key)] = value
+          end
+
+       else
+
+         values[#values+1]=line
+
+       end
+    end
+
+    return values
+end
+
+
+json=require("json")
+
+x = uci.cursor()
+settings = {}
+settings['host'] = x.get("mpd","server","host") or "localhost"
+settings['port'] = x.get("mpd","server","port") or 6600
+password = x.get("mpd","server","password")
+if password then
+  settings["password"] = password
+end
+
+m = mpd_new(settings)
+
+command = url_decode(os.getenv('QUERY_STRING'))
+
+if not command or command=="" then
+  command="status"
+end
+
+if command=="play" or command=="pause" or command=="stop" or command=="previous" or command=="next" then
+  res=mpd_send(m,command)
+elseif command=="vold" then
+  status=mpd_send(m,"status")
+  volume=tonumber(status["volume"])
+  res=mpd_send(m,"setvol "..(volume-3))
+elseif command=="volu" then
+  status=mpd_send(m,"status")
+  volume=tonumber(status["volume"])
+  res=mpd_send(m,"setvol "..(volume+3))
+elseif command=="status" then
+  res=mpd_send(m,"status")
+  song=res["song"]
+  playlist=mpd_send(m,"playlist",1)
+  pl=process_playlist(playlist)
+  if song then 
+    res['current_playing']=pl[song]['name']
+  else
+    res['current_playing']="No songs selected"
+  end
+elseif command=="playlist" then
+  playlist=mpd_send(m,"playlist",1)
+  res=process_playlist(playlist)
+elseif command=="repeat" then
+  status=mpd_send(m,"status")
+  rep=1-status["repeat"]
+  res=mpd_send(m,"repeat "..rep)
+elseif string.starts(command,"cpl") then
+  cmd=split(command,"|")
+  id=cmd[3]
+  command=cmd[2]
+  if command=="playitem" then
+    command="play "..id
+    res=mpd_send(m,command)
+  end
+  if command=="clear" then
+    res=mpd_send(m,"clear")
+  end
+  if command=="remove" then
+    command="delete "..id
+    res=mpd_send(m,command)
+  end
+  if command=="moveup" then
+    command="swap "..id.." "..(id-1)
+    res=mpd_send(m,command)
+  end
+  if command=="movedown" then
+    command="swap "..id.." "..(id+1)
+    res=mpd_send(m,command)
+  end
+elseif string.starts(command,"lists") then
+  cmd=split(command,"|")
+  command=cmd[2]
+  if command=="load" then
+    if not cmd[3] then
+      lists=mpd_send(m,"listplaylists",1)
+      res=process_playlists(lists)
+    else
+      res=mpd_send(m,"load "..cmd[3],1)
+    end
+  end
+  if command=="save" and cmd[3] then
+    res=mpd_send(m,"save "..cmd[3],1)
+  end
+  if command=="delete" and cmd[3] then
+    res=mpd_send(m,"rm "..cmd[3],1)
+  end
+  if command=="edit" then
+    if cmd[3] then
+      dir=mpd_send(m,"lsinfo \""..cmd[3].."\"",1)
+    else
+      dir=mpd_send(m,"lsinfo",1)
+    end
+    res=process_directory(dir)
+  end
+  if command=="add" then
+    res={}
+    if cmd[3] then
+      res=mpd_send(m,"add \""..cmd[3].."\"")
+    end
+  end
+end
+
+if not res then
+  print("Content-Type: text/plain\r\n")
+  print("MPD server - unknown command "..command)
+elseif (res['error_msg']) then
+  print("Content-Type: text/plain\r\n")
+  print("MPD server connection error: "..res['error_msg'])
+else
+  print "Content-Type: text/plain\r\n"
+  print(json.encode(res))
+end