]> git.wh0rd.org Git - chrome-ext/music-player-client.git/blob - js/mpc.js
37db8cdcc391388c1fc2e0fe9bb0fe4399f38abb
[chrome-ext/music-player-client.git] / js / mpc.js
1 // Written by Mike Frysinger <vapier@gmail.com>.  Released into the public domain.  Suck it.
2
3 function Mpc(socket, cb_update_state, debug_enabled) {
4         this._socket = socket;
5         this._cb_update_state = cb_update_state;
6         this._debug_enabled = debug_enabled;
7         this._queue = ['init'];
8         this.state = {};
9 }
10
11 Mpc.prototype.log = function(lvl, msg, obj) {
12         if (this._debug_enabled & lvl)
13                 console.log('mpc: ' + msg, obj);
14 }
15
16 Mpc.prototype.err = function(msg, obj) {
17         console.error('mpc: ' + msg, obj);
18 }
19
20 Mpc.prototype.set_debug = function(val) {
21         this._debug_enabled = val;
22 }
23
24 Mpc.prototype.send = function(msg) {
25         var _this = this;
26         this._queue.push(msg);
27         this._socket.send(msg, function(x) {
28                 _this.log(0x1, 'send: ' + msg + ':', x);
29         });
30 }
31
32 Mpc.prototype.recv_msg = function(lines) {
33         curr = this._queue.shift();
34         this.log(0x2, 'recv: [' + curr + ']:', lines.join('\n'));
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 = {};
44                 keys = [];
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                 // 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);
68                 break;
69         default:
70                 this._cb_update_state(lines, curr);
71                 break;
72         }
73 }
74
75 Mpc.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
93 Mpc.__make_send_void = function(cmd) {
94         return function() { this.send(cmd); }
95 }
96
97 Mpc.__make_send_arg1 = function(cmd) {
98         return function(a1) {
99                 if (a1 === undefined)
100                         this.err(cmd + ': function requires one argument');
101                 else
102                         this.send(cmd + ' ' + a1);
103         }
104 }
105
106 Mpc.__make_send_arg2 = function(cmd) {
107         return function(a1, a2) {
108                 if (a1 === undefined || a2 === undefined)
109                         this.err(cmd + ': function requires two arguments');
110                 else
111                         this.send(cmd + ' ' + a1 + ' ' + a2);
112         }
113 }
114
115 Mpc.__make_send_opt = function(cmd) {
116         return function(arg) {
117                 if (arg === undefined)
118                         arg = '';
119                 this.send(cmd + ' ' + arg);
120         };
121 }
122
123 Mpc.__make_send_range = function(cmd, min, max, def) {
124         return function(arg) {
125                 if (arg === undefined)
126                         arg = def;
127                 if (arg >= min && arg <= max)
128                         this.send(cmd + ' ' + arg);
129                 else
130                         this.err(cmd + ': arg must be [' + min + ',' + max + '] but got "' + arg + '"');
131         };
132 }
133
134 /*
135  * Querying MPD's status
136  * http://www.musicpd.org/doc/protocol/ch03.html#idp118752
137  */
138
139 // clearerror
140 Mpc.prototype.clearerror          = Mpc.__make_send_void('clearerror');
141 // currentsong
142 Mpc.prototype.currentsong         = Mpc.__make_send_void('currentsong');
143 // idle [SUBSYSTEMS...]
144 // TODO
145 // status
146 Mpc.prototype.status              = Mpc.__make_send_void('status');
147 // stats
148 Mpc.prototype.stats               = Mpc.__make_send_void('stats');
149
150 /*
151  * Playback options
152  * http://www.musicpd.org/doc/protocol/ch03s02.html
153  */
154
155 // consume {STATE}
156 Mpc.prototype.consume             = Mpc.__make_send_range('consume', 0, 1, 1);
157 // crossfade {SECONDS}
158 Mpc.prototype.crossfade           = Mpc.__make_send_arg1('crossfade');
159 // mixrampdb {deciBels}
160 Mpc.prototype.mixrampdb           = Mpc.__make_send_arg1('mixrampdb');
161 // mixrampdelay {SECONDS|nan}
162 // Note: Probably should handle javascript NaN here.
163 Mpc.prototype.mixrampdelay        = Mpc.__make_send_arg1('mixrampdelay');
164 // random {STATE}
165 Mpc.prototype.random              = Mpc.__make_send_range('random', 0, 1, 1);
166 // repeat {STATE}
167 Mpc.prototype.repeat              = Mpc.__make_send_range('repeat', 0, 1, 1);
168 // setvol {VOL}
169 Mpc.prototype.setvol              = Mpc.__make_send_range('setvol', 0, 100);
170 // single {STATE}
171 Mpc.prototype.single              = Mpc.__make_send_range('single', 0, 1, 1);
172 // replay_gain_mode {MODE}
173 Mpc.prototype.replay_gain_mode    = Mpc.__make_send_arg1('replay_gain_mode');
174 // replay_gain_status
175
176 /*
177  * Controlling playback
178  * http://www.musicpd.org/doc/protocol/ch03s03.html
179  */
180
181 // next
182 Mpc.prototype.next                = Mpc.__make_send_void('next');
183 // pause {PAUSE}
184 Mpc.prototype.pause               = Mpc.__make_send_range('pause', 0, 1, 1);
185 // play [SONGPOS]
186 Mpc.prototype.play                = Mpc.__make_send_opt('play');
187 // playid [SONGID]
188 Mpc.prototype.playid              = Mpc.__make_send_opt('playid');
189 // previous
190 Mpc.prototype.previous            = Mpc.__make_send_void('previous');
191 // seek {SONGPOS} {TIME}
192 Mpc.prototype.seek                = Mpc.__make_send_arg2('seek');
193 // seekid {SONGID} {TIME}
194 Mpc.prototype.seekid              = Mpc.__make_send_arg2('seekid');
195 // seekcur {TIME}
196 Mpc.prototype.seekcur             = Mpc.__make_send_arg1('seek');
197 // stop
198 Mpc.prototype.stop                = Mpc.__make_send_void('stop');
199
200 /*
201  * Connection settings
202  * http://www.musicpd.org/doc/protocol/ch03s08.html
203  */
204
205 // close
206 Mpc.prototype.close               = Mpc.__make_send_void('close');
207 // kill
208 Mpc.prototype.kill                = Mpc.__make_send_void('kill');
209 // password {PASSWORD}
210 Mpc.prototype.password            = Mpc.__make_send_arg1('password');
211 // ping
212 Mpc.prototype.ping                = Mpc.__make_send_void('ping');
213
214 /*
215  * Audio output devices
216  * http://www.musicpd.org/doc/protocol/ch03s09.html
217  */
218
219 // disableoutput {ID}
220 Mpc.prototype.disableoutput       = Mpc.__make_send_arg1('disableoutput');
221 // enableoutput {ID}
222 Mpc.prototype.enableoutput        = Mpc.__make_send_arg1('enableoutput');
223 // outputs
224 Mpc.prototype.outputs             = Mpc.__make_send_void('outputs');
225
226 /*
227  * Reflection
228  * http://www.musicpd.org/doc/protocol/ch03s10.html
229  */
230
231 // config
232 Mpc.prototype.config              = Mpc.__make_send_void('config');
233 // commands
234 Mpc.prototype.commands            = Mpc.__make_send_void('commands');
235 // notcommands
236 Mpc.prototype.notcommands         = Mpc.__make_send_void('notcommands');
237 // tagtypes
238 Mpc.prototype.tagtypes            = Mpc.__make_send_void('tagtypes');
239 // urlhandlers
240 Mpc.prototype.urlhandlers         = Mpc.__make_send_void('urlhandlers');
241 // decoders
242 Mpc.prototype.decoders            = Mpc.__make_send_void('decoders');
243
244 /*
245  * Client to client
246  * http://www.musicpd.org/doc/protocol/ch03s11.html
247  */
248
249 // subscribe {NAME}
250 Mpc.prototype.subscribe           = Mpc.__make_send_arg1('subscribe');
251 // unsubscribe {NAME}
252 Mpc.prototype.unsubscribe         = Mpc.__make_send_arg1('unsubscribe');
253 // channels
254 Mpc.prototype.channels            = Mpc.__make_send_void('channels');
255 // readmessages
256 Mpc.prototype.readmessages        = Mpc.__make_send_void('readmessages');
257 // sendmessage {CHANNEL} {TEXT}
258 Mpc.prototype.sendmessage         = Mpc.__make_send_arg2('sendmessage');