X-Git-Url: https://git.wh0rd.org/?p=chrome-ext%2Fmusic-player-client.git;a=blobdiff_plain;f=js%2Fmpc.js;h=84eea050a9970ff47ff69ae6e3867dad378f2bef;hp=7093ed38bd8c8f3df2758286263919aa05b7afe8;hb=HEAD;hpb=f5ed7157515b1aa6ca9bb0d70818bb1e43139200 diff --git a/js/mpc.js b/js/mpc.js index 7093ed3..84eea05 100644 --- a/js/mpc.js +++ b/js/mpc.js @@ -1,46 +1,114 @@ // Written by Mike Frysinger . Released into the public domain. Suck it. -function Mpc(socket, cb_update_state) { +function Mpc(socket, cb_update_state, debug_enabled) { this._socket = socket; this._cb_update_state = cb_update_state; + this._debug_enabled = debug_enabled; this._queue = ['init']; + this._recv_buffer = []; + this._recv_buffer_last = 0; this.state = {}; } -Mpc.log = function(msg, obj) { - console.log('mpc: ' + msg, obj); +Mpc.prototype.log = function(lvl, msg, obj) { + if (this._debug_enabled & lvl) + console.log('mpc: ' + msg, obj); +} + +Mpc.prototype.err = function(msg, obj) { + console.error('mpc: ' + msg, obj); +} + +Mpc.prototype.set_debug = function(val) { + this._debug_enabled = val; } Mpc.prototype.send = function(msg) { + var _this = this; this._queue.push(msg); this._socket.send(msg, function(x) { - Mpc.log('send: ' + msg + ':', x); + _this.log(0x1, 'send: ' + msg + ':', x); + if (x.bytesWritten < 0) { + _this.log(0x1, 'reconnecting...'); + _this._socket.reconnect(); + _this.queue = [msg]; + _this._socket.send(msg); + } + }); +} + +Mpc.prototype._parse_result = function(lines) { + var state = {}; + var keys = []; + var key, val, i; + + lines.forEach(function(line) { + i = line.indexOf(':'); + if (i == -1) + return; // Ignores the OK line + key = line.substr(0, i); + keys.push(key); + val = line.substr(i + 2); + state[key] = val; }); + + return { + 'state': state, + 'keys': keys, + }; } Mpc.prototype.recv_msg = function(lines) { - curr = this._queue.shift(); - Mpc.log('recv: [' + curr + ']:', lines.join('\n')); + var state, keys, ret; + var curr = this._queue.shift(); + this.log(0x2, 'recv: [' + curr + ']:', lines.join('\n')); + if (lines[0].substr(0, 4) == 'ACK ') + this.err(curr, lines.join('\n')); curr = curr.split(' '); switch (curr[0]) { - // Needs to return a list of dicts (see above for dicts). - //case 'playlistinfo': + case 'playlistinfo': + var i = 2, playlist = [], song; + while (i < lines.length) { + if (lines[i].substr(0, 5) == 'file:') { + song = this._parse_result(lines.splice(0, i + 1)).state; + playlist = playlist.concat(song); + i = 0; + } else + ++i; + } + this.state.Playlist = playlist; + this.state.Playlist.lastUpdate = (new Date()).getTime(); + this._cb_update_state(this.state); + break; + case 'currentsong': + this.state.Currentsong = this._parse_result(lines).state; + this.state.Currentsong.lastUpdate = (new Date()).getTime(); + this._cb_update_state(this.state); + break; + case 'stats': case 'status': - state = {}; - lines.forEach(function(line) { - i = line.indexOf(':'); - if (i == -1) - return; // Ignores the OK line - key = line.substr(0, i); - val = line.substr(i + 2); - state[key] = val; + ret = this._parse_result(lines); + state = ret.state; + keys = ret.keys; + + // When mpd is stopped, it gives us back crap values for some things. + if ('state' in state && state.state == 'stop') { + if ('volume' in state && state.volume == '-1') + keys.splice(keys.indexOf('volume'), 1); + } + // Now merge the current state with the previous one so that we don't + // lose information like volume or song position. + var curr_state = this.state; + keys.forEach(function(key) { + curr_state[key] = state[key]; }); - this.state = state; - this._cb_update_state(state); + + this._cb_update_state(curr_state); break; + default: this._cb_update_state(lines, curr); break; @@ -49,16 +117,23 @@ Mpc.prototype.recv_msg = function(lines) { Mpc.prototype.recv = function(msg) { /* We can get back a bunch of responses in a row, so parse them out */ - /* XXX: Do we have to handle partial reads ? like long playlists ... */ - lines = msg.split('\n'); + var lines = this._recv_buffer = this._recv_buffer.concat(msg.split('\n')); var i = 0; while (i < lines.length) { - if (lines[i] == 'OK' || lines[i].substr(0, 3) == 'OK ') { + if (lines[i] == 'OK' || lines[i].substr(0, 3) == 'OK ' || + lines[i].substr(0, 4) == 'ACK ') { this.recv_msg(lines.splice(0, i + 1)); i = 0; } else ++i; } + + if (lines.length && this._recv_buffer_last != lines.length) { + // Keep sucking in data so long as more exists. + this._recv_buffer_last = lines.length; + this._socket.poll(); + } else + this._recv_buffer_last = lines.length; } /* @@ -72,7 +147,7 @@ Mpc.__make_send_void = function(cmd) { Mpc.__make_send_arg1 = function(cmd) { return function(a1) { if (a1 === undefined) - Mpc.log(cmd + ': function requires one argument'); + this.err(cmd + ': function requires one argument'); else this.send(cmd + ' ' + a1); } @@ -81,12 +156,21 @@ Mpc.__make_send_arg1 = function(cmd) { Mpc.__make_send_arg2 = function(cmd) { return function(a1, a2) { if (a1 === undefined || a2 === undefined) - Mpc.log(cmd + ': function requires two arguments'); + this.err(cmd + ': function requires two arguments'); else this.send(cmd + ' ' + a1 + ' ' + a2); } } +Mpc.__make_send_arg3 = function(cmd) { + return function(a1, a2, a3) { + if (a1 === undefined || a2 === undefined || a3 == undefined) + this.err(cmd + ': function requires three arguments'); + else + this.send(cmd + ' ' + a1 + ' ' + a2 + ' ' + a3); + } +} + Mpc.__make_send_opt = function(cmd) { return function(arg) { if (arg === undefined) @@ -102,7 +186,7 @@ Mpc.__make_send_range = function(cmd, min, max, def) { if (arg >= min && arg <= max) this.send(cmd + ' ' + arg); else - Mpc.log(cmd + ': arg must be [' + min + ',' + max + '] but got "' + arg + '"'); + this.err(cmd + ': arg must be [' + min + ',' + max + '] but got "' + arg + '"'); }; } @@ -168,10 +252,89 @@ Mpc.prototype.seek = Mpc.__make_send_arg2('seek'); // seekid {SONGID} {TIME} Mpc.prototype.seekid = Mpc.__make_send_arg2('seekid'); // seekcur {TIME} -Mpc.prototype.seekcur = Mpc.__make_send_arg1('seek'); +Mpc.prototype.seekcur = Mpc.__make_send_arg1('seekcur'); // stop Mpc.prototype.stop = Mpc.__make_send_void('stop'); +/* + * The current playlist + * http://www.musicpd.org/doc/protocol/ch03s04.html + */ + +// add {URI} +Mpc.prototype.add = Mpc.__make_send_arg1('add'); +// addid {URI} [POSITION] +// TODO: handle position +Mpc.prototype.addid = Mpc.__make_send_arg1('addid'); +// clear +Mpc.prototype.clear = Mpc.__make_send_void('clear'); +// delete [{POS} | {START:END}] +Mpc.prototype.delete = Mpc.__make_send_arg1('delete'); +// deleteid {SONGID} +Mpc.prototype.deleteid = Mpc.__make_send_arg1('deleteid'); +// move [{FROM} | {START:END}] {TO} +Mpc.prototype.move = Mpc.__make_send_arg2('move'); +// moveid {FROM} {TO} +Mpc.prototype.moveid = Mpc.__make_send_arg2('moveid'); +// playlist +Mpc.prototype.playlist = Mpc.__make_send_void('playlist'); +// playlistfind {TAG} {NEEDLE} +Mpc.prototype.playlistfind = Mpc.__make_send_arg2('playlistfind'); +// playlistid {SONGID} +Mpc.prototype.playlistid = Mpc.__make_send_arg1('playlistid'); +// playlistinfo [[SONGPOS] | [START:END]] +Mpc.prototype.playlistinfo = Mpc.__make_send_opt('playlistinfo'); +// playlistsearch {TAG} {NEEDLE} +Mpc.prototype.playlistsearch = Mpc.__make_send_arg2('playlistsearch'); +// plchanges {VERSION} +Mpc.prototype.plchanges = Mpc.__make_send_arg1('plchanges'); +// plchangesposid {VERSION} +Mpc.prototype.plchangesposid = Mpc.__make_send_arg1('plchangesposid'); +// prio {PRIORITY} {START:END...} +Mpc.prototype.prio = Mpc.__make_send_arg2('prio'); +// prioid {PRIORITY} {ID...} +Mpc.prototype.prioid = Mpc.__make_send_arg2('prioid'); +// shuffle [START:END] +Mpc.prototype.shuffle = Mpc.__make_send_opt('shuffle'); +// swap {SONG1} {SONG2} +Mpc.prototype.swap = Mpc.__make_send_arg2('swap'); +// swapid {SONG1} {SONG2} +Mpc.prototype.swapid = Mpc.__make_send_arg2('swapid'); + +/* + * Stored playlists + * http://www.musicpd.org/doc/protocol/ch03s05.html + */ + +// listplaylist {NAME} +Mpc.prototype.listplaylist = Mpc.__make_send_arg1('listplaylist'); +// listplaylistinfo {NAME} +Mpc.prototype.listplaylistinfo = Mpc.__make_send_arg1('listplaylistinfo'); +// listplaylists +Mpc.prototype.listplaylists = Mpc.__make_send_void('listplaylists'); +// load {NAME} [START:END] +// TODO: handle optional start:end +Mpc.prototype.load = Mpc.__make_send_arg1('load'); +// playlistadd {NAME} {URI} +Mpc.prototype.playlistadd = Mpc.__make_send_arg2('playlistadd'); +// playlistclear {NAME} +Mpc.prototype.playlistclear = Mpc.__make_send_arg1('playlistclear'); +// playlistdelete {NAME} {SONGPOS} +Mpc.prototype.playlistdelete = Mpc.__make_send_arg2('playlistdelete'); +// playlistmove {NAME} {SONGID} {SONGPOS} +Mpc.prototype.playlistmove = Mpc.__make_send_arg3('playlistmove'); +// rename {NAME} {NEW_NAME} +Mpc.prototype.rename = Mpc.__make_send_arg2('rename'); +// rm {NAME} +Mpc.prototype.rm = Mpc.__make_send_arg1('rm'); +// save {NAME} +Mpc.prototype.save = Mpc.__make_send_arg1('save'); + +/* + * The music database + * http://www.musicpd.org/doc/protocol/ch03s06.html + */ + /* * Connection settings * http://www.musicpd.org/doc/protocol/ch03s08.html @@ -185,3 +348,49 @@ Mpc.prototype.kill = Mpc.__make_send_void('kill'); Mpc.prototype.password = Mpc.__make_send_arg1('password'); // ping Mpc.prototype.ping = Mpc.__make_send_void('ping'); + +/* + * Audio output devices + * http://www.musicpd.org/doc/protocol/ch03s09.html + */ + +// disableoutput {ID} +Mpc.prototype.disableoutput = Mpc.__make_send_arg1('disableoutput'); +// enableoutput {ID} +Mpc.prototype.enableoutput = Mpc.__make_send_arg1('enableoutput'); +// outputs +Mpc.prototype.outputs = Mpc.__make_send_void('outputs'); + +/* + * Reflection + * http://www.musicpd.org/doc/protocol/ch03s10.html + */ + +// config +Mpc.prototype.config = Mpc.__make_send_void('config'); +// commands +Mpc.prototype.commands = Mpc.__make_send_void('commands'); +// notcommands +Mpc.prototype.notcommands = Mpc.__make_send_void('notcommands'); +// tagtypes +Mpc.prototype.tagtypes = Mpc.__make_send_void('tagtypes'); +// urlhandlers +Mpc.prototype.urlhandlers = Mpc.__make_send_void('urlhandlers'); +// decoders +Mpc.prototype.decoders = Mpc.__make_send_void('decoders'); + +/* + * Client to client + * http://www.musicpd.org/doc/protocol/ch03s11.html + */ + +// subscribe {NAME} +Mpc.prototype.subscribe = Mpc.__make_send_arg1('subscribe'); +// unsubscribe {NAME} +Mpc.prototype.unsubscribe = Mpc.__make_send_arg1('unsubscribe'); +// channels +Mpc.prototype.channels = Mpc.__make_send_void('channels'); +// readmessages +Mpc.prototype.readmessages = Mpc.__make_send_void('readmessages'); +// sendmessage {CHANNEL} {TEXT} +Mpc.prototype.sendmessage = Mpc.__make_send_arg2('sendmessage');