]> git.wh0rd.org Git - chrome-ext/music-player-client.git/blob - js/mpc.js
images: update small tile to try & pass CWS guidelines
[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.state.Playlist.lastUpdate = (new Date()).getTime();
82                 this._cb_update_state(this.state);
83                 break;
84
85         case 'currentsong':
86                 this.state.Currentsong = this._parse_result(lines).state;
87                 this.state.Currentsong.lastUpdate = (new Date()).getTime();
88                 this._cb_update_state(this.state);
89                 break;
90
91         case 'stats':
92         case 'status':
93                 ret = this._parse_result(lines);
94                 state = ret.state;
95                 keys = ret.keys;
96
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);
101                 }
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];
107                 });
108
109                 this._cb_update_state(curr_state);
110                 break;
111
112         default:
113                 this._cb_update_state(lines, curr);
114                 break;
115         }
116 }
117
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'));
121         var i = 0;
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));
126                         i = 0;
127                 } else
128                         ++i;
129         }
130
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;
134                 this._socket.poll();
135         } else
136                 this._recv_buffer_last = lines.length;
137 }
138
139 /*
140  * Command generator helpers.
141  */
142
143 Mpc.__make_send_void = function(cmd) {
144         return function() { this.send(cmd); }
145 }
146
147 Mpc.__make_send_arg1 = function(cmd) {
148         return function(a1) {
149                 if (a1 === undefined)
150                         this.err(cmd + ': function requires one argument');
151                 else
152                         this.send(cmd + ' ' + a1);
153         }
154 }
155
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');
160                 else
161                         this.send(cmd + ' ' + a1 + ' ' + a2);
162         }
163 }
164
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');
169                 else
170                         this.send(cmd + ' ' + a1 + ' ' + a2 + ' ' + a3);
171         }
172 }
173
174 Mpc.__make_send_opt = function(cmd) {
175         return function(arg) {
176                 if (arg === undefined)
177                         arg = '';
178                 this.send(cmd + ' ' + arg);
179         };
180 }
181
182 Mpc.__make_send_range = function(cmd, min, max, def) {
183         return function(arg) {
184                 if (arg === undefined)
185                         arg = def;
186                 if (arg >= min && arg <= max)
187                         this.send(cmd + ' ' + arg);
188                 else
189                         this.err(cmd + ': arg must be [' + min + ',' + max + '] but got "' + arg + '"');
190         };
191 }
192
193 /*
194  * Querying MPD's status
195  * http://www.musicpd.org/doc/protocol/ch03.html#idp118752
196  */
197
198 // clearerror
199 Mpc.prototype.clearerror          = Mpc.__make_send_void('clearerror');
200 // currentsong
201 Mpc.prototype.currentsong         = Mpc.__make_send_void('currentsong');
202 // idle [SUBSYSTEMS...]
203 // TODO
204 // status
205 Mpc.prototype.status              = Mpc.__make_send_void('status');
206 // stats
207 Mpc.prototype.stats               = Mpc.__make_send_void('stats');
208
209 /*
210  * Playback options
211  * http://www.musicpd.org/doc/protocol/ch03s02.html
212  */
213
214 // consume {STATE}
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');
223 // random {STATE}
224 Mpc.prototype.random              = Mpc.__make_send_range('random', 0, 1, 1);
225 // repeat {STATE}
226 Mpc.prototype.repeat              = Mpc.__make_send_range('repeat', 0, 1, 1);
227 // setvol {VOL}
228 Mpc.prototype.setvol              = Mpc.__make_send_range('setvol', 0, 100);
229 // single {STATE}
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
234
235 /*
236  * Controlling playback
237  * http://www.musicpd.org/doc/protocol/ch03s03.html
238  */
239
240 // next
241 Mpc.prototype.next                = Mpc.__make_send_void('next');
242 // pause {PAUSE}
243 Mpc.prototype.pause               = Mpc.__make_send_range('pause', 0, 1, 1);
244 // play [SONGPOS]
245 Mpc.prototype.play                = Mpc.__make_send_opt('play');
246 // playid [SONGID]
247 Mpc.prototype.playid              = Mpc.__make_send_opt('playid');
248 // previous
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');
254 // seekcur {TIME}
255 Mpc.prototype.seekcur             = Mpc.__make_send_arg1('seekcur');
256 // stop
257 Mpc.prototype.stop                = Mpc.__make_send_void('stop');
258
259 /*
260  * The current playlist
261  * http://www.musicpd.org/doc/protocol/ch03s04.html
262  */
263
264 // add {URI}
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');
269 // clear
270 Mpc.prototype.clear               = Mpc.__make_send_void('clear');
271 // delete [{POS} | {START:END}]
272 Mpc.prototype.delete              = Mpc.__make_send_arg1('delete');
273 // deleteid {SONGID}
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');
279 // playlist
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');
303
304 /*
305  * Stored playlists
306  * http://www.musicpd.org/doc/protocol/ch03s05.html
307  */
308
309 // listplaylist {NAME}
310 Mpc.prototype.listplaylist        = Mpc.__make_send_arg1('listplaylist');
311 // listplaylistinfo {NAME}
312 Mpc.prototype.listplaylistinfo    = Mpc.__make_send_arg1('listplaylistinfo');
313 // listplaylists
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');
328 // rm {NAME}
329 Mpc.prototype.rm                  = Mpc.__make_send_arg1('rm');
330 // save {NAME}
331 Mpc.prototype.save                = Mpc.__make_send_arg1('save');
332
333 /*
334  * The music database
335  * http://www.musicpd.org/doc/protocol/ch03s06.html
336  */
337
338 /*
339  * Connection settings
340  * http://www.musicpd.org/doc/protocol/ch03s08.html
341  */
342
343 // close
344 Mpc.prototype.close               = Mpc.__make_send_void('close');
345 // kill
346 Mpc.prototype.kill                = Mpc.__make_send_void('kill');
347 // password {PASSWORD}
348 Mpc.prototype.password            = Mpc.__make_send_arg1('password');
349 // ping
350 Mpc.prototype.ping                = Mpc.__make_send_void('ping');
351
352 /*
353  * Audio output devices
354  * http://www.musicpd.org/doc/protocol/ch03s09.html
355  */
356
357 // disableoutput {ID}
358 Mpc.prototype.disableoutput       = Mpc.__make_send_arg1('disableoutput');
359 // enableoutput {ID}
360 Mpc.prototype.enableoutput        = Mpc.__make_send_arg1('enableoutput');
361 // outputs
362 Mpc.prototype.outputs             = Mpc.__make_send_void('outputs');
363
364 /*
365  * Reflection
366  * http://www.musicpd.org/doc/protocol/ch03s10.html
367  */
368
369 // config
370 Mpc.prototype.config              = Mpc.__make_send_void('config');
371 // commands
372 Mpc.prototype.commands            = Mpc.__make_send_void('commands');
373 // notcommands
374 Mpc.prototype.notcommands         = Mpc.__make_send_void('notcommands');
375 // tagtypes
376 Mpc.prototype.tagtypes            = Mpc.__make_send_void('tagtypes');
377 // urlhandlers
378 Mpc.prototype.urlhandlers         = Mpc.__make_send_void('urlhandlers');
379 // decoders
380 Mpc.prototype.decoders            = Mpc.__make_send_void('decoders');
381
382 /*
383  * Client to client
384  * http://www.musicpd.org/doc/protocol/ch03s11.html
385  */
386
387 // subscribe {NAME}
388 Mpc.prototype.subscribe           = Mpc.__make_send_arg1('subscribe');
389 // unsubscribe {NAME}
390 Mpc.prototype.unsubscribe         = Mpc.__make_send_arg1('unsubscribe');
391 // channels
392 Mpc.prototype.channels            = Mpc.__make_send_void('channels');
393 // readmessages
394 Mpc.prototype.readmessages        = Mpc.__make_send_void('readmessages');
395 // sendmessage {CHANNEL} {TEXT}
396 Mpc.prototype.sendmessage         = Mpc.__make_send_arg2('sendmessage');