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