2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4 * Music Player Daemon API
8 * LICENSE: This source file is subject to version 3.01 of the PHP license
9 * that is available thorugh the world-wide-web at the following URI:
10 * http://www.php.net/license/3_01.txt. If you did not receive a copy of
11 * the PHP License and are unable to obtain it through the web, please
12 * send a note to license@php.net so we can mail you a copy immediately.
14 * @category Networking
16 * @author Graham Christensen <graham.christensen@itrebal.com>
17 * @copyright 2006 Graham Christensen
18 * @license http://www.php.net/license/3_01.txt
23 * API for the common peices of Music Player Daemon commands
25 * Used for basic interaction and output handeling, as well as
26 * several standard commands.
28 * @category Networking
30 * @author Graham Christensen <graham.christensen@itrebal.com>
31 * @copyright 2006 Graham Christensen
32 * @license http://www.php.net/license/3_01.txt
37 //Connection & Write Errors
38 const CONNECTION_NOT_OPENED = 100;
39 const CONNECTION_FAILED = 102;
40 const WRITE_FAILED = 103;
43 const ACK_NOT_LIST = 1;
45 const ACK_PASSWORD = 3;
46 const ACK_PERMISSION = 4;
48 const ACK_NO_EXIST = 50;
49 const ACK_PLAYLIST_MAX = 51;
50 const ACK_SYSTEM = 52;
51 const ACK_PLAYLIST_LOAD = 53;
52 const ACK_UPDATE_ALREADY = 54;
53 const ACK_PLAYER_SYNC = 55;
55 const ACK_COMMAND_FAILED = -100;
58 const RESPONSE_OK = 'OK';
60 private $_connection = null;
61 protected $_errors = array();
62 private $_current_error = array();
63 protected $_commands = array();
64 protected $_output = array();
65 private $connection_params = array();
70 * Set connection params
72 * @param $host host to connect to, (default: localhost)
73 * @param $port port to connec through, (default: 6600)
74 * @param $password password to send, (default: null)
77 function __construct($host = 'localhost', $port = 6600, $password = null)
79 $this->connection_params['host'] = $host;
80 $this->connection_params['port'] = $port;
81 $this->connection_params['password'] = $password;
91 public function connect()
93 if ($this->isConnected()) {
96 $connection = @fsockopen($this->connection_params['host'], $this->connection_params['port'], $errn, $errs, 4);
98 $this->_connection = $connection;
99 // Read from the source until its ready for commands
101 while (!feof($this->_connection)) {
102 $line = fgets($this->_connection);
103 if (trim(substr($line, 0, 2)) == self::RESPONSE_OK) {
107 if (!is_null($this->connection_params['password'])) {
108 if (!$this->runCommand('password', $this->connection_params['password'])) {
109 throw new PEAR_Exception('Password invalid.', self::ACK_PASSWORD);
114 throw new PEAR_Exception('Error connecting: '.$errn.' ; '.$errs, self::CONNECTION_FAILED);
120 * Check connection status
124 public function isConnected()
126 if (!is_resource($this->_connection)) {
135 * Disconnect from MPD
139 public function disconnect()
141 $this->runCommand('close');
142 fclose($this->_connection);
143 $this->_connection = null;
150 * Write data to the socket
152 * @param $command string data to be sent
157 function write($data)
160 if (!$this->isConnected()) {
165 if (!fwrite($this->_connection, $data."\r\n")) {
166 throw new PEAR_Exception('Write failed', self::WRITE_FAILED);
168 $this->_commands[] = $data;
175 * Read data from the socket
177 * @return array of raw output
183 if (!$this->isConnected()) {
184 throw new PEAR_Exception('Not connected', self::CONNECTION_NOT_OPENED);
186 //Loop through the connection, putting the data into $line
188 while (!feof($this->_connection)) {
189 $line = fgets($this->_connection);
190 if (preg_match('/^ACK \[(.*?)\@(.*?)\] \{(.*?)\} (.*?)$/', $line, $matches)) {
191 //If the output is an ACK error
192 //$this->runCommand('clearerror'); //Cleanup the error
193 $this->_errors[] = $matches;
194 $this->_current_error = array('ack' => $matches[1], 'func' => $matches[3], 'error' => $matches[4]);
195 throw new PEAR_Exception('Command Failed', self::ACK_COMMAND_FAILED);
196 } elseif (trim($line) == self::RESPONSE_OK) {
197 //The last line of output was hit, close the loop
200 //Output from the server added to the return array
210 * Get the current error data
212 * @return array of error data
214 public function getErrorData()
216 return $this->_current_error;
220 public function clearError() {
221 $this->runCommand('clearerror'); //Cleanup the error
227 * @param $command string a command to be executed through MPD
228 * @param $args mixed string for a single argument, array for multiple
229 * @param $parse mixed false to parse the output, int for parse style
231 * @return array of server output
233 public function runCommand($command, $args = array(), $parse = 0)
235 //Generate the command
236 if (is_array($args)) {
237 foreach($args as $arg) {
238 $command.= ' "'.str_replace('"', '\"', $arg) .'"';
240 } elseif (!is_null($args)) {
241 $command.= ' "'.str_replace('"', '\"', $args) .'"';
243 //Write and then capture the output
244 $this->write($command);
245 $output = $this->read();
247 $this->_output[] = array($command, $output);
248 if ($output === array()) {
251 if ($parse !== false) {
252 return $this->parseOutput($output, $parse);
260 * Parse MPD output on a line-by-line basis
261 * creating output that is easy to work with
263 * @param $input array of input from MPD
264 * @param $style int style number,'0' for the "intelligent" sorting
268 public function parseOutput($input, $style = 0)
270 if (!is_array($input)) {
271 $input = array($input);
273 $count = array('outputs' => 0, 'file' => -1, 'key' => 0);
274 $used_keys = array();
276 $prev = array('key' => null, 'value' => null);
278 foreach($input as $line) {
279 if (is_array($line)) {
280 $this->_errors[] = 'Server output not expected: '.print_r($line, true);
283 $parts = explode(': ', $line, 2);
284 if (!isset($parts[0], $parts[1])) {
285 $this->errors[] = 'Server output not expected: '.$line;
289 $key = trim($parts[0]);
290 $value = trim($parts[1]);
293 //The following has to do strictly
294 //with files in the output
311 if ($key == 'file'||$key== 'cpos') {
314 $output['file'][$count['file']][$key] = $value;
317 //The next section is for a 'stats' call
325 $output['stats'][$key] = $value;
328 //Now for a status call:
332 case 'playlistlength':
342 $output['status'][$key] = $value;
348 case 'outputenabled':
349 if ($key == 'outputid') {
352 $output['outputs'][$count['outputs']][$key] = $value;
355 //The 'playlist' case works in 2 scenarios
356 //1) in a file/directory listing
357 //2) in a status call
358 // This is to determine if it was in a status call
359 // or in a directory call.
361 if ($prev['key'] == 'random') {
362 $output['status'][$key] = $value;
364 $output[$key][] = $value;
368 //Now that we've covered most of the weird
370 //lets cover everything else!
372 if (isset($used_keys[$key])) {
373 $used_keys = array();
376 $used_keys[$key] = true;
377 //$output[$count['key']][$key] = $value;//This is rarely useful
378 $output[$key][] = $value;
381 } elseif ($style == 1) {
382 $output[$key][] = $value;
384 if ($key == 'directory') {
388 $prev['value'] = $value;
396 * A method to access errors
400 public function getErrors()
402 return $this->_errors;
408 * Used to discover commands that are not available
410 * @return array (null on no functions not being available)
412 public function getNotCommands()
414 $cmds = $this->runCommand('notcommands');
415 if (!isset($cmds['command'])) {
418 return $cmds['command'];
424 * Used to discover which commands are available
426 * @return array (null on no functions being available
428 public function getCommands()
430 $cmds = $this->runCommand('commands');
431 if (!isset($cmds['command'])) {
434 return $cmds['command'];
437 public function hasCommand($name) {
438 $cmds = $this->getCommands();
439 foreach ($cmds as $cmd)
448 * Ping the MPD server to keep the connection running
452 public function ping()
454 if ($this->runCommand('ping')) {
463 * Get various statistics about the MPD server
467 public function getStats()
469 $stats = $this->runCommand('stats');
470 if (!isset($stats['stats'])) {
473 return $stats['stats'];
479 * Get the status of the MPD server
483 public function getStatus()
485 $status = $this->runCommand('status');
486 if (!isset($status['status'])) {
489 return $status['status'];