From 759671cb4bae0c5a209dc4406f5e5c3b46316375 Mon Sep 17 00:00:00 2001 From: rvbglas Date: Thu, 28 Dec 2017 14:52:02 +0300 Subject: [PATCH] =?utf8?q?=D0=9A=D0=BE=D1=81=D0=BC=D0=B5=D1=82=D0=B8=D1=87?= =?utf8?q?=D0=B5=D1=81=D0=BA=D0=B8=D0=B5=20=D0=B4=D0=BE=D1=80=D0=B0=D0=B1?= =?utf8?q?=D0=BE=D1=82=D0=BA=D0=B8=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D1=84?= =?utf8?q?=D0=B5=D0=B9=D1=81=D0=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- mpd.js | 9 +- mpd.js~ | 473 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ mpd.lua | 48 ++++-- mpd.lua~ | 417 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 935 insertions(+), 12 deletions(-) create mode 100644 mpd.js~ create mode 100755 mpd.lua~ diff --git a/mpd.js b/mpd.js index c23c316..78d9c8b 100644 --- a/mpd.js +++ b/mpd.js @@ -1,4 +1,4 @@ -urlbase="/cgi-bin/mpd.lua?" +urlbase="mpd.lua?" minScrollHeight=200 currentState="" @@ -79,7 +79,12 @@ function RefreshPageStatus() { trackNo = returnedData['song']; currentState = returnedData['state']; document.title='MPD Player: '+trackName; - nowPlayingTrack = (1+Number(trackNo)) + '/' + returnedData['playlistlength']; + if (trackNo) { + var nowPlayingTrackNo=String(1+Number(trackNo)) + } else { + var nowPlayingTrackNo="--" + } + nowPlayingTrack = nowPlayingTrackNo + '/' + returnedData['playlistlength']; nowPlayingName = trackName; playingTime = returnedData['time'] if (playingTime) { diff --git a/mpd.js~ b/mpd.js~ new file mode 100644 index 0000000..3412633 --- /dev/null +++ b/mpd.js~ @@ -0,0 +1,473 @@ +urlbase="mpd.lua?" +minScrollHeight=200 + +currentState="" + +function GetFilename(url) +{ + if (url) + return url.split('/').pop().split('#')[0].split('?')[0]; +} + +function EscapeStr(str) { + res = str.replace(/'/g,"\\'"); + return res; +} + +function SetSize() { + var w = window, + d = document, + e = d.documentElement, + g = d.getElementsByTagName('body')[0], + body_h = g.clientHeight, + window_h = w.innerHeight|| e.clientHeight|| g.clientHeight, + items = d.getElementById('items'), + current_h = items.clientHeight, + new_h=(window_h-body_h)+current_h; + if (new_h>minScrollHeight) { + items.style.height=new_h+"px"; + } +} + +function toHHMMSS(seconds) { + var hours = Math.floor(seconds / 3600); + seconds -= hours*3600; + var minutes = Math.floor(seconds / 60); + seconds -= minutes*60; + + if (hours < 10) {hours = "0"+hours;} + if (minutes < 10) {minutes = "0"+minutes;} + if (seconds < 10) {seconds = "0"+seconds;} + if (hours == 0) { + return minutes+':'+seconds; + } else { + return hours+':'+minutes+':'+seconds; + } +} + +function RefreshTime() { + if (currentSeconds) { + currentTime = toHHMMSS(currentSeconds) + trackTime = toHHMMSS(trackSeconds) + nowPlayingTime = currentTime+"/"+trackTime + } else { + nowPlayingTime = "-:--/-:--" + } + document.getElementById('nowplaying_tracklen').innerHTML=nowPlayingTime; +} + +function PeriodicRefreshTime() { + if (currentState == "play") { + nowTime = Date.now() + delta = (nowTime - updateTime)/1000 + currentSeconds = updateSeconds + Math.round(delta) + if (currentSeconds > trackSeconds) { + currentSeconds = trackSeconds + } + RefreshTime() + } +} + +function RefreshPageStatus() { + + var req = new XMLHttpRequest(); + + req.onreadystatechange = function () { + if (this.readyState != 4 || this.status != 200) return; + var returnedData = JSON.parse(this.responseText); + trackName = GetFilename(returnedData['current_playing']); + trackNo = returnedData['song']; + currentState = returnedData['state']; + document.title='MPD Player: '+trackName; + if (trackNo) { + var nowPlayingTrackNo=String(1+Number(trackNo)) + } else { + var nowPlayingTrackNo="-" + } + nowPlayingTrack = nowPlayingTrackNo + '/' + returnedData['playlistlength']; + nowPlayingName = trackName; + playingTime = returnedData['time'] + if (playingTime) { + var splits = playingTime.split(":") + updateTime = Date.now() + currentSeconds = Number(splits[0]) + updateSeconds = currentSeconds + trackSeconds = Number(splits[1]) + } else { + currentSeconds = null + } + if (currentState=='stop') { + nowPlayingName = '' + nowPlayingName+ '' + } + document.getElementById('nowplaying_trackno').innerHTML=nowPlayingTrack; + document.getElementById('nowplaying_trackname').innerHTML=nowPlayingName; + RefreshTime() + if (currentState=="play") { + document.getElementById('playpausebutton').innerHTML=""; + } else { + document.getElementById('playpausebutton').innerHTML=""; + } + if (currentState=="stop") { + document.getElementById('stopbutton').innerHTML=""; + } else { + document.getElementById('stopbutton').innerHTML=""; + } + if (returnedData["repeat"]=="1") { + document.getElementById('repeatstate').innerHTML=""; + } else { + document.getElementById('repeatstate').innerHTML=""; + } + document.getElementById('volume_total').innerHTML="
"; + + var items = document.getElementById('items'); + var table = items.getElementsByClassName('track'); + var current_track="track_"+trackNo; + for (var i = 0; i < table.length; i++) { + if (table[i].id==current_track) { + table[i].classList.add("itemActive"); + } else { + table[i].classList.remove("itemActive") + } + } + + }; + + 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 playlistMenuText = "\ + \ + \ +
\ + \ + \ + \ +
"; + + var itemsText="\ + \ + \ + "; + + 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; + + itemsText = itemsText + "\ + \ + \ + \ + \ + \ + \ + "; + } + + itemsText = itemsText + "
TitleControls
\ + "+(Number(id)+1)+"\ + "+name+"\ + \ + \ + \ + \ + \ + \ +
"; + + document.getElementById('items').innerHTML=itemsText; + document.getElementById('playlist_menu_top').innerHTML=playlistMenuText; + document.getElementById('playlist_menu_bottom').innerHTML=playlistMenuText; +}; + +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 playlistMenuText = "\ + \ + \ +
\ + \ +
"; + + var itemsText= "\ + \ + \ + "; + + var even = 0; + if (dir) { + var lastSlash=dir.lastIndexOf("/"); + if (lastSlash>0) { + var upperLevel=dir.slice(0,lastSlash); + } else { + var upperLevel=""; + } + even = ! even; + var itemsText = itemsText + "\ + \ + \ + "; + } + + 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; + + itemsText = itemsText + "\ + \ + "; + + if (type == "directory") { + itemsText = itemsText + ""; + }; + + if (type == "file") { + itemsText = itemsText + ""; + }; + + itemsText = itemsText + ""; + + } + + } + + var itemsText = itemsText+"
TitleControls
..
\ + \ + "+tailName+"\ + \ + "+tailName+"\ +
"; + document.getElementById('items').innerHTML=itemsText; + document.getElementById('playlist_menu_top').innerHTML=playlistMenuText; + document.getElementById('playlist_menu_bottom').innerHTML=playlistMenuText; +}; + +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); + playlistMenuText="\ + \ + \ +
\ + \ +
"; + itemsText="\ + \ + \ + "; + + var even = 0; + for (var key in returnedData) { + var name=returnedData[key]; + + if (even) { + evText="itemEven"; + } else { + evText="itemOdd"; + }; + + even = ! even; + + itemsText = itemsText + "\ + \ + \ + \ + "; + } + + itemsText=itemsText+"
NameControls
"+name+"
"; + document.getElementById('items').innerHTML=itemsText; + document.getElementById('playlist_menu_top').innerHTML=playlistMenuText; + document.getElementById('playlist_menu_bottom').innerHTML=playlistMenuText; +}; + +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; + LoadPlayList(); + RefreshPageStatus(); +}; + +req.open("GET", urlbase+"lists|delete|"+item, true); +req.send(); + +} + +function RefreshPageContent() { + RefreshPlaylist(); + RefreshPageStatus(); +} + +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(); + +} + +function subscribe_status() { + var xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = function() { + if (this.readyState != 4) return; + if (this.status == 200) { + RefreshPageStatus() + setTimeout(subscribe_status,1000) + } else { + setTimeout(subscribe_status,15000) + } + } + xhr.open("GET", urlbase+"idle", true); + xhr.send(); +} + +setTimeout(subscribe_status,5000) +setInterval(PeriodicRefreshTime, 1000); diff --git a/mpd.lua b/mpd.lua index b5e180d..8935c9a 100755 --- a/mpd.lua +++ b/mpd.lua @@ -1,6 +1,7 @@ #!/usr/bin/lua -require "uci" +local hasuci,uci = pcall(require,"uci") + require("socket") json=require("json") @@ -160,16 +161,44 @@ function mpd_send(mpd,action,raw) return values end -x = uci.cursor() +if hasuci then + + x = uci.cursor() + + settings = {} + settings['host'] = x.get("mpd","server","host") or "localhost" + settings['port'] = x.get("mpd","server","port") or 6600 + settings['timeout'] = x.get("mpd","server","timeout") or 1 + + volstep = x.get("mpd","control","volume_step") or 3 -settings = {} -settings['host'] = x.get("mpd","server","host") or "localhost" -settings['port'] = x.get("mpd","server","port") or 6600 -settings['timeout'] = x.get("mpd","server","timeout") or 1 + password = x.get("mpd","server","password") -volstep = x.get("mpd","control","volume_step") or 3 +else + + config = arg[1] + if not config then + config="/etc/mpd-lua.json" + end -password = x.get("mpd","server","password") + settings={} + local open = io.open + local file = open(config, "r") + if file then + local content = file:read "*a" + file:close() + settings=json.decode(content) + end + + settings['host'] = settings['host'] or "localhost" + settings['port'] = settings["port"] or 6600 + settings['timeout'] = settings["timeout"] or 1 + + volstep = settings["volstep"] or 3 + +end + + if password then settings["password"] = password end @@ -290,8 +319,7 @@ elseif command=="status" then if song then res['current_playing']=pl[song]['name'] - else - res['song']="--" + else res['current_playing']="---" end diff --git a/mpd.lua~ b/mpd.lua~ new file mode 100755 index 0000000..97ac4f4 --- /dev/null +++ b/mpd.lua~ @@ -0,0 +1,417 @@ +#!/usr/bin/lua + +local hasuci,uci = pcall(require,"uci") + +require("socket") +json=require("json") + +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 + +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 + +if hasuci then + + x = uci.cursor() + + settings = {} + settings['host'] = x.get("mpd","server","host") or "localhost" + settings['port'] = x.get("mpd","server","port") or 6600 + settings['timeout'] = x.get("mpd","server","timeout") or 1 + + volstep = x.get("mpd","control","volume_step") or 3 + + password = x.get("mpd","server","password") + +else + + config = arg[1] + if not config then + config="/etc/mpd-lua.json" + end + + settings={} + local open = io.open + local file = open(config, "r") + if file then + local content = file:read "*a" + file:close() + settings=json.decode(content) + end + + settings['host'] = settings['host'] or "localhost" + settings['port'] = settings["port"] or 6600 + settings['timeout'] = settings["timeout"] or 1 + + volstep = settings["volstep"] or 3 + +end + + +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="idle" +end + +if command=="play" or command=="pause" or command=="stop" then + + res=mpd_send(m,command) + +elseif command=="previous" or command=="next" then + + res=mpd_send(m,"play") + res=mpd_send(m,command) + +elseif command=="idle" then + + m.timeout=30 + res=mpd_send(m,command) + +elseif command=="vold" then + + status=mpd_send(m,"status") + volume=tonumber(status["volume"]) + res=mpd_send(m,"setvol "..(volume-volstep)) + +elseif command=="volu" then + + status=mpd_send(m,"status") + volume=tonumber(status["volume"]) + res=mpd_send(m,"setvol "..(volume+volstep)) + +elseif string.starts(command,"fastfwd") then + + cmd=split(command,"|") + skip=tonumber(cmd[2]) + if not skip then + skip=15 + end + + status=mpd_send(m,"status") + rec_time=status["time"] + song=status["song"] + + if song then + + if rec_time then + rec_time=split(rec_time,":") + cur_time=tonumber(rec_time[1]) + + track_time=tonumber(rec_time[2]) + cur_time=cur_time+skip + if cur_time>track_time then + cur_time=track_time + end + + mpd_send(m,"seek "..song.." "..cur_time) + + else + + mpd_send(m,"play") + + end + + end + + res={} + +elseif string.starts(command,"rewind") then + + cmd=split(command,"|") + skip=tonumber(cmd[2]) + if not skip then + skip=15 + end + + status=mpd_send(m,"status") + rec_time=status["time"] + song=status["song"] + + if song then + + if rec_time then + rec_time=split(rec_time,":") + cur_time=tonumber(rec_time[1]) + + track_time=tonumber(rec_time[2]) + cur_time=cur_time-skip + if cur_time<0 then + cur_time=0 + end + + mpd_send(m,"seek "..song.." "..cur_time) + + else + + mpd_send(m,"play") + mpd_send(m,"previous") + + end + + end + + res={} + +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['song']="--" + res['current_playing']="---" + 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 -- 2.34.1