Location: PHPKode > scripts > HN Shoutcast > hn-shoutcast/hn_shoutcastinfo.class.php
<?PHP
/*******************************************************************************
  * PHP-Script:
  *
  * Retrieve Information from ShoutcastServers
  *
  * - Optional with Proxy- and 'user:hide@address.com:port'-Support
  * - Automatic handling of accidential connections to a stream instead to the HTML-Infopage!
  *   (Maybe happen, if you automatically retrieve URLs from Playlists and pass them to the class)
  * - can be used in web-environment or as commandline-script
  *
  * Basic-Class: hn_shoutcast
  * - hn_shoutcast($http_debug=FALSE,$socket_timeout=10)
  * - query_URL($url,$proxy=NULL) connect the $url (optional via proxy),
  *   retrieve all available Information and hold them internally in array $info:
  *   ('Server Status','Stream Status','Stream Title','Stream URL','Content Type','Bandwidth','Stream Genre','Current Song','Listeners','MaxListeners','Listener Peak','Average Listen Time','Stream AIM','Stream IRC','Stream ICQ')
  *
  * With ClassExtension: hn_shoutcastInfo
  * you have easy access to each single information:
  *   - is_online()
  *   - station()
  *   - url()
  *   - genre()
  *   - song()
  *   - bandwidth()
  *   - listeners()
  *   - maxlisteners()
  *   - listenerpeak()
  *   - content_type()
  *   - aim()
  *   - irc()
  *   - icq()
  *
  * For more Information, read the comments in class itself and
  * see/try the example files.
  *
  * If you don't have them, go to
  * - http://hn273.users.phpclasses.org/browse/author/45770.html
  * and select the desired classpage, or go directly to:
  * - http://hn273.users.phpclasses.org/browse/package/2049.html
  *
  ******************************************************************************
  *
  * @Author:    Horst Nogajski <hide@address.com>
  * @Copyright: (c) 1999 - 2005
  * @Licence:   GNU GPL (http://www.opensource.org/licenses/gpl-license.html)
  * @Version:   1.0
  *
  * $Source: //BIKO/CVS_REPOSITORY/hn_php/hn_shoutcastinfo/hn_shoutcastinfo.class.php,v $
  * $Id: hn_shoutcastinfo.class.php,v 1.4 2005/01/05 23:44:25 horst Exp $
  *
  * Tabsize: 4
  *
  **/


// BASIC-CLASS
class hn_shoutcast
{

	// PARAMS for PUBLIC READ/WRITE-ACCESS
		var $http_debug;                    // BOOLEAN: switch debugging ON/OFF
		var $socket_timeout;                // INTEGER: seconds until SocketTimeout

	// PARAMS for PUBLIC READ-ACCESS
		var $online;                        // BOOLEAN: is TRUE if the Server is streaming
		var $info;                          // ARRAY:   holds all available Response-Informations

	// PARAMS for PRIVATE USAGE
		var $useragent;                     // STRING:  best it should be a known Webbrowser-UserAgentString
		var $statuscode;                    // INTEGER: HTTP-Response-Code
		var $redirections;                  // INTEGER: count redirections (Status-Codes 3xx)
		var $NotAvailable   = 'N/A';        // STRING:  is displayed instead of infos witch couldn't retrieved from Shoutcastserver
		var $is_stream_msg  = '<-- NO HTML-PAGE, MAYBE A STREAM -->'; // STRING: is stored in HTTP['body'], when accidentially a stream-URL was queried instead of the HTML-page


	// CONSTRUCTOR
		function hn_shoutcast($http_debug=FALSE,$socket_timeout=10)
		{
			$this->http_debug = $http_debug;
			$this->socket_timeout = $socket_timeout;
			$this->useragent = 'Mozilla/5.0 (Windows; U; Windows NT 5.0; rv:1.7.3) Gecko/20041001 Firefox/0.10.1';
		}



	// PUBLIC METHODS

		function query_URL($url,$proxy=NULL)
		{
			// RESET
			$this->online = FALSE;
			$this->statuscode = 0;
			$this->redirections = 0;
			$this->info = array();

			// QUERY
			$response = $this->_HTTP_REQUEST($url,$proxy);
			if($this->statuscode != 200)
			{
				$this->debug("\n- Query has failed! Status-Code: {$this->statuscode}");
				return FALSE;
			}

			// PARSE RESPONSE
			$this->_parse_ShoutcastInfo($response['body']);
		}



	// PRIVATE METHODS

		function _HTTP_REQUEST($query_url,$proxy=NULL,$default_PORT=8888,$fullPath=TRUE,$try=0,$ExplicitlYUseDefaultport=FALSE)
		{
			// PARAMS
			$method   = 'GET';
			$HTTP     = '1.0';
			$response = '';
			$try++;

			// PARSE URL
			$url = trim($query_url);
			if(!preg_match("=://=", $url))
			{
				$url = "http://$url";
			}
			$url = parse_url($url);
			if(strtolower($url["scheme"]) != "http")
			{
				return FALSE;
			}
			if(!isset($url['port']) || $ExplicitlYUseDefaultport)
			{
				$url['port'] = $default_PORT;
			}
			if(!isset($url['path']))
			{
				$url['path'] = "/";
			}
			// BUILD PATH
			if($fullPath)
			{
				if(empty($proxy))
				{
					$path		= $url['path'];
				}
				else
				{
					$proxy_url	= parse_url($proxy);
					$path		= 'http://'.$url['host'].':'.$url['port'].$url['path'];
				}
				if(isset($url['query']))
				{
					$path .= '?'.$url['query'];
				}
				if(isset($url['fragment']))
				{
					$path .= '#'.$url['fragment'];
				}
			}
			else
			{
				if(empty($proxy))
				{
					$path 		= '/';
				}
				else
				{
					$proxy_url	= parse_url($proxy);
					$path		= 'http://'.$url['host'].':'.$url['port'].'/';
				}
			}


			// CONNECT
			$errstr = '';
			$errno = 0;
			if(empty($proxy))
			{
				$this->debug("\n- open Sock to Host: ".$url["host"]." Port: ".$url["port"]);
				$fp = @fsockopen($url["host"], $url["port"], $errno, $errstr, $this->socket_timeout);
			}
			else
			{
				$this->debug("\n- open Sock to Proxy: ".$proxy_url["host"]." Port: ".$proxy_url["port"]);
				$fp = @fsockopen($proxy_url['host'], $proxy_url['port'], $errno, $errstr, $this->socket_timeout);
			}

			// Check Connection-Success
			if($fp===FALSE)
			{
				$this->debug("\n- ERROR: connecting has failed!\n  REASON: ($errno) $errstr\n");
				return FALSE;
			}


			// BUILD HEADERS
			$headers = array();
			$headers[] = "$method $path HTTP/$HTTP";
			$headers[] = "Host: {$url['host']}";
			if(!empty($proxy))
			{
				if($proxy_url['user'] != '' && $proxy_url['pass'] != '')
				{
					$headers[] = 'Proxy-Authorization: Basic '.base64_encode($proxy_url['user'].':'.$proxy_url['pass']);
				}
				$headers[] = "Proxy-Connection: keep-alive";
			}
			if(isset($url['user']) && isset($url['pass']))
			{
				$headers[] = 'Authorization: Basic '.base64_encode($url['user'].':'.$url['pass']);
			}
			$headers[] = "Keep-Alive: 300";
			$headers[] = "User-Agent: {$this->useragent}";
			$headers[] = "Accept: text/html;q=0.9,text/plain;q=0.8,";
			$headers[] = "Accept-Language: de,en;q=0.5";
			$headers[] = "Accept-Charset: ISO-8859-1;q=0.9,Windows-1252;q=0.3,";
			//$headers[] = "Accept-Encoding: gzip,deflate";
			$headers[] = "Connection: keep-alive";

			$request = join("\r\n", $headers)."\r\n\r\n";


			// SEND REQUEST
			$this->debug("\n- QUERY: ".(empty($proxy) ? "http://{$url['host']}:{$url['port']}$path\n" : "$path\n"));
			$this->debug("\n- send Requestheaders:\n$request");
			fwrite($fp, $request);


			// GET RESPONSE
			$responseheaders = '';
			$file = '';
			$this->debug("\n- retrieve Responseheaders:\n");
			while($line = fgets($fp, 1024))
			{
				if(preg_match('/^\s*[\r\n|\r|\n]$/', $line))
				{
					// Have an empty line! All Headers retrieved now! Body begins with next line.
					break;
				}
				$responseheaders .= $line;
			}
			$this->debug($responseheaders);


			// PARSE REPONSEHEADERS
			$http = $this->_parse_ResponseHeader($responseheaders);


			// CHECK STATUSCODE, RETURN RESPONSE OR DO A RECURSIVECALL
			$this->statuscode = $http["Status-Code"];
			if($http["Status-Code"][0] == 2)
			{
				// A (YET UNKNOWN) RESOURCE IS AVAILABLE, START TO RETRIEVE IT
				$buffer = '';
				while(!feof($fp))
				{
					$buffer = fgets($fp,1024);
					$file .= $buffer;

					// validate that we have a HTML-page and not a media-stream:
					if((!stristr($buffer,'ICY 404')===FALSE) || (trim($buffer)=='ICY 200 OK') || (isset($http['content-type']) && $http['content-type']!='text/html'))
					{
						// LITTLE ACCIDENT: LOOKS LIKE WE HAVE A STREAM-HEADER IN FIRST TRY
						switch($try)
						{
							case 1:
							// we do a second try with the URL cutted down to root-path
							fclose($fp);
							$this->debug("\n- OOOps! Looks like we are connected to a media-stream\n  Try once more\n");
							return $this->_HTTP_REQUEST($query_url,$proxy,$default_PORT,FALSE,$try);
							break;

							case 2:
							// we do a third try with the URL cutted down to root-path AND explicitly using the ShoutCast-default-port
							fclose($fp);
							$this->debug("\n- OOOps! Looks like we are connected to a media-stream\n  Last try now!\n");
							return $this->_HTTP_REQUEST($query_url,$proxy,$default_PORT,FALSE,$try,TRUE);
							break;

							default:
							// BAD: HAVE NO VALID HTML-PAGE ALSO IN THIRD TRY
							fclose($fp);
							return array('header'=>$http, 'body'=>$this->is_stream_msg);
						}
					}
				}
				fclose($fp);
				return array('header'=>$http, 'body'=>$file);
			}
			elseif($http["Status-Code"][0] == 3)
			{
				// RESOURCE HAS MOVED! RECURSIVE CALL WITH NEW LOCATION
				if(++$this->redirections >= 5)
				{
					// MAXIMUM REDIRECTIONS REACHED! GIVE UP
					return array('header'=>$http, 'body'=>FALSE);
				}
				$location = '';
				$location = isset($http["Location"]) ? $http["Location"] : $location;
				$location = isset($http["location"]) ? $http["location"] : $location;
				return $this->_HTTP_REQUEST($location,$proxy,$default_PORT);
			}
			else
			{
				// RESOURCE IS NOT AVAILABLE!
				return array('header'=>$http, 'body'=>FALSE);
			}
		}


		function _parse_ResponseHeader($headers)
		{
			$matches = array();
			$http = array();
			preg_match("=^(HTTP/\d+\.\d+) (\d{3}) ([^\r\n]*)=", $headers, $matches);
			if(isset($matches[0])) $http["Status-Line"] = $matches[0];
			if(isset($matches[1])) $http["HTTP-Version"] = $matches[1];
			if(isset($matches[2])) $http["Status-Code"] = $matches[2];
			if(isset($matches[3])) $http["Reason-Phrase"] = $matches[3];

			$rclass = array("Informational", "Success", "Redirection", "Client Error", "Server Error");
			$http["Response-Class"] = $rclass[$http["Status-Code"][0] - 1];

			preg_match_all("=^(.+): ([^\r\n]*)=m", $headers, $matches, PREG_SET_ORDER);
			foreach($matches as $line) $http[$line[1]] = $line[2];

			return $http;
		}


		function _parse_ShoutcastInfo($body)
		{
			// VALIDATION
			if($body == $this->is_stream_msg || $body == FALSE)
			{
				$this->debug("\n- No Infos available:\n  $body");
				return FALSE;
			}
			if(!stristr($body,'ICY 404')===FALSE)
			{
				$this->debug("\n- No valid Infos available:\n<pre>\n".strip_tags($body)."</pre>\n");
				return FALSE;
			}
			$this->debug("\n- SUCCESS! We got a HTML-Page. Now parse it.");

			// Prepare string
			$body = str_replace(array('&nbsp;','</tr>','</TR>'),array(' ','</tr>'.chr(9),'</TR>'.chr(9)),$body);
			$body = strip_tags($body);
			// separate table with infos
			$body = preg_replace('/^.*?Current Stream Information/ism','',$body);
			$body = preg_replace('/Written by.*$/ism','',$body);
			// read all available infos into infoArray
			$lines = split(chr(9),$body);
			foreach($lines as $line)
			{
				if(trim($line)=='') continue;
				$temp = split(":",$line,2);
				$this->info[$temp[0]] = trim($temp[1]);
			}
			// strip down listener infos
			if(isset($this->info['Stream Status']) && strstr($this->info['Stream Status'],'Stream is up'))
			{
				$this->online = TRUE;
				$pattern = '/Stream is up at (.*?) with (.*?) of (.*?) listeners.*/i';
				$matches = array();
				preg_match($pattern,$this->info['Stream Status'],$matches);
				$this->info['Bandwidth'] = isset($matches[1]) ? $matches[1] : $this->NotAvailable;
				$this->info['Listeners'] = isset($matches[2]) ? $matches[2] : $this->NotAvailable;
				$this->info['MaxListeners'] = isset($matches[3]) ? $matches[3] : $this->NotAvailable;
			}
			// validate against needed infos
			$names = array('Server Status','Stream Status','Stream Title','Stream URL','Content Type','Bandwidth','Stream Genre','Current Song','Listeners','MaxListeners','Listener Peak','Average Listen Time','Stream AIM','Stream IRC','Stream ICQ');
			foreach($names as $v)
			{
				if(!isset($this->info[$v])) $this->info[$v] = $this->NotAvailable;
			}

			// debugging
			if($this->http_debug)
			{
				if(isset($_SERVER['HTTP_HOST'])) echo '<pre>';
				echo "\n- InfoArray now contains:\n";
				foreach($this->info as $k=>$v)
				{
					echo "\t$k => $v\n";
				}
				if(isset($_SERVER['HTTP_HOST'])) echo '</pre>';
			}
			return TRUE;
		}


		function debug($msg)
		{
			if(!$this->http_debug) return;
			if(isset($_SERVER['HTTP_HOST'])) echo '<pre>';
			echo $msg;
			if(isset($_SERVER['HTTP_HOST'])) echo '</pre>';
		}

} // END CLASS hn_shoutcast



// EXTENSION FOR EASY ACCESS TO EACH RETRIEVED SINGLE INFORMATION
class hn_ShoutcastInfo extends hn_Shoutcast
{

	// CONSTRUCTOR

		function hn_shoutcastInfo($http_debug=FALSE,$socket_timeout=10)
		{
			$this->hn_shoutcast($http_debug,$socket_timeout);
		}


	// PUBLIC METHODS

		function is_online()
		{
			return $this->online;
		}

		function station()
		{
			return isset($this->info['Stream Title']) ? $this->info['Stream Title'] : $this->NotAvailable;
		}

		function url()
		{
			return isset($this->info['Stream URL']) ? $this->info['Stream URL'] : $this->NotAvailable;
		}

		function genre()
		{
			return isset($this->info['Stream Genre']) ? $this->info['Stream Genre'] : $this->NotAvailable;
		}

		function song()
		{
			return isset($this->info['Current Song']) ? $this->info['Current Song'] : $this->NotAvailable;
		}

		function bandwidth()
		{
			return isset($this->info['Bandwidth']) ? $this->info['Bandwidth'] : $this->NotAvailable;
		}

		function listeners()
		{
			return isset($this->info['Listeners']) ? $this->info['Listeners'] : $this->NotAvailable;
		}

		function maxlisteners()
		{
			return isset($this->info['MaxListeners']) ? $this->info['MaxListeners'] : $this->NotAvailable;
		}

		function listenerpeak()
		{
			return isset($this->info['Listener Peak']) ? $this->info['Listener Peak'] : $this->NotAvailable;
		}

		function content_type()
		{
			return isset($this->info['Content Type']) ? $this->info['Content Type'] : $this->NotAvailable;
		}

		function aim()
		{
			return isset($this->info['Stream AIM']) ? $this->info['Stream AIM'] : $this->NotAvailable;
		}

		function irc()
		{
			return isset($this->info['Stream IRC']) ? $this->info['Stream IRC'] : $this->NotAvailable;
		}

		function icq()
		{
			return isset($this->info['Stream ICQ']) ? $this->info['Stream ICQ'] : $this->NotAvailable;
		}


} // END CLASS-EXTENSION hn_shoutcastInfo



?>
Return current item: HN Shoutcast