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