]> git.wh0rd.org Git - chrome-ext/music-player-client.git/blob - js/mpc.js
controls: fix display of volume/position when values are unknown
[chrome-ext/music-player-client.git] / js / mpc.js
1 // Written by Mike Frysinger <vapier@gmail.com>.  Released into the public domain.  Suck it.
2
3 function Mpc(socket, cb_update_state, debug_enabled) {
4         this._socket = socket;
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;
10         this.state = {};
11 }
12
13 Mpc.prototype.log = function(lvl, msg, obj) {
14         if (this._debug_enabled & lvl)
15                 console.log('mpc: ' + msg, obj);
16 }
17
18 Mpc.prototype.err = function(msg, obj) {
19         console.error('mpc: ' + msg, obj);
20 }
21
22 Mpc.prototype.set_debug = function(val) {
23         this._debug_enabled = val;
24 }
25
26 Mpc.prototype.send = function(msg) {
27         var _this = this;
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();
34                         _this.queue = [msg];
35                         _this._socket.send(msg);
36                 }
37         });
38 }
39
40 Mpc.prototype._parse_result = function(lines) {
41         var state = {};
42         var keys = [];
43         var key, val, i;
44
45         lines.forEach(function(line) {
46                 i = line.indexOf(':');
47                 if (i == -1)
48                         return; // Ignores the OK line
49                 key = line.substr(0, i);
50                 keys.push(key);
51                 val = line.substr(i + 2);
52                 state[key] = val;
53         });
54
55         return {
56                 'state': state,
57                 'keys': keys,
58         };
59 }
60
61 Mpc.prototype.recv_msg = function(lines) {
62         var state, keys, ret;
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(' ');
68
69         switch (curr[0]) {
70         case 'playlistinfo':
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);
76                                 i = 0;
77                         } else
78                                 ++i;
79                 }
80                 this.state.Playlist = playlist;
81                 this._cb_update_state(this.state);
82                 break;
83
84         case 'currentsong':
85                 this.state.Currentsong = this._parse_result(lines).state;
86                 this._cb_update_state(this.state);
87                 break;
88
89         case 'stats':
90         case 'status':
91                 ret = this._parse_result(lines);
92                 state = ret.state;
93                 keys = ret.keys;
94
95                 // When mpd is stopped, it gives us back crap values for some things.
96                 if ('state' in state && state.state == 'stop') {
97                         if ('volume' in state && state.volume == '-1')
98                                 keys.splice(keys.indexOf('volume'), 1);
99                 }
100                 // Now merge the current state with the previous one so that we don't
101                 // lose information like volume or song position.
102                 var curr_state = this.state;
103                 keys.forEach(function(key) {
104                         curr_state[key] = state[key];
105                 });
106
107                 this._cb_update_state(curr_state);
108                 break;
109
110         default:
111                 this._cb_update_state(lines, curr);
112                 break;
113         }
114 }
115
116 Mpc.prototype.recv = function(msg) {
117         /* We can get back a bunch of responses in a row, so parse them out */
118         var lines = this._recv_buffer = this._recv_buffer.concat(msg.split('\n'));
119         var i = 0;
120         while (i < lines.length) {
121                 if (lines[i] == 'OK' || lines[i].substr(0, 3) == 'OK ' ||
122                     lines[i].substr(0, 4) == 'ACK ') {
123                         this.recv_msg(lines.splice(0, i + 1));
124                         i = 0;
125                 } else
126                         ++i;
127         }
128
129         if (lines.length && this._recv_buffer_last != lines.length) {
130                 // Keep sucking in data so long as more exists.
131                 this._recv_buffer_last = lines.length;
132                 this._socket.poll();
133         } else
134                 this._recv_buffer_last = lines.length;
135 }
136
137 /*
138  * Command generator helpers.
139  */
140
141 Mpc.__make_send_void = function(cmd) {
142         return function() { this.send(cmd); }
143 }
144
145 Mpc.__make_send_arg1 = function(cmd) {
146         return function(a1) {
147                 if (a1 === undefined)
148                         this.err(cmd + ': function requires one argument');
149                 else
150                         this.send(cmd + ' ' + a1);
151         }
152 }
153
154 Mpc.__make_send_arg2 = function(cmd) {
155         return function(a1, a2) {
156                 if (a1 === undefined || a2 === undefined)
157                         this.err(cmd + ': function requires two arguments');
158                 else
159                         this.send(cmd + ' ' + a1 + ' ' + a2);
160         }
161 }
162
163 Mpc.__make_send_arg3 = function(cmd) {
164         return function(a1, a2, a3) {
165                 if (a1 === undefined || a2 === undefined || a3 == undefined)
166                         this.err(cmd + ': function requires three arguments');
167                 else
168                         this.send(cmd + ' ' + a1 + ' ' + a2 + ' ' + a3);
169         }
170 }
171
172 Mpc.__make_send_opt = function(cmd) {
173         return function(arg) {
174                 if (arg === undefined)
175                         arg = '';
176                 this.send(cmd + ' ' + arg);
177         };
178 }
179
180 Mpc.__make_send_range = function(cmd, min, max, def) {
181         return function(arg) {
182                 if (arg === undefined)
183                         arg = def;
184                 if (arg >= min && arg <= max)
185                         this.send(cmd + ' ' + arg);
186                 else
187                         this.err(cmd + ': arg must be [' + min + ',' + max + '] but got "' + arg + '"');
188         };
189 }
190
191 /*
192  * Querying MPD's status
193  * http://www.musicpd.org/doc/protocol/ch03.html#idp118752
194  */
195
196 // clearerror
197 Mpc.prototype.clearerror          = Mpc.__make_send_void('clearerror');
198 // currentsong
199 Mpc.prototype.currentsong         = Mpc.__make_send_void('currentsong');
200 // idle [SUBSYSTEMS...]
201 // TODO
202 // status
203 Mpc.prototype.status              = Mpc.__make_send_void('status');
204 // stats
205 Mpc.prototype.stats               = Mpc.__make_send_void('stats');
206
207 /*
208  * Playback options
209  * http://www.musicpd.org/doc/protocol/ch03s02.html
210  */
211
212 // consume {STATE}
213 Mpc.prototype.consume             = Mpc.__make_send_range('consume', 0, 1, 1);
214 // crossfade {SECONDS}
215 Mpc.prototype.crossfade           = Mpc.__make_send_arg1('crossfade');
216 // mixrampdb {deciBels}
217 Mpc.prototype.mixrampdb           = Mpc.__make_send_arg1('mixrampdb');
218 // mixrampdelay {SECONDS|nan}
219 // Note: Probably should handle javascript NaN here.
220 Mpc.prototype.mixrampdelay        = Mpc.__make_send_arg1('mixrampdelay');
221 // random {STATE}
222 Mpc.prototype.random              = Mpc.__make_send_range('random', 0, 1, 1);
223 // repeat {STATE}
224 Mpc.prototype.repeat              = Mpc.__make_send_range('repeat', 0, 1, 1);
225 // setvol {VOL}
226 Mpc.prototype.setvol              = Mpc.__make_send_range('setvol', 0, 100);
227 // single {STATE}
228 Mpc.prototype.single              = Mpc.__make_send_range('single', 0, 1, 1);
229 // replay_gain_mode {MODE}
230 Mpc.prototype.replay_gain_mode    = Mpc.__make_send_arg1('replay_gain_mode');
231 // replay_gain_status
232
233 /*
234  * Controlling playback
235  * http://www.musicpd.org/doc/protocol/ch03s03.html
236  */
237
238 // next
239 Mpc.prototype.next                = Mpc.__make_send_void('next');
240 // pause {PAUSE}
241 Mpc.prototype.pause               = Mpc.__make_send_range('pause', 0, 1, 1);
242 // play [SONGPOS]
243 Mpc.prototype.play                = Mpc.__make_send_opt('play');
244 // playid [SONGID]
245 Mpc.prototype.playid              = Mpc.__make_send_opt('playid');
246 // previous
247 Mpc.prototype.previous            = Mpc.__make_send_void('previous');
248 // seek {SONGPOS} {TIME}
249 Mpc.prototype.seek                = Mpc.__make_send_arg2('seek');
250 // seekid {SONGID} {TIME}
251 Mpc.prototype.seekid              = Mpc.__make_send_arg2('seekid');
252 // seekcur {TIME}
253 Mpc.prototype.seekcur             = Mpc.__make_send_arg1('seekcur');
254 // stop
255 Mpc.prototype.stop                = Mpc.__make_send_void('stop');
256
257 /*
258  * The current playlist
259  * http://www.musicpd.org/doc/protocol/ch03s04.html
260  */
261
262 // add {URI}
263 Mpc.prototype.add                 = Mpc.__make_send_arg1('add');
264 // addid {URI} [POSITION]
265 // TODO: handle position
266 Mpc.prototype.addid               = Mpc.__make_send_arg1('addid');
267 // clear
268 Mpc.prototype.clear               = Mpc.__make_send_void('clear');
269 // delete [{POS} | {START:END}]
270 Mpc.prototype.delete              = Mpc.__make_send_arg1('delete');
271 // deleteid {SONGID}
272 Mpc.prototype.deleteid            = Mpc.__make_send_arg1('deleteid');
273 // move [{FROM} | {START:END}] {TO}
274 Mpc.prototype.move                = Mpc.__make_send_arg2('move');
275 // moveid {FROM} {TO}
276 Mpc.prototype.moveid              = Mpc.__make_send_arg2('moveid');
277 // playlist
278 Mpc.prototype.playlist            = Mpc.__make_send_void('playlist');
279 // playlistfind {TAG} {NEEDLE}
280 Mpc.prototype.playlistfind        = Mpc.__make_send_arg2('playlistfind');
281 // playlistid {SONGID}
282 Mpc.prototype.playlistid          = Mpc.__make_send_arg1('playlistid');
283 // playlistinfo [[SONGPOS] | [START:END]]
284 Mpc.prototype.playlistinfo        = Mpc.__make_send_opt('playlistinfo');
285 // playlistsearch {TAG} {NEEDLE}
286 Mpc.prototype.playlistsearch      = Mpc.__make_send_arg2('playlistsearch');
287 // plchanges {VERSION}
288 Mpc.prototype.plchanges           = Mpc.__make_send_arg1('plchanges');
289 // plchangesposid {VERSION}
290 Mpc.prototype.plchangesposid      = Mpc.__make_send_arg1('plchangesposid');
291 // prio {PRIORITY} {START:END...}
292 Mpc.prototype.prio                = Mpc.__make_send_arg2('prio');
293 // prioid {PRIORITY} {ID...}
294 Mpc.prototype.prioid              = Mpc.__make_send_arg2('prioid');
295 // shuffle [START:END]
296 Mpc.prototype.shuffle             = Mpc.__make_send_opt('shuffle');
297 // swap {SONG1} {SONG2}
298 Mpc.prototype.swap                = Mpc.__make_send_arg2('swap');
299 // swapid {SONG1} {SONG2}
300 Mpc.prototype.swapid              = Mpc.__make_send_arg2('swapid');
301
302 /*
303  * Stored playlists
304  * http://www.musicpd.org/doc/protocol/ch03s05.html
305  */
306
307 // listplaylist {NAME}
308 Mpc.prototype.listplaylist        = Mpc.__make_send_arg1('listplaylist');
309 // listplaylistinfo {NAME}
310 Mpc.prototype.listplaylistinfo    = Mpc.__make_send_arg1('listplaylistinfo');
311 // listplaylists
312 Mpc.prototype.listplaylists       = Mpc.__make_send_void('listplaylists');
313 // load {NAME} [START:END]
314 // TODO: handle optional start:end
315 Mpc.prototype.load                = Mpc.__make_send_arg1('load');
316 // playlistadd {NAME} {URI}
317 Mpc.prototype.playlistadd         = Mpc.__make_send_arg2('playlistadd');
318 // playlistclear {NAME}
319 Mpc.prototype.playlistclear       = Mpc.__make_send_arg1('playlistclear');
320 // playlistdelete {NAME} {SONGPOS}
321 Mpc.prototype.playlistdelete      = Mpc.__make_send_arg2('playlistdelete');
322 // playlistmove {NAME} {SONGID} {SONGPOS}
323 Mpc.prototype.playlistmove        = Mpc.__make_send_arg3('playlistmove');
324 // rename {NAME} {NEW_NAME}
325 Mpc.prototype.rename              = Mpc.__make_send_arg2('rename');
326 // rm {NAME}
327 Mpc.prototype.rm                  = Mpc.__make_send_arg1('rm');
328 // save {NAME}
329 Mpc.prototype.save                = Mpc.__make_send_arg1('save');
330
331 /*
332  * The music database
333  * http://www.musicpd.org/doc/protocol/ch03s06.html
334  */
335
336 /*
337  * Connection settings
338  * http://www.musicpd.org/doc/protocol/ch03s08.html
339  */
340
341 // close
342 Mpc.prototype.close               = Mpc.__make_send_void('close');
343 // kill
344 Mpc.prototype.kill                = Mpc.__make_send_void('kill');
345 // password {PASSWORD}
346 Mpc.prototype.password            = Mpc.__make_send_arg1('password');
347 // ping
348 Mpc.prototype.ping                = Mpc.__make_send_void('ping');
349
350 /*
351  * Audio output devices
352  * http://www.musicpd.org/doc/protocol/ch03s09.html
353  */
354
355 // disableoutput {ID}
356 Mpc.prototype.disableoutput       = Mpc.__make_send_arg1('disableoutput');
357 // enableoutput {ID}
358 Mpc.prototype.enableoutput        = Mpc.__make_send_arg1('enableoutput');
359 // outputs
360 Mpc.prototype.outputs             = Mpc.__make_send_void('outputs');
361
362 /*
363  * Reflection
364  * http://www.musicpd.org/doc/protocol/ch03s10.html
365  */
366
367 // config
368 Mpc.prototype.config              = Mpc.__make_send_void('config');
369 // commands
370 Mpc.prototype.commands            = Mpc.__make_send_void('commands');
371 // notcommands
372 Mpc.prototype.notcommands         = Mpc.__make_send_void('notcommands');
373 // tagtypes
374 Mpc.prototype.tagtypes            = Mpc.__make_send_void('tagtypes');
375 // urlhandlers
376 Mpc.prototype.urlhandlers         = Mpc.__make_send_void('urlhandlers');
377 // decoders
378 Mpc.prototype.decoders            = Mpc.__make_send_void('decoders');
379
380 /*
381  * Client to client
382  * http://www.musicpd.org/doc/protocol/ch03s11.html
383  */
384
385 // subscribe {NAME}
386 Mpc.prototype.subscribe           = Mpc.__make_send_arg1('subscribe');
387 // unsubscribe {NAME}
388 Mpc.prototype.unsubscribe         = Mpc.__make_send_arg1('unsubscribe');
389 // channels
390 Mpc.prototype.channels            = Mpc.__make_send_void('channels');
391 // readmessages
392 Mpc.prototype.readmessages        = Mpc.__make_send_void('readmessages');
393 // sendmessage {CHANNEL} {TEXT}
394 Mpc.prototype.sendmessage         = Mpc.__make_send_arg2('sendmessage');