mpc: handle ACK responses
[chrome-ext/music-player-client.git] / js / mpc.js
CommitLineData
50dafc4c
MF
1// Written by Mike Frysinger <vapier@gmail.com>. Released into the public domain. Suck it.
2
4d3d4e03 3function Mpc(socket, cb_update_state, debug_enabled) {
50dafc4c
MF
4 this._socket = socket;
5 this._cb_update_state = cb_update_state;
4d3d4e03 6 this._debug_enabled = debug_enabled;
50dafc4c 7 this._queue = ['init'];
1ae37b16
MF
8 this._recv_buffer = [];
9 this._recv_buffer_last = 0;
50dafc4c
MF
10 this.state = {};
11}
12
e4657cab
MF
13Mpc.prototype.log = function(lvl, msg, obj) {
14 if (this._debug_enabled & lvl)
4d3d4e03
MF
15 console.log('mpc: ' + msg, obj);
16}
17
e4657cab
MF
18Mpc.prototype.err = function(msg, obj) {
19 console.error('mpc: ' + msg, obj);
20}
21
4d3d4e03
MF
22Mpc.prototype.set_debug = function(val) {
23 this._debug_enabled = val;
50dafc4c
MF
24}
25
26Mpc.prototype.send = function(msg) {
4d3d4e03 27 var _this = this;
50dafc4c
MF
28 this._queue.push(msg);
29 this._socket.send(msg, function(x) {
e4657cab 30 _this.log(0x1, 'send: ' + msg + ':', x);
50dafc4c
MF
31 });
32}
33
34Mpc.prototype.recv_msg = function(lines) {
35 curr = this._queue.shift();
e4657cab 36 this.log(0x2, 'recv: [' + curr + ']:', lines.join('\n'));
39034774
MF
37 if (lines[0].substr(0, 4) == 'ACK ')
38 this.err(curr, lines.join('\n'));
50dafc4c
MF
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 = {};
f5b43479 48 keys = [];
50dafc4c
MF
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);
f5b43479 54 keys.push(key);
50dafc4c
MF
55 val = line.substr(i + 2);
56 state[key] = val;
57 });
f5b43479
MF
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);
50dafc4c
MF
72 break;
73 default:
74 this._cb_update_state(lines, curr);
75 break;
76 }
77}
78
79Mpc.prototype.recv = function(msg) {
80 /* We can get back a bunch of responses in a row, so parse them out */
1ae37b16 81 var lines = this._recv_buffer = this._recv_buffer.concat(msg.split('\n'));
50dafc4c
MF
82 var i = 0;
83 while (i < lines.length) {
39034774
MF
84 if (lines[i] == 'OK' || lines[i].substr(0, 3) == 'OK ' ||
85 lines[i].substr(0, 4) == 'ACK ') {
50dafc4c
MF
86 this.recv_msg(lines.splice(0, i + 1));
87 i = 0;
88 } else
89 ++i;
90 }
1ae37b16
MF
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;
50dafc4c
MF
98}
99
100/*
101 * Command generator helpers.
102 */
103
104Mpc.__make_send_void = function(cmd) {
105 return function() { this.send(cmd); }
106}
107
108Mpc.__make_send_arg1 = function(cmd) {
109 return function(a1) {
110 if (a1 === undefined)
e4657cab 111 this.err(cmd + ': function requires one argument');
50dafc4c
MF
112 else
113 this.send(cmd + ' ' + a1);
114 }
115}
116
117Mpc.__make_send_arg2 = function(cmd) {
118 return function(a1, a2) {
119 if (a1 === undefined || a2 === undefined)
e4657cab 120 this.err(cmd + ': function requires two arguments');
50dafc4c
MF
121 else
122 this.send(cmd + ' ' + a1 + ' ' + a2);
123 }
124}
125
60d8b85a
MF
126Mpc.__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
50dafc4c
MF
135Mpc.__make_send_opt = function(cmd) {
136 return function(arg) {
137 if (arg === undefined)
138 arg = '';
139 this.send(cmd + ' ' + arg);
140 };
141}
142
143Mpc.__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
e4657cab 150 this.err(cmd + ': arg must be [' + min + ',' + max + '] but got "' + arg + '"');
50dafc4c
MF
151 };
152}
153
154/*
155 * Querying MPD's status
156 * http://www.musicpd.org/doc/protocol/ch03.html#idp118752
157 */
158
159// clearerror
160Mpc.prototype.clearerror = Mpc.__make_send_void('clearerror');
161// currentsong
162Mpc.prototype.currentsong = Mpc.__make_send_void('currentsong');
163// idle [SUBSYSTEMS...]
164// TODO
165// status
166Mpc.prototype.status = Mpc.__make_send_void('status');
167// stats
168Mpc.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}
176Mpc.prototype.consume = Mpc.__make_send_range('consume', 0, 1, 1);
177// crossfade {SECONDS}
f5ed7157 178Mpc.prototype.crossfade = Mpc.__make_send_arg1('crossfade');
50dafc4c
MF
179// mixrampdb {deciBels}
180Mpc.prototype.mixrampdb = Mpc.__make_send_arg1('mixrampdb');
181// mixrampdelay {SECONDS|nan}
182// Note: Probably should handle javascript NaN here.
183Mpc.prototype.mixrampdelay = Mpc.__make_send_arg1('mixrampdelay');
184// random {STATE}
185Mpc.prototype.random = Mpc.__make_send_range('random', 0, 1, 1);
186// repeat {STATE}
187Mpc.prototype.repeat = Mpc.__make_send_range('repeat', 0, 1, 1);
188// setvol {VOL}
189Mpc.prototype.setvol = Mpc.__make_send_range('setvol', 0, 100);
190// single {STATE}
191Mpc.prototype.single = Mpc.__make_send_range('single', 0, 1, 1);
192// replay_gain_mode {MODE}
193Mpc.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
202Mpc.prototype.next = Mpc.__make_send_void('next');
203// pause {PAUSE}
204Mpc.prototype.pause = Mpc.__make_send_range('pause', 0, 1, 1);
205// play [SONGPOS]
206Mpc.prototype.play = Mpc.__make_send_opt('play');
207// playid [SONGID]
208Mpc.prototype.playid = Mpc.__make_send_opt('playid');
209// previous
210Mpc.prototype.previous = Mpc.__make_send_void('previous');
211// seek {SONGPOS} {TIME}
212Mpc.prototype.seek = Mpc.__make_send_arg2('seek');
213// seekid {SONGID} {TIME}
214Mpc.prototype.seekid = Mpc.__make_send_arg2('seekid');
215// seekcur {TIME}
216Mpc.prototype.seekcur = Mpc.__make_send_arg1('seek');
217// stop
218Mpc.prototype.stop = Mpc.__make_send_void('stop');
219
60d8b85a
MF
220/*
221 * The current playlist
222 * http://www.musicpd.org/doc/protocol/ch03s04.html
223 */
224
225// add {URI}
226Mpc.prototype.add = Mpc.__make_send_arg1('add');
227// addid {URI} [POSITION]
228// TODO: handle position
229Mpc.prototype.addid = Mpc.__make_send_arg1('addid');
230// clear
231Mpc.prototype.clear = Mpc.__make_send_void('clear');
232// delete [{POS} | {START:END}]
233Mpc.prototype.delete = Mpc.__make_send_arg1('delete');
234// deleteid {SONGID}
235Mpc.prototype.deleteid = Mpc.__make_send_arg1('deleteid');
236// move [{FROM} | {START:END}] {TO}
237Mpc.prototype.move = Mpc.__make_send_arg2('move');
238// moveid {FROM} {TO}
239Mpc.prototype.moveid = Mpc.__make_send_arg2('moveid');
240// playlist
241Mpc.prototype.playlist = Mpc.__make_send_void('playlist');
242// playlistfind {TAG} {NEEDLE}
243Mpc.prototype.playlistfind = Mpc.__make_send_arg2('playlistfind');
244// playlistid {SONGID}
245Mpc.prototype.playlistid = Mpc.__make_send_arg1('playlistid');
246// playlistinfo [[SONGPOS] | [START:END]]
247Mpc.prototype.playlistinfo = Mpc.__make_send_opt('playlistinfo');
248// playlistsearch {TAG} {NEEDLE}
249Mpc.prototype.playlistsearch = Mpc.__make_send_arg2('playlistsearch');
250// plchanges {VERSION}
251Mpc.prototype.plchanges = Mpc.__make_send_arg1('plchanges');
252// plchangesposid {VERSION}
253Mpc.prototype.plchangesposid = Mpc.__make_send_arg1('plchangesposid');
254// prio {PRIORITY} {START:END...}
255Mpc.prototype.prio = Mpc.__make_send_arg2('prio');
256// prioid {PRIORITY} {ID...}
257Mpc.prototype.prioid = Mpc.__make_send_arg2('prioid');
258// shuffle [START:END]
259Mpc.prototype.shuffle = Mpc.__make_send_opt('shuffle');
260// swap {SONG1} {SONG2}
261Mpc.prototype.swap = Mpc.__make_send_arg2('swap');
262// swapid {SONG1} {SONG2}
263Mpc.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}
271Mpc.prototype.listplaylist = Mpc.__make_send_arg1('listplaylist');
272// listplaylistinfo {NAME}
273Mpc.prototype.listplaylistinfo = Mpc.__make_send_arg1('listplaylistinfo');
274// listplaylists
275Mpc.prototype.listplaylists = Mpc.__make_send_void('listplaylists');
276// load {NAME} [START:END]
277// TODO: handle optional start:end
278Mpc.prototype.load = Mpc.__make_send_arg1('load');
279// playlistadd {NAME} {URI}
280Mpc.prototype.playlistadd = Mpc.__make_send_arg2('playlistadd');
281// playlistclear {NAME}
282Mpc.prototype.playlistclear = Mpc.__make_send_arg1('playlistclear');
283// playlistdelete {NAME} {SONGPOS}
284Mpc.prototype.playlistdelete = Mpc.__make_send_arg2('playlistdelete');
285// playlistmove {NAME} {SONGID} {SONGPOS}
286Mpc.prototype.playlistmove = Mpc.__make_send_arg3('playlistmove');
287// rename {NAME} {NEW_NAME}
288Mpc.prototype.rename = Mpc.__make_send_arg2('rename');
289// rm {NAME}
290Mpc.prototype.rm = Mpc.__make_send_arg1('rm');
291// save {NAME}
292Mpc.prototype.save = Mpc.__make_send_arg1('save');
293
294/*
295 * The music database
296 * http://www.musicpd.org/doc/protocol/ch03s06.html
297 */
298
50dafc4c
MF
299/*
300 * Connection settings
301 * http://www.musicpd.org/doc/protocol/ch03s08.html
302 */
303
304// close
305Mpc.prototype.close = Mpc.__make_send_void('close');
306// kill
307Mpc.prototype.kill = Mpc.__make_send_void('kill');
308// password {PASSWORD}
309Mpc.prototype.password = Mpc.__make_send_arg1('password');
310// ping
311Mpc.prototype.ping = Mpc.__make_send_void('ping');
024ea284
MF
312
313/*
314 * Audio output devices
315 * http://www.musicpd.org/doc/protocol/ch03s09.html
316 */
317
318// disableoutput {ID}
319Mpc.prototype.disableoutput = Mpc.__make_send_arg1('disableoutput');
320// enableoutput {ID}
321Mpc.prototype.enableoutput = Mpc.__make_send_arg1('enableoutput');
322// outputs
323Mpc.prototype.outputs = Mpc.__make_send_void('outputs');
324
325/*
326 * Reflection
327 * http://www.musicpd.org/doc/protocol/ch03s10.html
328 */
329
330// config
331Mpc.prototype.config = Mpc.__make_send_void('config');
332// commands
333Mpc.prototype.commands = Mpc.__make_send_void('commands');
334// notcommands
335Mpc.prototype.notcommands = Mpc.__make_send_void('notcommands');
336// tagtypes
337Mpc.prototype.tagtypes = Mpc.__make_send_void('tagtypes');
338// urlhandlers
339Mpc.prototype.urlhandlers = Mpc.__make_send_void('urlhandlers');
340// decoders
341Mpc.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}
349Mpc.prototype.subscribe = Mpc.__make_send_arg1('subscribe');
350// unsubscribe {NAME}
351Mpc.prototype.unsubscribe = Mpc.__make_send_arg1('unsubscribe');
352// channels
353Mpc.prototype.channels = Mpc.__make_send_void('channels');
354// readmessages
355Mpc.prototype.readmessages = Mpc.__make_send_void('readmessages');
356// sendmessage {CHANNEL} {TEXT}
357Mpc.prototype.sendmessage = Mpc.__make_send_arg2('sendmessage');