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