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