cmus-remote-node
Version:
API to cmus-remote for controlling cmus
213 lines (191 loc) • 5.18 kB
JavaScript
"use strict";
const spawn=require("child_process").spawn;
const READ_TIMEOUT=50;
const KILL_TIMEOUT=1000;
function mProcessClosed(){
this._.open=false;
if(this._.killtimeout){
clearTimeout(this._.killtimeout);
}
}
function mCheckQueue(){
if(!this._.open){
throw new Error("cmus-remote process was closed");
}
if(!this._.datahandler&&this._.queue.length>0){
const [l,h,eR]=this._.queue.shift();
// console.log("Committing write <<"+l+">>");
this._.proc.stdin.write(l+"\n","utf8");
if(eR){
this._.datahandler=h;
} else {
h.call(this);
mCheckQueue.call(this);
}
}
}
function mSubmitLine(line,expectResponse,handler){
if(!handler)throw 0;
this._.queue.push([line,handler,expectResponse]);
mCheckQueue.call(this);
}
class CmusRemote{
constructor(socketloc /* optional */){
this._={};
let args=[];
if(socketloc)args=args.concat(["--server",socketloc]);
this._.proc=spawn("cmus-remote",args,{
stdio: ["pipe","pipe",process.stderr]
});
this._.open=true;
this._.proc.on("close",mProcessClosed.bind(this));
this._.proc.on("exit",mProcessClosed.bind(this));
this._.proc.on("error",(err)=>{
throw err;
});
this._.queue=[];
let buffer="";
this._.proc.stdout.on("data",(data)=>{
if(data instanceof Buffer){
data=new String(data);
}
buffer+=data;
if(this._.readtimeout){
clearTimeout(this._.readtimeout);
}
this._.readtimeout=setTimeout((()=>{
// console.log("Readtimeout: buffer=<<"+buffer+">>, datahandler=<<"+this._.datahandler+">>, queue=<<"+this._.queue+">>");
if(buffer.length>0&&this._.datahandler){
let e=null;
try {
this._.datahandler.call(this,buffer);
} catch(err){
e=err;
}
buffer="";
this._.datahandler=null;
mCheckQueue.call(this);
if(e)throw e;
}
}).bind(this),READ_TIMEOUT);
});
}
close(){
this._.proc.stdin.end();
this._.killtimeout=setTimeout((()=>{
this._.proc.kill("SIGTERM");
this._.killtimeout=setTimeout((()=>{
this._.proc.kill("SIGKILL");
this._.killtimeout=null;
}).bind(this),KILL_TIMEOUT);
}).bind(this),KILL_TIMEOUT);
}
ready(){
return this._.open;
}
status(cb){
mSubmitLine.call(this,"status",true,(str)=>{
// console.log("Status internal callback called");
const lines=str.split("\n");
const obj={
track: {},
settings: {}
};
for(const line of lines){
if(line.length==0)continue;
const idx=line.indexOf(" ");
if(idx==-1){
throw new Error("Unexpected line on 'status' output from cmus-remote: '"+line+"'");
}
const head=line.slice(0,idx),rest=line.slice(idx+1);
if(head=="status"){
obj.status=rest;
} else if(head=="file"||head=="duration"||head=="position"){
obj.track[head]=rest;
} else if(head=="tag"||head=="set"){
const idx=rest.indexOf(" ");
if(idx==-1){
throw new Error("Unexpected line on 'status' output from cmus-remote: '"+line+"'");
}
const tag=rest.slice(0,idx),rest2=rest.slice(idx+1);
obj[head=="tag"?"track":"settings"][tag]=rest2;
}
}
cb(obj);
});
}
// This requests data of THE ENTIRE LIBRARY. Use with care on large libraries.
getLibrary(cb){
mSubmitLine.call(this,"save -e -l -",true,(str)=>{
const lines=str.split("\n");
const tracks=[];
let track=null;
for(const line of lines){
if(line.length==0)continue;
const idx=line.indexOf(" ");
if(idx==-1){
throw new Error("Unexpected line on 'save -l -' output from cmus-remote: '"+line+"'");
}
const head=line.slice(0,idx),rest=line.slice(idx+1);
if(head=="file"){
if(track)tracks.push(track);
track={};
track.file=rest;
continue;
}
if(!track){
throw new Error("Expected 'file' key on output 'save -l -' from cmus-remote: '"+line+"'");
}
if(head=="duration"||head=="codec"||head=="bitrate"){
track[head]=rest;
} else if(head=="tag"){
const idx=rest.indexOf(" ");
if(idx==-1){
throw new Error("Unexpected line on 'save -l -' output from cmus-remote: '"+line+"'");
}
const tag=rest.slice(0,idx),rest2=rest.slice(idx+1);
track[tag]=rest2;
}
}
if(track)tracks.push(track);
cb(tracks);
});
}
play(filename /* optional */){
mSubmitLine.call(this,"player-play"+(filename?" "+filename:""),false,()=>{});
}
pause(){
mSubmitLine.call(this,"player-pause",false,()=>{});
}
next(){
mSubmitLine.call(this,"player-next",false,()=>{});
}
prev(){
mSubmitLine.call(this,"player-prev",false,()=>{});
}
stop(){
mSubmitLine.call(this,"player-stop",false,()=>{});
}
/* From the cmus man page:
*
* seek [+-](<num>[mh] | [HH:]MM:SS)
* Seek to absolute or relative position. Position can be given in seconds,
* minutes (m), hours (h) or HH:MM:SS format where HH: is optional.
*
* Seek 1 minute backward
* :seek -1m
*
* Seek 5 seconds forward
* :seek +5
*
* Seek to absolute position 1h
* :seek 1h
*
* Seek 90 seconds forward
* :seek +1:30
*/
seek(repr){
mSubmitLine.call(this,"seek "+repr,false,()=>{});
}
}
module.exports=CmusRemote;