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 ($output['status']) {
 
 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'];