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