auto-reconnect when we get an error
[chrome-ext/music-player-client.git] / js / mpc.js
... / ...
CommitLineData
1// Written by Mike Frysinger <vapier@gmail.com>. Released into the public domain. Suck it.
2
3function 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
13Mpc.prototype.log = function(lvl, msg, obj) {
14 if (this._debug_enabled & lvl)
15 console.log('mpc: ' + msg, obj);
16}
17
18Mpc.prototype.err = function(msg, obj) {
19 console.error('mpc: ' + msg, obj);
20}
21
22Mpc.prototype.set_debug = function(val) {
23 this._debug_enabled = val;
24}
25
26Mpc.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
40Mpc.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
61Mpc.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
116Mpc.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
141Mpc.__make_send_void = function(cmd) {
142 return function() { this.send(cmd); }
143}
144
145Mpc.__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
154Mpc.__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
163Mpc.__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
172Mpc.__make_send_opt = function(cmd) {
173 return function(arg) {
174 if (arg === undefined)
175 arg = '';
176 this.send(cmd + ' ' + arg);
177 };
178}
179
180Mpc.__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
197Mpc.prototype.clearerror = Mpc.__make_send_void('clearerror');
198// currentsong
199Mpc.prototype.currentsong = Mpc.__make_send_void('currentsong');
200// idle [SUBSYSTEMS...]
201// TODO
202// status
203Mpc.prototype.status = Mpc.__make_send_void('status');
204// stats
205Mpc.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}
213Mpc.prototype.consume = Mpc.__make_send_range('consume', 0, 1, 1);
214// crossfade {SECONDS}
215Mpc.prototype.crossfade = Mpc.__make_send_arg1('crossfade');
216// mixrampdb {deciBels}
217Mpc.prototype.mixrampdb = Mpc.__make_send_arg1('mixrampdb');
218// mixrampdelay {SECONDS|nan}
219// Note: Probably should handle javascript NaN here.
220Mpc.prototype.mixrampdelay = Mpc.__make_send_arg1('mixrampdelay');
221// random {STATE}
222Mpc.prototype.random = Mpc.__make_send_range('random', 0, 1, 1);
223// repeat {STATE}
224Mpc.prototype.repeat = Mpc.__make_send_range('repeat', 0, 1, 1);
225// setvol {VOL}
226Mpc.prototype.setvol = Mpc.__make_send_range('setvol', 0, 100);
227// single {STATE}
228Mpc.prototype.single = Mpc.__make_send_range('single', 0, 1, 1);
229// replay_gain_mode {MODE}
230Mpc.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
239Mpc.prototype.next = Mpc.__make_send_void('next');
240// pause {PAUSE}
241Mpc.prototype.pause = Mpc.__make_send_range('pause', 0, 1, 1);
242// play [SONGPOS]
243Mpc.prototype.play = Mpc.__make_send_opt('play');
244// playid [SONGID]
245Mpc.prototype.playid = Mpc.__make_send_opt('playid');
246// previous
247Mpc.prototype.previous = Mpc.__make_send_void('previous');
248// seek {SONGPOS} {TIME}
249Mpc.prototype.seek = Mpc.__make_send_arg2('seek');
250// seekid {SONGID} {TIME}
251Mpc.prototype.seekid = Mpc.__make_send_arg2('seekid');
252// seekcur {TIME}
253Mpc.prototype.seekcur = Mpc.__make_send_arg1('seekcur');
254// stop
255Mpc.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}
263Mpc.prototype.add = Mpc.__make_send_arg1('add');
264// addid {URI} [POSITION]
265// TODO: handle position
266Mpc.prototype.addid = Mpc.__make_send_arg1('addid');
267// clear
268Mpc.prototype.clear = Mpc.__make_send_void('clear');
269// delete [{POS} | {START:END}]
270Mpc.prototype.delete = Mpc.__make_send_arg1('delete');
271// deleteid {SONGID}
272Mpc.prototype.deleteid = Mpc.__make_send_arg1('deleteid');
273// move [{FROM} | {START:END}] {TO}
274Mpc.prototype.move = Mpc.__make_send_arg2('move');
275// moveid {FROM} {TO}
276Mpc.prototype.moveid = Mpc.__make_send_arg2('moveid');
277// playlist
278Mpc.prototype.playlist = Mpc.__make_send_void('playlist');
279// playlistfind {TAG} {NEEDLE}
280Mpc.prototype.playlistfind = Mpc.__make_send_arg2('playlistfind');
281// playlistid {SONGID}
282Mpc.prototype.playlistid = Mpc.__make_send_arg1('playlistid');
283// playlistinfo [[SONGPOS] | [START:END]]
284Mpc.prototype.playlistinfo = Mpc.__make_send_opt('playlistinfo');
285// playlistsearch {TAG} {NEEDLE}
286Mpc.prototype.playlistsearch = Mpc.__make_send_arg2('playlistsearch');
287// plchanges {VERSION}
288Mpc.prototype.plchanges = Mpc.__make_send_arg1('plchanges');
289// plchangesposid {VERSION}
290Mpc.prototype.plchangesposid = Mpc.__make_send_arg1('plchangesposid');
291// prio {PRIORITY} {START:END...}
292Mpc.prototype.prio = Mpc.__make_send_arg2('prio');
293// prioid {PRIORITY} {ID...}
294Mpc.prototype.prioid = Mpc.__make_send_arg2('prioid');
295// shuffle [START:END]
296Mpc.prototype.shuffle = Mpc.__make_send_opt('shuffle');
297// swap {SONG1} {SONG2}
298Mpc.prototype.swap = Mpc.__make_send_arg2('swap');
299// swapid {SONG1} {SONG2}
300Mpc.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}
308Mpc.prototype.listplaylist = Mpc.__make_send_arg1('listplaylist');
309// listplaylistinfo {NAME}
310Mpc.prototype.listplaylistinfo = Mpc.__make_send_arg1('listplaylistinfo');
311// listplaylists
312Mpc.prototype.listplaylists = Mpc.__make_send_void('listplaylists');
313// load {NAME} [START:END]
314// TODO: handle optional start:end
315Mpc.prototype.load = Mpc.__make_send_arg1('load');
316// playlistadd {NAME} {URI}
317Mpc.prototype.playlistadd = Mpc.__make_send_arg2('playlistadd');
318// playlistclear {NAME}
319Mpc.prototype.playlistclear = Mpc.__make_send_arg1('playlistclear');
320// playlistdelete {NAME} {SONGPOS}
321Mpc.prototype.playlistdelete = Mpc.__make_send_arg2('playlistdelete');
322// playlistmove {NAME} {SONGID} {SONGPOS}
323Mpc.prototype.playlistmove = Mpc.__make_send_arg3('playlistmove');
324// rename {NAME} {NEW_NAME}
325Mpc.prototype.rename = Mpc.__make_send_arg2('rename');
326// rm {NAME}
327Mpc.prototype.rm = Mpc.__make_send_arg1('rm');
328// save {NAME}
329Mpc.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
342Mpc.prototype.close = Mpc.__make_send_void('close');
343// kill
344Mpc.prototype.kill = Mpc.__make_send_void('kill');
345// password {PASSWORD}
346Mpc.prototype.password = Mpc.__make_send_arg1('password');
347// ping
348Mpc.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}
356Mpc.prototype.disableoutput = Mpc.__make_send_arg1('disableoutput');
357// enableoutput {ID}
358Mpc.prototype.enableoutput = Mpc.__make_send_arg1('enableoutput');
359// outputs
360Mpc.prototype.outputs = Mpc.__make_send_void('outputs');
361
362/*
363 * Reflection
364 * http://www.musicpd.org/doc/protocol/ch03s10.html
365 */
366
367// config
368Mpc.prototype.config = Mpc.__make_send_void('config');
369// commands
370Mpc.prototype.commands = Mpc.__make_send_void('commands');
371// notcommands
372Mpc.prototype.notcommands = Mpc.__make_send_void('notcommands');
373// tagtypes
374Mpc.prototype.tagtypes = Mpc.__make_send_void('tagtypes');
375// urlhandlers
376Mpc.prototype.urlhandlers = Mpc.__make_send_void('urlhandlers');
377// decoders
378Mpc.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}
386Mpc.prototype.subscribe = Mpc.__make_send_arg1('subscribe');
387// unsubscribe {NAME}
388Mpc.prototype.unsubscribe = Mpc.__make_send_arg1('unsubscribe');
389// channels
390Mpc.prototype.channels = Mpc.__make_send_void('channels');
391// readmessages
392Mpc.prototype.readmessages = Mpc.__make_send_void('readmessages');
393// sendmessage {CHANNEL} {TEXT}
394Mpc.prototype.sendmessage = Mpc.__make_send_arg2('sendmessage');