1 // Written by Mike Frysinger <vapier@gmail.com>. Released into the public domain. Suck it.
3 function Mpc(socket, cb_update_state, debug_enabled) {
5 this._cb_update_state = cb_update_state;
6 this._debug_enabled = debug_enabled;
7 this._queue = ['init'];
8 this._recv_buffer = [];
9 this._recv_buffer_last = 0;
13 Mpc.prototype.log = function(lvl, msg, obj) {
14 if (this._debug_enabled & lvl)
15 console.log('mpc: ' + msg, obj);
18 Mpc.prototype.err = function(msg, obj) {
19 console.error('mpc: ' + msg, obj);
22 Mpc.prototype.set_debug = function(val) {
23 this._debug_enabled = val;
26 Mpc.prototype.send = function(msg) {
28 this._queue.push(msg);
29 this._socket.send(msg, function(x) {
30 _this.log(0x1, 'send: ' + msg + ':', x);
31 if (x.bytesWritten < 0) {
32 _this.log(0x1, 'reconnecting...');
33 _this._socket.reconnect();
35 _this._socket.send(msg);
40 Mpc.prototype._parse_result = function(lines) {
45 lines.forEach(function(line) {
46 i = line.indexOf(':');
48 return; // Ignores the OK line
49 key = line.substr(0, i);
51 val = line.substr(i + 2);
61 Mpc.prototype.recv_msg = function(lines) {
63 var curr = this._queue.shift();
64 this.log(0x2, 'recv: [' + curr + ']:', lines.join('\n'));
65 if (lines[0].substr(0, 4) == 'ACK ')
66 this.err(curr, lines.join('\n'));
67 curr = curr.split(' ');
71 var i = 2, playlist = [], song;
72 while (i < lines.length) {
73 if (lines[i].substr(0, 5) == 'file:') {
74 song = this._parse_result(lines.splice(0, i + 1)).state;
75 playlist = playlist.concat(song);
80 this.state.Playlist = playlist;
81 this.state.Playlist.lastUpdate = (new Date()).getTime();
82 this._cb_update_state(this.state);
86 this.state.Currentsong = this._parse_result(lines).state;
87 this.state.Currentsong.lastUpdate = (new Date()).getTime();
88 this._cb_update_state(this.state);
93 ret = this._parse_result(lines);
97 // When mpd is stopped, it gives us back crap values for some things.
98 if ('state' in state && state.state == 'stop') {
99 if ('volume' in state && state.volume == '-1')
100 keys.splice(keys.indexOf('volume'), 1);
102 // Now merge the current state with the previous one so that we don't
103 // lose information like volume or song position.
104 var curr_state = this.state;
105 keys.forEach(function(key) {
106 curr_state[key] = state[key];
109 this._cb_update_state(curr_state);
113 this._cb_update_state(lines, curr);
118 Mpc.prototype.recv = function(msg) {
119 /* We can get back a bunch of responses in a row, so parse them out */
120 var lines = this._recv_buffer = this._recv_buffer.concat(msg.split('\n'));
122 while (i < lines.length) {
123 if (lines[i] == 'OK' || lines[i].substr(0, 3) == 'OK ' ||
124 lines[i].substr(0, 4) == 'ACK ') {
125 this.recv_msg(lines.splice(0, i + 1));
131 if (lines.length && this._recv_buffer_last != lines.length) {
132 // Keep sucking in data so long as more exists.
133 this._recv_buffer_last = lines.length;
136 this._recv_buffer_last = lines.length;
140 * Command generator helpers.
143 Mpc.__make_send_void = function(cmd) {
144 return function() { this.send(cmd); }
147 Mpc.__make_send_arg1 = function(cmd) {
148 return function(a1) {
149 if (a1 === undefined)
150 this.err(cmd + ': function requires one argument');
152 this.send(cmd + ' ' + a1);
156 Mpc.__make_send_arg2 = function(cmd) {
157 return function(a1, a2) {
158 if (a1 === undefined || a2 === undefined)
159 this.err(cmd + ': function requires two arguments');
161 this.send(cmd + ' ' + a1 + ' ' + a2);
165 Mpc.__make_send_arg3 = function(cmd) {
166 return function(a1, a2, a3) {
167 if (a1 === undefined || a2 === undefined || a3 == undefined)
168 this.err(cmd + ': function requires three arguments');
170 this.send(cmd + ' ' + a1 + ' ' + a2 + ' ' + a3);
174 Mpc.__make_send_opt = function(cmd) {
175 return function(arg) {
176 if (arg === undefined)
178 this.send(cmd + ' ' + arg);
182 Mpc.__make_send_range = function(cmd, min, max, def) {
183 return function(arg) {
184 if (arg === undefined)
186 if (arg >= min && arg <= max)
187 this.send(cmd + ' ' + arg);
189 this.err(cmd + ': arg must be [' + min + ',' + max + '] but got "' + arg + '"');
194 * Querying MPD's status
195 * http://www.musicpd.org/doc/protocol/ch03.html#idp118752
199 Mpc.prototype.clearerror = Mpc.__make_send_void('clearerror');
201 Mpc.prototype.currentsong = Mpc.__make_send_void('currentsong');
202 // idle [SUBSYSTEMS...]
205 Mpc.prototype.status = Mpc.__make_send_void('status');
207 Mpc.prototype.stats = Mpc.__make_send_void('stats');
211 * http://www.musicpd.org/doc/protocol/ch03s02.html
215 Mpc.prototype.consume = Mpc.__make_send_range('consume', 0, 1, 1);
216 // crossfade {SECONDS}
217 Mpc.prototype.crossfade = Mpc.__make_send_arg1('crossfade');
218 // mixrampdb {deciBels}
219 Mpc.prototype.mixrampdb = Mpc.__make_send_arg1('mixrampdb');
220 // mixrampdelay {SECONDS|nan}
221 // Note: Probably should handle javascript NaN here.
222 Mpc.prototype.mixrampdelay = Mpc.__make_send_arg1('mixrampdelay');
224 Mpc.prototype.random = Mpc.__make_send_range('random', 0, 1, 1);
226 Mpc.prototype.repeat = Mpc.__make_send_range('repeat', 0, 1, 1);
228 Mpc.prototype.setvol = Mpc.__make_send_range('setvol', 0, 100);
230 Mpc.prototype.single = Mpc.__make_send_range('single', 0, 1, 1);
231 // replay_gain_mode {MODE}
232 Mpc.prototype.replay_gain_mode = Mpc.__make_send_arg1('replay_gain_mode');
233 // replay_gain_status
236 * Controlling playback
237 * http://www.musicpd.org/doc/protocol/ch03s03.html
241 Mpc.prototype.next = Mpc.__make_send_void('next');
243 Mpc.prototype.pause = Mpc.__make_send_range('pause', 0, 1, 1);
245 Mpc.prototype.play = Mpc.__make_send_opt('play');
247 Mpc.prototype.playid = Mpc.__make_send_opt('playid');
249 Mpc.prototype.previous = Mpc.__make_send_void('previous');
250 // seek {SONGPOS} {TIME}
251 Mpc.prototype.seek = Mpc.__make_send_arg2('seek');
252 // seekid {SONGID} {TIME}
253 Mpc.prototype.seekid = Mpc.__make_send_arg2('seekid');
255 Mpc.prototype.seekcur = Mpc.__make_send_arg1('seekcur');
257 Mpc.prototype.stop = Mpc.__make_send_void('stop');
260 * The current playlist
261 * http://www.musicpd.org/doc/protocol/ch03s04.html
265 Mpc.prototype.add = Mpc.__make_send_arg1('add');
266 // addid {URI} [POSITION]
267 // TODO: handle position
268 Mpc.prototype.addid = Mpc.__make_send_arg1('addid');
270 Mpc.prototype.clear = Mpc.__make_send_void('clear');
271 // delete [{POS} | {START:END}]
272 Mpc.prototype.delete = Mpc.__make_send_arg1('delete');
274 Mpc.prototype.deleteid = Mpc.__make_send_arg1('deleteid');
275 // move [{FROM} | {START:END}] {TO}
276 Mpc.prototype.move = Mpc.__make_send_arg2('move');
277 // moveid {FROM} {TO}
278 Mpc.prototype.moveid = Mpc.__make_send_arg2('moveid');
280 Mpc.prototype.playlist = Mpc.__make_send_void('playlist');
281 // playlistfind {TAG} {NEEDLE}
282 Mpc.prototype.playlistfind = Mpc.__make_send_arg2('playlistfind');
283 // playlistid {SONGID}
284 Mpc.prototype.playlistid = Mpc.__make_send_arg1('playlistid');
285 // playlistinfo [[SONGPOS] | [START:END]]
286 Mpc.prototype.playlistinfo = Mpc.__make_send_opt('playlistinfo');
287 // playlistsearch {TAG} {NEEDLE}
288 Mpc.prototype.playlistsearch = Mpc.__make_send_arg2('playlistsearch');
289 // plchanges {VERSION}
290 Mpc.prototype.plchanges = Mpc.__make_send_arg1('plchanges');
291 // plchangesposid {VERSION}
292 Mpc.prototype.plchangesposid = Mpc.__make_send_arg1('plchangesposid');
293 // prio {PRIORITY} {START:END...}
294 Mpc.prototype.prio = Mpc.__make_send_arg2('prio');
295 // prioid {PRIORITY} {ID...}
296 Mpc.prototype.prioid = Mpc.__make_send_arg2('prioid');
297 // shuffle [START:END]
298 Mpc.prototype.shuffle = Mpc.__make_send_opt('shuffle');
299 // swap {SONG1} {SONG2}
300 Mpc.prototype.swap = Mpc.__make_send_arg2('swap');
301 // swapid {SONG1} {SONG2}
302 Mpc.prototype.swapid = Mpc.__make_send_arg2('swapid');
306 * http://www.musicpd.org/doc/protocol/ch03s05.html
309 // listplaylist {NAME}
310 Mpc.prototype.listplaylist = Mpc.__make_send_arg1('listplaylist');
311 // listplaylistinfo {NAME}
312 Mpc.prototype.listplaylistinfo = Mpc.__make_send_arg1('listplaylistinfo');
314 Mpc.prototype.listplaylists = Mpc.__make_send_void('listplaylists');
315 // load {NAME} [START:END]
316 // TODO: handle optional start:end
317 Mpc.prototype.load = Mpc.__make_send_arg1('load');
318 // playlistadd {NAME} {URI}
319 Mpc.prototype.playlistadd = Mpc.__make_send_arg2('playlistadd');
320 // playlistclear {NAME}
321 Mpc.prototype.playlistclear = Mpc.__make_send_arg1('playlistclear');
322 // playlistdelete {NAME} {SONGPOS}
323 Mpc.prototype.playlistdelete = Mpc.__make_send_arg2('playlistdelete');
324 // playlistmove {NAME} {SONGID} {SONGPOS}
325 Mpc.prototype.playlistmove = Mpc.__make_send_arg3('playlistmove');
326 // rename {NAME} {NEW_NAME}
327 Mpc.prototype.rename = Mpc.__make_send_arg2('rename');
329 Mpc.prototype.rm = Mpc.__make_send_arg1('rm');
331 Mpc.prototype.save = Mpc.__make_send_arg1('save');
335 * http://www.musicpd.org/doc/protocol/ch03s06.html
339 * Connection settings
340 * http://www.musicpd.org/doc/protocol/ch03s08.html
344 Mpc.prototype.close = Mpc.__make_send_void('close');
346 Mpc.prototype.kill = Mpc.__make_send_void('kill');
347 // password {PASSWORD}
348 Mpc.prototype.password = Mpc.__make_send_arg1('password');
350 Mpc.prototype.ping = Mpc.__make_send_void('ping');
353 * Audio output devices
354 * http://www.musicpd.org/doc/protocol/ch03s09.html
357 // disableoutput {ID}
358 Mpc.prototype.disableoutput = Mpc.__make_send_arg1('disableoutput');
360 Mpc.prototype.enableoutput = Mpc.__make_send_arg1('enableoutput');
362 Mpc.prototype.outputs = Mpc.__make_send_void('outputs');
366 * http://www.musicpd.org/doc/protocol/ch03s10.html
370 Mpc.prototype.config = Mpc.__make_send_void('config');
372 Mpc.prototype.commands = Mpc.__make_send_void('commands');
374 Mpc.prototype.notcommands = Mpc.__make_send_void('notcommands');
376 Mpc.prototype.tagtypes = Mpc.__make_send_void('tagtypes');
378 Mpc.prototype.urlhandlers = Mpc.__make_send_void('urlhandlers');
380 Mpc.prototype.decoders = Mpc.__make_send_void('decoders');
384 * http://www.musicpd.org/doc/protocol/ch03s11.html
388 Mpc.prototype.subscribe = Mpc.__make_send_arg1('subscribe');
389 // unsubscribe {NAME}
390 Mpc.prototype.unsubscribe = Mpc.__make_send_arg1('unsubscribe');
392 Mpc.prototype.channels = Mpc.__make_send_void('channels');
394 Mpc.prototype.readmessages = Mpc.__make_send_void('readmessages');
395 // sendmessage {CHANNEL} {TEXT}
396 Mpc.prototype.sendmessage = Mpc.__make_send_arg2('sendmessage');