info about link
: Javascript : PHP Index : MySQL :
internet radio
: Source : : Explanation : : Example : : Todo : : Feedback :

internetradio.php

A collection of classes which can retrieve information from internet radio streams such as shoutcast, icecast and steamcast

Added: 2010-07-15 18:40:14

download

<?
/**
 * Excudo InternetRadio
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://devshed.excudo.net/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to marty@excudo.net so we can send you a copy immediately.
 *
 * @category   Excudo
 * @package    InternetRadio
 * @copyright  Copyright (c) 2005-2010 Excudo. (http://www.excudo.net)
 * @license    http://devshed.excudo.net/license/new-bsd     New BSD License
 */

/***
 * file: Abstract.php
 */

/**
 * @see InternetRadio_Exception
 */
require_once "Exception.php";

/**
 * Contains blueprint for some necessary functions as well as most of the functionality needed
 * to parse the online information of an internet radiostation
 */
abstract class InternetRadio_Abstract
{
   
/**
    * This value represents the case in which we want any possible exception to be thrown.
    * @see $exceptionReporting
    */
   
const EXCEPTION_THROW   1;
   
/**
    * This value represents the case in which we want any possible exception to caught, but shown
    * @see $exceptionReporting
    */
   
const EXCEPTION_SHOW   2;
   
/**
    * This value represents the case in which we want any possible exception to caught and hidden
    * @see $exceptionReporting
    */
   
const EXCEPTION_HIDE   3;

   
/**
    * This setting determines what will be done when a exception occurs
    * This only applies to exceptions that are thrown during the retrieval of information. Other
    * exceptions, for example during the parsing of that information, will always be thrown.
    *
    * @see getExceptionReporting()
    * @see setExceptionReporting()
    *
    * @var integer
    */
   
protected $exceptionReporting self::EXCEPTION_HIDE;

   
/**
    * The domain where the radiostation can be found
    *
    * @var String
    */
   
public $domain;

   
/**
    * The port at which it can be found
    *
    * @var String
    */
   
public $port;

   
/**
    * The path where we can find the output.
    * This is the path where the server/stream information can be found
    *
    * @var String
    */
   
public $path;

   
/**
    * This array will be filled with the applicable fields (which are different for every server-type)
    * It can only contain key-value pairs of which the key also exist in the $defaultFields array
    * of the implementing class
    *
    * @see setFields()
    *
    * @var array
    */
   
public $fields = array();

   
/**
    * Array which is going to store different kinds of content (depending on which type of content
    * is loaded). In order to display information about the radio-station, different kinds of
    * content may be loaded from different paths on the server. Whenever data has been loaded, it
    * will be cached in this array.
    *
    * @see setContentArr()
    *
    * @var Array
    */
   
protected $contentArr;

   
/**
    * Different kinds of output-templates can be set in order to display the information with a
    * certain markup. These templates can be passed as an argument whenever output is required,
    * but you can set a default one as well, which is stored in this variable.
    *
    * @see setOutputTemplate()
    *
    * @var InternetRadio_Output_Interface
    */
   
protected $outputTemplate;

   
/**
    * Flag to toggle the creation of hyperlinks in the output of the server-information.
    * By default this is not something we want
    *
    * @see setCreateHyperlinks()
    *
    * @var boolean
    */
   
protected $createHyperlinks False;

   
/**
    * By setting this property you can indicate what the character encoding is, that
    * the server uses in the public html pages with information.
    * Note: an attempt will be made to auto-detect the character encoding, but this
    * doesn't always work. This setting will always overrule that attempt though.
    *
    * @see setInputEncoding()
    *
    * @var String
    */
   
protected $inputEncoding;

   
/**
    * By setting this property you can decide how the output should be encoded. For
    * example: if the stream-information is encoded 'iso-8859-1' and the website
    * on which you want to display it is 'utf-8', then you should set this property
    * to 'utf-8'
    *
    * @see setOutputEncoding()
    *
    * @var String
    */
   
protected $outputEncoding;

   
/**
    * An attempt will be made to auto-detect the encoding of the stream. (Currently, this
    * only works for shoutcast server). When the attempt is succesfull, it will be
    * saved in this property.
    * Unless the $inputEncoding is set manually, this value will be treated as the
    * inputEncoding
    *
    * @see getStreamEncoding()
    *
    * @var String
    */
   
protected $streamEncoding;


   
/**
    * Abstract functions
    */

   /**
    * Implementation of this function should parse the html with server/stream info
    * and fill the internal $fields array with it.
    *
    * @return void
    */
   
abstract protected function parseFields();

   
/**
    * Implementation of this function should return what type of InternetRadiostation
    * the class can handle
    *
    * @return String
    */
   
abstract public function getServerType();

   
/**
    * Implementation of this function should assign an array to internal $contentArr
    * which contains the different kinds of content we can retrieve. These types
    * should be the keys in the array and their values should be null.
    * When that particular type of content has been loaded, null will be overwritten
    * with the retrieved content.
    *
    * example: $this->contentArr = array("stream" => null, "history" => null);
    *
    * @return void
    */
   
abstract protected function setContentArr();

   
/**
    *
    *
    * @return void
    */
   
abstract protected function setPageMap();

   
/**
    * Every extended class should define this method in such a way that by default it set the
    * $streamChunks array for the different kinds of information that can be retrieved.
    * The array consists of the index under which the contents of a page are cached (and loaded)
    * and the value represents how many chunks of data should be loaded by the fopen() command.
    * This is because sometimes the data is embedded in a(n endless) stream in which case you
    * want to limit it, to prevent it loads forever.
    *
    * @return void
    */
   
abstract protected function setStreamChunks();

   
/**
    * Public funtions
    */

   /**
    * Constructor.
    *
    * @return InternetRadio_Abstract
    */
   
public function __construct($url)
   {
      
// parsing url and setting appro
      
$parsed_url parse_url($url);
      
$this->domain   = isset($parsed_url['host']) ? $parsed_url['host'] : "";
      
$this->port   = !isset($parsed_url['port']) || empty($parsed_url['port']) ? "80" $parsed_url['port'];
      
$this->path   = empty($parsed_url['path']) ? "/" $parsed_url['path'];

      if (empty(
$this->domain))
      {
         
$this->domain $this->path;
         
$this->path "";
      }

      
// setting default fields
      
$this->setFields();
      
// setting default page map
      
$this->setPageMap();
      
// setting default stream-chunks
      
$this->setStreamChunks();

      
// sets the indexes for all the different kinds of content
      
$this->setContentArr();
      if (!
is_array($this->contentArr))
         throw new 
InternetRadio_Exception("contentArr is not an array. Please implement setContentArr() properly.");
      elseif (
count($this->contentArr) == 0)
         throw new 
InternetRadio_Exception("contentArr is empty. Please implement setContentArr() properly.");
      else
      {
         foreach (
$this->contentArr AS $key => $value)
         {
            if (!
is_null($value))
               throw new 
InternetRadio_Exception("Every value in the contentArr must have a default value of 'null'");
         }
      }

      
// setting default output template
      
require_once dirname(__FILE__)."/Output/Html/Table.php";
      
$this->setOutputTemplate(new InternetRadio_Output_Html_Table());
   }

   
/**
    * Retrieves the information about the server/stream and returns it using
    * the choosen (or default) template
    *
    * @param String $page                  The page where the information about
    *                         the stream can be found. If null, the
    *                         default will be used. See
    * @param InternetRadio_Output_Interface $template      The template that has to be used when
    *                         upon returning the data
    *
    * @return mixed   The output as rendered by the template object. In most cases
    *         this will be a string, but it could just as easily be another
    *         type (for example, InternerRadio_Output_Php returns an array)
    */
   
public function getServerInfo($page nullInternetRadio_Output_Interface $template null)
   {
      try {
         
$this->parseFields($page);
      } catch (
InternetRadio_Exception $e) {
         
$this->error $e->getMessage();
         switch (
$this->exceptionReporting)
         {
            case 
self::EXCEPTION_THROW :
               throw 
$e;
               break;
            case 
self::EXCEPTION_SHOW :
               
$this->fields = array("error" => $e->getMessage());
               break;
            case 
self::EXCEPTION_HIDE :
            default :
               
$this->fields = array("error"   => "failed to load stream");
               break;
         }
      }
      return 
$this->getInfo($this->fields$template);
   }

   
/**
    * Same as getServerInfo($page, InternetRadio_Output_Html_Table()), but without the
    * need to pass the template object as an argument
    *
    * @param String $page
    *
    * @see getServerInfo()
    *
    * @return mixed
    */
   
public function getServerInfoAsHtml($page null)
   {
      require_once 
dirname(__FILE__)."/Output/Html/Table.php";
      return 
$this->getServerInfo($page, new InternetRadio_Output_Html_Table());
   }
   
/**
    * Same as getServerInfo($page, InternetRadio_Output_Php()), but without the
    * need to pass the template object as an argument
    *
    * @param String $page
    *
    * @see getServerInfo()
    *
    * @return mixed
    */
   
public function getServerInfoAsPhp($page null)
   {
      require_once 
dirname(__FILE__)."/Output/Php.php";
      return 
$this->getServerInfo($page, new InternetRadio_Output_Php());
   }
   
/**
    * Same as getServerInfo($page, InternetRadio_Output_Xml()), but without the
    * need to pass the template object as an argument
    *
    * @param String $page
    *
    * @see getServerInfo()
    *
    * @return mixed
    */
   
public function getServerInfoAsXml($page null)
   {
      require_once 
dirname(__FILE__)."/Output/Xml.php";
      return 
$this->getServerInfo($page, new InternetRadio_Output_Xml());
   }

   
/**
    * By passing an array with field-names, this function can exercise control over which server-information
    * will be displayed when getServerInfo() is called.
    * When called without arguments (or boolean false as argument), all available fields will be shown
    *
    * @param array $array      Array of fields that we want to display when the server-info is requested
    *                      These fields have to exist in the $defaultFields property
    *
    * @return void
    */
   
public function setFields($array=null)
   {
      if (
is_null($array))
      {
         
$this->fields $this->defaultFields;
      }
      else
      {
         
$tmpArr = array();
         
// before setting the array, we check if it contains invalid fields
         
foreach ($array AS $key => $value)
         {
            if (
array_key_exists($key$this->defaultFields))
            {
               
$tmpArr[$key] = "n/a";
            }
            elseif (
array_key_exists($value$this->defaultFields))
            {
               
$tmpArr[$value] = "n/a";
            }
            else
            {
               throw new 
InternetRadio_Exception($key."/".$value." is not a valid field (".implode(", "array_keys($this->defaultFields)).")");
            }
         }
         
$this->fields $tmpArr;
      }
   }

   
/**
    * Influences how the urls in the server information are displayed.
    *
    * @param boolean $bool   If true, the returned server-information will show urls as hyperlinks
    *
    * @see parseFields()
    * @see getServerInfo()
    *
    * @return void
    */
   
public function setCreateHyperlinks($bool)
   {
      
$this->createHyperlinks = (bool) $bool;
   }

   
/**
    * Getter for $outputTemplate
    *
    * @return InternetRadio_Output_Interface
    */
   
public function getOutputTemplate()
   {
      return 
$this->outputTemplate;
   }
   
/**
    * Setter for $outputTemplate
    *
    * @param InternetRadio_Output_Interface $template
    *
    * @return void
    */
   
public function setOutputTemplate(InternetRadio_Output_Interface $template)
   {
      
$this->outputTemplate $template;
   }

   
/**
    * Getter for $exceptionReporting
    *
    * @see self::EXCEPTION_THROW, self::EXCEPTION_SHOW, self::EXCEPTION_HIDE
    *
    * @return integer
    */
   
public function getExceptionReporting()
   {
      return 
$this->exceptionReporting;
   }
   
/**
    * Setter for $exceptionReporting
    *
    * @see self::EXCEPTION_THROW, self::EXCEPTION_SHOW, self::EXCEPTION_HIDE
    *
    * @return void
    */
   
public function setExceptionReporting($int)
   {
      
$this->exceptionReporting = (int) $int;
   }

   
/**
    * Setter for the outputEncoding
    *
    * @see $outputEncoding
    *
    * @return void
    */
   
public function setOutputEncoding($charset)
   {
      
$this->outputEncoding $charset;
   }
   
/**
    * Setter for the inputEncoding
    *
    * @see $inputEncoding
    *
    * @return void
    */
   
public function setInputEncoding($charset)
   {
      
$this->inputEncoding $charset;
   }
   
/**
    * Getter for the streamEncoding
    *
    * @see $streamEncoding
    *
    * @return void
    */
   
public function getStreamEncoding()
   {
      return 
$this->streamEncoding;
   }

   
/**
    * internal methods
    */

   /**
    * Setter for the streamEncoding
    * This setter is not public because only the inputEncoding may be manipulated
    * when working with an object of this class.
    *
    * @see $streamEncoding
    * @see $inputEncoding
    *
    * @return void
    */
   
protected function setStreamEncoding($charset)
   {
      
$this->streamEncoding $charset;
   }
   
   
/**
    * Wrapper method for getContents, which fills the first argument correctly
    *
    * @param String $page      The location of the page with the content. This is the
    *                      [page] part in http://[domain]:[port]/[page]
    *
    * @see getContents()
    *
    * @return String
    */
   
protected function getStreamContents($page null)
   {
      return 
$this->getContents("stream"$page);
   }

   
/**
    * Loads the contents of the stream (/server) information of a radiostationg.
    *
    * @param String $page      The location of the page with the content. This is the
    *                      [page] part in http://[domain]:[port]/[page]
    *
    * @throws InternetRadio_Exception
    *
    * @see loadContents()
    *
    * @return void
    */
   
protected function loadStreamContents($page null)
   {
      
$this->loadContents("stream"$page);
   }

   
/**
    * This function checks if loadContents() has been called before and either returns
    * the cached information if it has, or otherwise calls loadContents and then
    * returns the info
    *
    * @param String $index      This indicates what kind of content we're downloading.
    *                      By naming it, we are able to cache it. (usually a server
    *                      has more than 1 page with information)
    * @param String $page      The location of the page with the content. This is the
    *                      [page] part in http://[domain]:[port]/[page]
    *
    * @see loadContents()
    *
    * @return String
    */
   
protected function getContents($index$page null)
   {
      if (
is_null($this->contentArr[$index]))
      {
         
$this->loadContents($index$page);
      }
      return 
$this->contentArr[$index];
   }

   
/**
    * This method contains all the logic to download the information from the public
    * pages of the internet-radiostation, which contain all the data we're interested
    * in showing.
    *
    * Two other settings are important for correctly loading the content of a page:
    * 1. The $streamChunks array. Every extended class should define this array for the
    * different kinds of information that can be retrieved. The default is 20 and this will be
    * if nothing else has been defined. This is because the server information is usually
    * embedded in the radio-stream who's content is unlimited. By setting the streamChunks
    * to -1, all the content of a certain page will be loaded. So, if the index (see index parameter)
    * of a page is 'history' then you should set $streamChunks['history'] = -1 in the class.
    * 2. The $pageMap array. This array can form a mapping for the type of content to the default page.
    * For example, the history of a shoutcast-server is usually found at /played.html. So in the Shoutcast
    * implementation of this class $pageMap['history'] = '/played.html'. This takes away the necessity
    * to always pass the $page variable as an argument, while keeping the flexibility for instances
    * where
    *
    * @param String $index      This indicates what kind of content we're downloading.
    *                      By naming it, we are able to cache it. (usually a server
    *                      has more than 1 page with information)
    * @param String $page      The location of the page with the content. This is the
    *                      [page] part in http://[domain]:[port]/[page]
    *
    * @see setPageMap()
    * @see setStreamChunks()
    *
    * @throws InternetRadio_Exception
    *
    * @return void
    */
   
protected function loadContents($index$page null)
   {
      if (!
array_key_exists($index$this->contentArr))
      {
         throw new 
InternetRadio_Exception($index." is not a valid content-index. should be: ".implode(", "array_keys($this->contentArr)));
      }
      if (
is_null($page))
      {
         if (!isset(
$this->pageMap[$index]))
            throw new 
InternetRadio_Exception("There is no default page defined for ".$index);
         else
            
$page $this->pageMap[$index];
      }
      
$this->contentArr[$index] = "";
      
$domain = (substr($this->domain07) == "http://") ? substr($this->domain7) : $this->domain;

      if (@
$fp fsockopen($domain$this->port$this->errno$this->errstr2))
      {
         
fputs($fp"GET ".$page." HTTP/1.1\r\n".
            
"User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows 98)\r\n".
            
"Accept: */*\r\n".
            
"Host: ".$domain."\r\n\r\n");

         
$c 0;
         
$cMax = isset($this->streamChunks[$index]) ? $this->streamChunks[$index] : 20;
         while (!
feof($fp) && ($cMax == -|| $c <= $cMax))
         {
            
$this->contentArr[$index] .= fgets($fp4096);
            
$c++;
         }
         
fclose ($fp);

         
// this doesn't work for all server-types
         
preg_match("/(meta http-equiv=\"Content-Type\" content=\"(.*); charset=(.*)\")/iU"$this->contentArr[$index], $matches);
         if (isset(
$matches[3]))
         {
            
$this->setStreamEncoding($matches[3]);
         }

         
$this->encodeContent($this->contentArr[$index]);
      }
      else
      {
         
$errArr error_get_last();
         throw new 
InternetRadio_Exception("couldn't open stream @ ".$domain.":".$this->port."; ERROR: ".$errArr['message'].", type[".$errArr['type']."] in ".$errArr['file']." on line ".$errArr['line']);
      }
   }

   
/**
    * Returns any kind of data-array using a template's render function.
    * When no template is passed, if will use the default template (@see constructor)
    *
    * @param array $data                  An array containing data with information about the radio-station.
    * @param InternetRadio_Output_Interface $template   The template that has to be used when upon returning the data
    *
    * @return mixed   The output as rendered by the template object. In most cases
    *                this will be a string, but it could just as easily be another
    *                type (for example, InternerRadio_Output_Php returns an array)
    */
   
protected function getInfo(array $dataInternetRadio_Output_Interface $template null)
   {
      if (!
is_null($template))
      {
         
$oldTemplate $this->getOutputTemplate();
         
$this->setOutputTemplate($template);
      }
      else
      {
         
$template $this->getOutputTemplate();
      }

      
$return $template->render($data);

      if (isset(
$oldTemplate))
      {
         
$this->setOutputTemplate($oldTemplate);
      }
      return 
$return;
   }

   
/**
    * This method encodes the passed string, based on the settings of $inputEncoding and $outputEncoding
    *
    * @param String $content   The string which we want to encode
    *
    * @return void         (The argument is be passed by reference)
    */
   
protected function encodeContent(&$content)
   {
      
// we only continue if the outputEncoding has been set
      
if (!is_null($this->outputEncoding))
      {
         if (
is_null($this->inputEncoding))
         {
            
$this->inputEncoding $this->streamEncoding;
         }
         if (!
function_exists("iconv"))
         {
            if (empty(
$this->inputEncoding))
               
$content mb_convert_encoding($content$this->outputEncoding);
            else
               
$content mb_convert_encoding($content$this->outputEncoding$this->inputEncoding);
         }
         else
         {
            if (empty(
$this->inputEncoding))
               
$content iconv(null$this->outputEncoding$content);
            else
               
$content iconv($this->inputEncoding$this->outputEncoding$content);
         }
      }
   }
}

/***
 * file: Shoutcast.php
 */

/**
 * @see InternetRadio_Abstract
 */
require_once "Abstract.php";

/**
 * Class that can parse and display the information of Shoutcast Servers
 */
class InternetRadio_Shoutcast extends InternetRadio_Abstract
{
   
/**
    * Used by getServerType() to show what kind of internet-radiostations this class
    * can handle
    */
   
const SERVER_TYPE   "Shoutcast";

   
/**
    * The following constants all serve the function which parses the public information
    * pages. This way, it is easy to change in case the format of these pages change in
    * the future
    */
   /**
    * Determines from which point we start searching for the fields with server information
    * @see parseFields();
    */
   
const OFFSET      "Current Stream Information";
   
/**
    * This is how the html-table with the track-history starts
    * @see parseHistory();
    * @see TABLE_END
    */
   
const TABLE_START   "<table border=0 cellpadding=2 cellspacing=2>";
   
/**
    * End this is how it ends
    * @see parseHistory();
    * @see TABLE_START
    */
   
const TABLE_END      "</table>";


   
/**
    * All the possible fields with information about the stream/server that can be loaded
    * By default, this array is assigned to the $fields (see parent class) array. But, the
    * $fields array can also be set with setFields() in order to display a particular set
    * of fields
    *
    * @var array
    *
    * @see $fields
    * @see setFields()
    */
   
protected $defaultFields = array(
               
"Server Status" => "n/a",
               
"Stream Status" => "n/a",
               
"Listener Peak" => "n/a",
               
"Average Listen Time" => "n/a",
               
"Stream Title" => "n/a",
               
"Content Type" => "n/a",
               
"Stream Genre" => "n/a",
               
"Stream URL" => "n/a",
               
"Current Song" => "n/a"
               
);

   
/**
    * The default streamChunks which are loaded when setStreamChunks() is called without
    * argument (which is done in the constructor of the Abstract parent classs)
    *
    * @var array
    */
   
private $defaultStreamChunks = array(
      
"history"   => -1,
      );
   
/**
    * By default it will be assigned $defaultStreamChunks as value.
    *
    * @see setStreamChunks()
    *
    * @var array
    */
   
protected $streamChunks;

   
/**
    * The default pageMaps which is loaded when setPageMap() is called without
    * argument (which is done in the constructor of the Abstract parent classs)
    *
    * @var array
    */
   
private $defaultPageMap = array(
      
"stream"   => "/",
      
"history"   => "/played.html",
      );
   
/**
    * By default it will be assigned $defaultPageMap as value.
    *
    * @see setPageMap()
    *
    * @var array
    */
   
protected $pageMap;

   
/**
    * Implementation of abstract parent methods
    */

   /**
    * Returns the type of InternetRadiostation this class can handle
    *
    * @return String
    */
   
public function getServerType()
   {
      return 
self::SERVER_TYPE;
   }

   
/**
    * When called without argument it sets the default pageMap.
    * The page map links
    *
    * @see parent::setPageMap()
    *
    * @param array $data   Array containing the mapping
    *
    * @return void
    */
   
protected function setPageMap($data null)
   {
      if (
is_null($data))
         
$data $this->defaultPageMap;
      
$this->pageMap $data;
   }

   
/**
    * When called without argument it sets the default streamChunks.
    *
    * @see parent::setStreamChunks()
    *
    * @param array $data   Array containing the mapping
    *
    * @return void
    */
   
protected function setStreamChunks($data null)
   {
      if (
is_null($data))
         
$data $this->defaultStreamChunks;
      
$this->streamChunks $data;
   }

   
/**
    * Retrieves the page with the information about the server/stream, parses it and puts the
    * parsed data into the appropriate fields
    *
    * @see getStreamContents()
    * @see loadStatus()
    *
    * @return void
    */
   
public function parseFields($page null)
   {
      if (
$contents $this->getStreamContents($page))
      {
          
// parsing the contents
         
$very_first_pos stripos($contentsself::OFFSET);

         foreach (
$this->fields AS $item => $value)
         {
            
$first_pos      stripos($contents$item$very_first_pos);
            
$line_start      strpos($contents"<td>"$first_pos);
            
$line_end      strpos($contents"</td>"$line_start) + 4;
            
$difference      $line_end $line_start;
            
$line         substr($contents$line_start$difference);

            
$this->fields[$item]   = strip_tags($line);

            if (
$this->createHyperlinks && strtolower(substr($item, -3)) == "url")
            {
               
$this->fields[$item] = "<a href=\"".$this->fields[$item]."\">".$this->fields[$item]."</a>";
            }
         }
      }
   }

   
/**
    * This function will be called from the parent's constructor
    * By setting the contentArr, it serves two purposes:
    * 1. The keys of the array can later be used in validations that check if the
    *    index is an existing one (so, an existing key of this array)
    * 2. It initializes the array with null values. This way the caching-functions
    *    will be able to tell if content has not been loaded or if it has been loaded
    *    with empty data
    *
    * @see parent::contentArr
    * @see parent::__construct()
    *
    * @return void
    */
   
protected function setContentArr()
   {
      
$this->contentArr = array(
         
"stream"   => null,
         
"history"   => null,
         );
   }

   
/**
    * overloaded methods
    */

   /***
    * Overloads the parents function in order to cut of part of the output which is irrelevant
    *
    * @var String $page      Optional. The page where we can find the content.
    *                     If none is given, the $pageMap variable will be used
    *
    * @see parent::loadStreamContents()
    *
    * @return void
    */
   
protected function loadContents($index$page null)
   {
      
parent::loadContents($index$page);
      if (
$index == "stream")
      {
         if (!empty(
$this->contentArr['stream']))
         {
            
preg_match("/(Content-Type:)(.*)/i"$this->contentArr['stream'], $matches);
            if (
count($matches) > 0)
            {
               
$contentType trim($matches[2]);
               if (
$contentType != "text/html")
               {
                  throw new 
Exception("This is not a valid shoutcast-stream");
               }
            }
         }
      }
   }

   
/**
    * Other methods which are specific for this internet-radiostation
    */

   /**
    * Retrieves the history of the played tracks and returns it using the choosen
    * (or default) template
    *
    * @param unknown_type $page            The page where the information about
    *                      the stream can be found. If null, the
    *                      default will be used. See
    * @param InternetRadio_Output_Interface $template   The template that has to be used when
    *                     upon returning the data
    *
    * @return mixed   The output as rendered by the template object. In most cases
    *                this will be a string, but it could just as easily be another
    *                type (for example, InternerRadio_Output_Php returns an array)
    */
   
public function getHistoryInfo($page nullInternetRadio_Output_Interface $template null)
   {
      if (empty(
$this->tracks))
      {
         try {
            
$this->parseHistory($page);
         } catch (
InternetRadio_Exception $e) {
            
$this->error $e->getMessage();
            switch (
$this->exceptionReporting)
            {
               case 
self::EXCEPTION_THROW :
                  throw 
$e;
                  break;
               case 
self::EXCEPTION_SHOW :
                  
$this->tracks = array(array("error"$e->getMessage()));
                  break;
               case 
self::EXCEPTION_HIDE :
               default :
                  
$this->tracks = array(array("error""failed to load stream"));
                  break;
            }
         }
      }
      return 
$this->getInfo($this->tracks$template);
   }
   
/**
    * Same as getHistoryInfo($page, InternetRadio_Output_Html_Table()), but without the
    * need to pass the template object as an argument
    *
    * @param String $page
    *
    * @see getHistoryInfo()
    *
    * @return mixed
    */
   
public function getHistoryInfoAsHtml($page "/played.html")
   {
      require_once 
dirname(__FILE__)."/Output/Html/Table.php";
      return 
$this->getHistoryInfo($page, new InternetRadio_Output_Html_Table());
   }
   
/**
    * Same as getHistoryInfo($page, InternetRadio_Output_Php()), but without the
    * need to pass the template object as an argument
    *
    * @param String $page
    *
    * @see getHistoryInfo()
    *
    * @return mixed
    */
   
public function getHistoryInfoAsPhp($page "/played.html")
   {
      require_once 
dirname(__FILE__)."/Output/Php.php";
      return 
$this->getHistoryInfo($page, new InternetRadio_Output_Php());
   }
   
/**
    * Same as getHistoryInfo($page, InternetRadio_Output_Xml()), but without the
    * need to pass the template object as an argument
    *
    * @param String $page
    *
    * @see getHistoryInfo()
    *
    * @return mixed
    */
   
public function getHistoryInfoAsXml($page "/played.html")
   {
      require_once 
dirname(__FILE__)."/Output/Xml.php";
      return 
$this->getHistoryInfo($page, new InternetRadio_Output_Xml());
   }

   
/**
    * Retrieves the page with the information about the history of the played tracks,
    * parses it and craetes an array of tracks out of the parsed data
    *
    * @param String $page      The location of the page with the content. This is the
    *                      [page] part in http://[domain]:[port]/[page]
    *
    * @return void
    */
   
protected function parseHistory($page null)
   {
      
$html $this->getHistoryContents($page);

      
$fromPos   stripos($htmlself::TABLE_START);
      
$toPos      stripos($htmlself::TABLE_END$fromPos);
      
$tableData   substr($html$fromPos, ($toPos-$fromPos));
      
$lines      explode("</tr><tr>"$tableData);
      
$tracks = array();
      
$c 0;
      foreach (
$lines AS $line)
      {
         
$info explode ("</td><td>"$line);
         
$time trim(strip_tags($info[0]));
         if (
substr($time09) != "Copyright" && !preg_match("/Tag Loomis, Tom Pepper and Justin Frankel/i"$info[1]))
         {
            
$this->tracks[$c]['time'] = $time;
            
$this->tracks[$c++]['track'] = trim(strip_tags($info[1]));
         }
      }
      if (
count($this->tracks) > 0)
      {
         unset(
$this->tracks[0]);
         if (isset(
$this->tracks[1]))
            
$this->tracks[1]['track'] = str_replace("Current Song"""$this->tracks[1]['track']);
      }
   }

   
/**
    * Loads the contents of the information about the history of the played tracks
    *
    * @param String $page      The location of the page with the content. This is the
    *                      [page] part in http://[domain]:[port]/[page]
    *
    * @throws InternetRadio_Exception
    *
    * @see loadContents()
    *
    * @return void
    */
   
protected function loadHistoryContents($page null)
   {
      
$this->loadContents("history"$page);
   }

   
/**
    * Wrapper for parent::getContents() with the first parameter filled correctly
    *
    * @param String $page      The location of the page with the content. This is the
    *                      [page] part in http://[domain]:[port]/[page]
    *
    * @see getContents()
    *
    * @return String
    */
   
protected function getHistoryContents($page null)
   {
      return 
$this->getContents("history"$page);
   }
}

/***
 * file: Icecast.php
 */

/**
 * @see InternetRadio_Abstract
 */
require_once "Abstract.php";

/**
 * Class that can parse and display the information of Icecast Servers
 */
class InternetRadio_Icecast extends InternetRadio_Abstract
{
   const 
SERVER_TYPE   "Icecast";

   
/**
    * All the possible fields with information about the stream/server that can be loaded
    * By default, this array is assigned to the $fields (see parent class) array. But, the
    * $fields array can also be set with setFields() in order to display a particular set
    * of fields
    *
    * @var array
    *
    * @see $fields
    * @see setFields()
    */
   
protected $defaultFields = array(
               
"Server Type" => "n/a",
               
"Stream Status" => "n/a",
               
"Listener Peak" => "n/a",
               
"Stream Title" => "n/a",
               
"Content Type" => "n/a",
               
"Mount Started" => "n/a",
               
"Stream Genre" => "n/a",
               
"Stream Description" => "n/a",
               
"Stream URL" => "n/a",
               
"Current Song" => "n/a",
               
"Current Listeners" => "n/a",
               
"Bitrate" => "n/a",
               
"Audio Info" => "n/a",
               );

   
/**
    * The default streamChunks which are loaded when setStreamChunks() is called without
    * argument (which is done in the constructor of the Abstract parent classs)
    *
    * @var array
    */
   
private $defaultStreamChunks = array(
      
"stream"   => 20,
      
"status"   => -1
      
);
   
/**
    * By default it will be assigned $defaultStreamChunks as value.
    *
    * @see setStreamChunks()
    *
    * @var array
    */
   
protected $streamChunks;

   
/**
    * The default pageMaps which is loaded when setPageMap() is called without
    * argument (which is done in the constructor of the Abstract parent classs)
    *
    * @var array
    */
   
private $defaultPageMap = array(
      
"stream"   => "/",
      
"status"   => "/status.xsl"
      
);
   
/**
    * By default it will be assigned $defaultPageMap as value.
    *
    * @see setPageMap()
    *
    * @var array
    */
   
protected $pageMap;

   
/**
    * When true the 'Audio Info' data will be parsed as well and saved
    * in seperate bits of information: bitrate, channels & samplerate.
    * Note: bitrate is already available from another field
    *
    * @var boolean
    */
   
protected $parseAudioInfo True;

   
/**
    * Implementation of abstract parent methods
    */

   /**
    * Returns the type of InternetRadiostation this class can handle
    *
    * @return String
    */
   
public function getServerType()
   {
      return 
self::SERVER_TYPE;
   }

   
/**
    * When called without argument it sets the default pageMap.
    * The page map links
    *
    * @see parent::setPageMap()
    *
    * @param array $data   Array containing the mapping
    *
    * @return void
    */
   
protected function setPageMap(array $data null)
   {
      if (
is_null($data))
         
$data $this->defaultPageMap;
      
$this->pageMap $data;
   }

   
/**
    * When called without argument it sets the default streamChunks.
    *
    * @see parent::setStreamChunks()
    *
    * @param array $data   Array containing the mapping
    *
    * @return void
    */
   
protected function setStreamChunks($data null)
   {
      if (
is_null($data))
         
$data $this->defaultStreamChunks;
      
$this->streamChunks $data;
   }

   
/**
    * Retrieves the page with the information about the server/stream, parses it and puts the
    * parsed data into the appropriate fields
    *
    * @see getStreamContents()
    * @see loadStatus()
    */
   
protected function parseFields($page null)
   {
      
$contents $this->getStreamContents($page);
      if (
False !== $contents)
      {
         
$dataStr str_replace("\r""\n"str_replace("\r\n""\n"$contents));
         
$lines explode("\n"$dataStr);
         foreach (
$lines AS $line)
         {
            if (
$dp strpos($line":"))
            {
               
$key substr($line0$dp);
               
$value trim(substr($line, ($dp+1)));
               if (
preg_match("/genre/i"$key) && isset($this->fields['Stream Genre']))
                  
$this->fields['Stream Genre'] = $value;
               if (
preg_match("/^server$/i"$key) && isset($this->fields['Server Type']))
                  
$this->fields['Server Type'] = $value;
               elseif (
preg_match("/name/i"$key) && isset($this->fields['Stream Title']))
                  
$this->fields['Stream Title'] = $value;
               elseif (
preg_match("/server/i"$key) && isset($this->fields['Stream Type']))
                  
$this->fields['Stream Type'] = $value;
               elseif (
preg_match("/description/i"$key) && isset($this->fields['Stream Description']))
                  
$this->fields['Stream Description'] = $value;
               elseif (
preg_match("/content-type/i"$key) && isset($this->fields['Content Type']))
                  
$this->fields['Content Type'] = $value;
               elseif (
preg_match("/icy-br/i"$key))
               {
                  if (isset(
$this->fields['Stream Genre']))
                     
$this->fields['Stream Status'] = "Stream is up at ".$value."kbps";
                  if (isset(
$this->fields['Bitrate']))
                     
$this->fields['Bitrate'] = $value;
               }
               elseif (
preg_match("/url/i"$key) && isset($this->fields['Stream URL']))
               {
                  if (
$this->createHyperlinks)
                  {
                     
$this->fields['Stream URL'] = "<a href=\"".$value."\">".$value."</a>";
                  }
                  else
                  {
                     
$this->fields['Stream URL'] = $value;
                  }
               }
               elseif (
preg_match("/ice-audio-info/i"$key) && isset($this->fields['Audio Info']))
               {
                  if (
$this->getParseAudioInfo())
                  {
                     
$dataString trim(str_replace("ice-"" "$value));
                     
$dataArr explode("; "$dataString);
                     foreach (
$dataArr AS $data)
                     {
                        list(
$k$v) = explode("="$data);
                        if (
$k == "samplerate")
                           
$this->fields['Samplerate'] = $v;
                        elseif (
$k == "channels")
                           
$this->fields['Channels'] = $v;
                     }
                     unset(
$this->fields['Audio Info']);
                  }
                  else
                  {
                     
$this->fields['Audio Info'] = str_replace("ice-"" "$value);
                  }
               }
            }
         }
         if (!
$this->loadStatus())
         {
            
trigger_error("couldn't find the current stream on the icecast-status-page"E_USER_NOTICE);
         }

         return 
True;
      }
      else
      {
         return 
False;
      }
   }

   
/**
    * This function will be called from the parent's constructor
    * By setting the contentArr, it serves two purposes:
    * 1. The keys of the array can later be used in validations that check if the
    *    index is an existing one (so, an existing key of this array)
    * 2. It initializes the array with null values. This way the caching-functions
    *    will be able to tell if content has not been loaded or if it has been loaded
    *    with empty data
    *
    * @see parent::contentArr
    * @see parent::__construct()
    *
    * @return void
    */
   
protected function setContentArr()
   {
      
$this->contentArr = array(
         
"stream"   => null,
         
"status"   => null,
         
"history"   => null,
         );
   }

   
/**
    * Other methods which are specific for this internet-radiostation
    */

   /**
    * Getter for $parseAudioInfo
    *
    * @see $parseAudioInfo
    *
    * @return boolean
    */
   
public function getParseAudioInfo()
   {
      return 
$this->parseAudioInfo;
   }
   
/**
    * Setter for $parseAudioInfo
    *
    * @param boolean $bool
    *
    * @see $parseAudioInfo
    *
    * @return void
    */
   
public function setParseAudioInfo($bool)
   {
      
$this->parseAudioInfo = (bool) $bool;
   }

   
/**
    * Loads and parses the contents of the status-page
    *
    * @param String $page         The location of the page with the content. This is the
    *                         [page] part in http://[domain]:[port]/[page]
    * @param String $streamTitle   The title of the stream we're interested in
    *                         If not passed, we will retrieve it either from the cache
    *                         or load it from the server-information page
    *
    * @return boolean            True if status could be loaded properly
    */
   
protected function loadStatus($page null$streamTitle null)
   {
      
// if the streamtitle is not given, we retrieve it from the server-information
      
if (is_null($streamTitle))
      {
         
// if the field is not yet or empty, we load the data first
         
if (!isset($this->fields['Stream Title']) || empty($this->fields['Stream Title']))
         {
            
$this->parseFields();
            if (!isset(
$this->fields['Stream Title']))
            {
               
// without streamtitle, we cannot load the status
               
throw new InternetRadio_Exception("Stream Title couldn't be found in contents");
            }
         }
         
$streamTitle $this->fields['Stream Title'];
      }

      
// retrieving the contents of the status page
      
$contents $this->getStatusContents($page);

      if (
False !== strpos($contents"<table"))
      {
         
// the status page contains lots of html-tables with information about the various
         // available streams on this server
         
$tables explode("<table"$contents);
         foreach (
$tables AS $table)
         {
            
// check if the current table is the stream we're interested in
            
if (preg_match("/(<td(.*)>".$streamTitle."<\/td>)/"$table))
            {
               
// this is the one, parsing it's data now
               
$rows explode("<tr>"$table);
               foreach (
$rows AS $row)
               {
                  if (
preg_match_all("/<td.*>(.*)<\/td>/siU"$row$matches))
                  {
                     
$type trim(str_replace(":"""$matches[1][0]));
                     
$value $matches[1][1];
                     if (
$type == "Current Song" && isset($this->fields['Current Song']))
                        
$this->fields['Current Song'] = $value;
                     elseif (
$type == "Current Listeners" && isset($this->fields['Current Listeners']))
                        
$this->fields['Current Listeners'] = $value;
                     elseif (
$type == "Peak Listeners" && isset($this->fields['Listener Peak']))
                        
$this->fields['Listener Peak'] = $value;
                     elseif (
$type == "Mount started" && isset($this->fields['Mount Started']))
                        
$this->fields['Mount Started'] = $value;
                  }
               }
               return 
True;
            }
         }
      }
      else
      {
         throw new 
Exception($page." is not a valid status page or unreachable");
      }
      return 
False;
   }

   
/**
    * Wrapper for parent::getContents() with the first parameter filled correctly
    *
    * @param String $page      The location of the page with the content. This is the
    *                      [page] part in http://[domain]:[port]/[page]
    *
    * @see getContents()
    *
    * @return String
    */
   
protected function getStatusContents($page null)
   {
      return 
$this->getContents("status"$page);
   }
}

/***
 * file: Steamcast.php
 */

/**
 * @see InternetRadio_Abstract
 */
require_once "Abstract.php";

/**
 * Class that can parse and display the information of Steamcast Servers
 */
class InternetRadio_Steamcast extends InternetRadio_Abstract
{
   
/**
    * Used by getServerType() to show what kind of internet-radiostations this class
    * can handle
    */
   
const SERVER_TYPE   "Steamcast";

   
/**
    * The following constants all serve the function which parses the public information
    * pages. This way, it is easy to change in case the format of these pages change in
    * the future
    */
   /**
    * Determines from which point we start searching for the fields with server information
    * @see parseFields();
    */
   
const OFFSET      "about.html";
   
/**
    * This is how the html-table with the track-history starts
    * @see parseHistory();
    * @see TABLE_END
    */
   
const TABLE_START   "<table align=center>";
   
/**
    * End this is how it ends
    * @see parseHistory();
    * @see TABLE_START
    */
   
const TABLE_END      "</table>";

   
/**
    * All the possible fields with information about the stream/server that can be loaded
    * By default, this array is assigned to the $fields (see parent class) array. But, the
    * $fields array can also be set with setFields() in order to display a particular set
    * of fields
    *
    * @var array
    *
    * @see $fields
    * @see setFields()
    */
   
protected $defaultFields = array(
               
"Stream Status" => "n/a",
               
"Stream Name" => "n/a",
               
"Stream Url" => "n/a",
               
"Genre" => "n/a",
               
"Content Type" => "n/a",
               
"Stream Bitrate" => "n/a",
               
"Listeners" => "n/a",
               
"Average Listening Time" => "n/a",
               
"Overall Listeners" => "n/a",
               
"Peak Listeners" => "n/a",
               
"Tune-ins" => "n/a",
               
"5min Tune-ins" => "n/a",
               
"Now Playing" => "n/a",
               
"Active Url" => "n/a",
               );
   
/**
    * The default streamChunks which are loaded when setStreamChunks() is called without
    * argument (which is done in the constructor of the Abstract parent classs)
    *
    * @var array
    */
   
private $defaultStreamChunks = array(
      
"history"   => -1,
      
"status"   => -1
      
);
   
/**
    * By default it will be assigned $defaultStreamChunks as value.
    *
    * @see setStreamChunks()
    *
    * @var array
    */
   
protected $streamChunks;

   
/**
    * The default pageMaps which is loaded when setPageMap() is called without
    * argument (which is done in the constructor of the Abstract parent classs)
    *
    * @var array
    */
   
private $defaultPageMap = array(
      
"stream"   => "/live.mp3",
      
"history"   => "/played.html",
      
"status"   => "/",
      );
   
/**
    * By default it will be assigned $defaultPageMap as value.
    *
    * @see setPageMap()
    *
    * @var array
    */
   
protected $pageMap;

   
/**
    * Implementation of abstract parent methods
    */

   /**
    * Returns the type of InternetRadiostation this class can handle
    *
    * @return String
    */
   
public function getServerType()
   {
      return 
self::SERVER_TYPE;
   }

   
/**
    * When called without argument it sets the default pageMap.
    * The page map links
    *
    * @see parent::setPageMap()
    *
    * @param array $data   Array containing the mapping
    *
    * @return void
    */
   
protected function setPageMap($data null)
   {
      if (
is_null($data))
         
$data $this->defaultPageMap;
      
$this->pageMap $data;
   }

   
/**
    * When called without argument it sets the default streamChunks.
    *
    * @see parent::setStreamChunks()
    *
    * @param array $data   Array containing the mapping
    *
    * @return void
    */
   
protected function setStreamChunks($data null)
   {
      if (
is_null($data))
         
$data $this->defaultStreamChunks;
      
$this->streamChunks $data;
   }

   
/**
    * Retrieves the page with the information about the server/stream, parses it and puts the
    * parsed data into the appropriate fields
    *
    * @see getStreamContents()
    * @see loadStatus()
    */
   
public function parseFields($page null)
   {
      if (
$contents $this->getStreamContents($page))
      {
         
$very_first_pos stripos($contentsself::OFFSET);
         foreach (
$this->defaultFields AS $item => $value)
         {
            
$first_pos      stripos($contents$item$very_first_pos);
            
$line_start      strpos($contents"</td>"$first_pos);
            
$line_end      strpos($contents"</tr>"$line_start) + 5;
            
$difference      $line_end $line_start;
            
$line         substr($contents$line_start$difference);
            
$this->fields[$item]   = strip_tags($line);

            if (
$this->createHyperlinks && strtolower(substr($item, -3)) == "url")
            {
               
$this->fields[$item] = "<a href=\"".$this->fields[$item]."\">".$this->fields[$item]."</a>";
            }
         }

         
$this->loadStatus();
      }
   }

   
/**
    * This function will be called from the parent's constructor
    * By setting the contentArr, it serves two purposes:
    * 1. The keys of the array can later be used in validations that check if the
    *    index is an existing one (so, an existing key of this array)
    * 2. It initializes the array with null values. This way the caching-functions
    *    will be able to tell if content has not been loaded or if it has been loaded
    *    with empty data
    *
    * @see parent::contentArr
    * @see parent::__construct()
    *
    * @return void
    */
   
protected function setContentArr()
   {
      
$this->contentArr = array(
         
"stream"   => null,
         
"status"   => null,
         
"history"   => null,
         );
   }

   
/**
    * overloaded methods
    */

   /***
    * Overloads the parents function in order to cut of part of the output which is irrelevant
    *
    * @var String $page      Optional. The page where we can find the content.
    *                     If none is given, the $pageMap variable will be used
    *
    * @see parent::loadStreamContents()
    *
    * @return void
    */
   
protected function loadContents($index$page null)
   {
      
parent::loadContents($index$page);
      if (!empty(
$this->contentArr['stream']))
      {
         
preg_match("/(Content-Type:)(.*)/i"$this->contentArr['stream'], $matches);
         if (
count($matches) > 0)
         {
            
$contentType trim($matches[2]);
            if (
$contentType != "text/html")
            {
               throw new 
Exception("This is not a valid shoutcast-stream");
            }
         }
      }
   }

   
/**
    * Other methods which are specific for this internet-radiostation
    */

   /**
    * Retrieves the history of the played tracks and returns it using the choosen
    * (or default) template
    *
    * @param unknown_type $page            The page where the information about
    *                      the stream can be found. If null, the
    *                      default will be used. See
    * @param InternetRadio_Output_Interface $template   The template that has to be used when
    *                      upon returning the data
    *
    * @return mixed   The output as rendered by the template object. In most cases
    *         this will be a string, but it could just as easily be another
    *          type (for example, InternerRadio_Output_Php returns an array)
    */
   
public function getHistoryInfo($page "/played.html"InternetRadio_Output_Interface $template null)
   {
      if (empty(
$this->tracks))
      {
         try {
            
$this->parseHistory($page);
         } catch (
InternetRadio_Exception $e) {
            
$this->error $e->getMessage();
            switch (
$this->exceptionReporting)
            {
               case 
self::EXCEPTION_THROW :
                  throw 
$e;
                  break;
               case 
self::EXCEPTION_SHOW :
                  
$this->tracks = array(array("error"$e->getMessage()));
                  break;
               case 
self::EXCEPTION_HIDE :
               default :
                  
$this->tracks = array(array("error""failed to load stream"));
                  break;
            }
         }
      }
      return 
$this->getInfo($this->tracks$template);
   }
   
/**
    * Same as getHistoryInfo($page, InternetRadio_Output_Html_Table()), but without the
    * need to pass the template object as an argument
    *
    * @param String $page
    *
    * @see getHistoryInfo()
    *
    * @return mixed
    */
   
public function getHistoryInfoAsHtml($page "/played.html")
   {
      require_once 
dirname(__FILE__)."/Output/Html/Table.php";
      return 
$this->getHistoryInfo($page, new InternetRadio_Output_Html_Table());
   }
   
/**
    * Same as getHistoryInfo($page, InternetRadio_Output_Php()), but without the
    * need to pass the template object as an argument
    *
    * @param String $page
    *
    * @see getHistoryInfo()
    *
    * @return mixed
    */
   
public function getHistoryInfoAsPhp($page "/played.html")
   {
      require_once 
dirname(__FILE__)."/Output/Php.php";
      return 
$this->getHistoryInfo($page, new InternetRadio_Output_Php());
   }
   
/**
    * Same as getHistoryInfo($page, InternetRadio_Output_Xml()), but without the
    * need to pass the template object as an argument
    *
    * @param String $page
    *
    * @see getHistoryInfo()
    *
    * @return mixed
    */
   
public function getHistoryInfoAsXml($page "/played.html")
   {
      require_once 
dirname(__FILE__)."/Output/Xml.php";
      return 
$this->getHistoryInfo($page, new InternetRadio_Output_Xml());
   }

   
/**
    * Retrieves the page with the information about the history of the played tracks,
    * parses it and craetes an array of tracks out of the parsed data
    *
    * @param String $page      The location of the page with the content. This is the
    *                      [page] part in http://[domain]:[port]/[page]
    *
    * @return void
    */
   
protected function parseHistory($page "/played.html")
   {
      
$html $this->getHistoryContents($page);

      
$fromPos   stripos($htmlself::TABLE_START);
      
$toPos      stripos($htmlself::TABLE_END$fromPos);
      
$tableData   substr($html$fromPos, ($toPos-$fromPos));
      
$lines      explode("<tr>"$tableData);
      
$tracks = array();
      
$c 0;

      foreach (
$lines AS $line)
      {
         
$info explode ("</font></small>"$line);

         if (isset(
$info[1]))
         {
            
$time trim(strip_tags($info[0]));

            
$this->tracks[$c]['time'] = $time;
            
$this->tracks[$c++]['track'] = trim(strip_tags($info[1]));
         }
      }
      if (
count($this->tracks) > 0)
      {
         if (isset(
$this->tracks[0]))
            
$this->tracks[0]['time'] = str_replace("Last 20 Played"""$this->tracks[0]['time']);
      }
   }

   
/**
    * Loads the contents of the information about the history of the played tracks
    *
    * @param String $page      The location of the page with the content. This is the
    *                      [page] part in http://[domain]:[port]/[page]
    *
    * @throws InternetRadio_Exception
    *
    * @see loadContents()
    *
    * @return void
    */
   
protected function loadHistoryContents($page "/played.html")
   {
      
$this->loadContents("history"$page);
   }

   
/**
    * Wrapper for parent::getContents() with the first parameter filled correctly
    *
    * @param String $page      The location of the page with the content. This is the
    *                      [page] part in http://[domain]:[port]/[page]
    *
    * @see getContents()
    *
    * @return String
    */
   
protected function getHistoryContents($page "/played.html")
   {
      return 
$this->getContents("history"$page);
   }

   
/**
    * Loads and parses the contents of the status-page
    *
    * @param String $page      The location of the page with the content. This is the
    *                      [page] part in http://[domain]:[port]/[page]
    *
    * @return void
    */
   
protected function loadStatus($page null)
   {
      
$contents $this->getStatusContents($page);

      
$very_first_pos stripos($contentsself::OFFSET);

      foreach (
$this->defaultFields AS $item => $value)
      {
         if (!isset(
$this->fields[$item]) || empty($this->fields[$item]) || $this->fields[$item] == $this->defaultFields[$item])
         {
            
$first_pos      stripos($contents$item$very_first_pos);
            
$line_start      strpos($contents"</td>"$first_pos);
            
$line_end      strpos($contents"</tr>"$line_start) + 5;
            
$difference      $line_end $line_start;
            
$line         substr($contents$line_start$difference);
            
$this->fields[$item]   = strip_tags($line);
         }
      }
   }

   
/**
    * Wrapper for parent::getContents() with the first parameter filled correctly
    *
    * @param String $page      The location of the page with the content. This is the
    *                      [page] part in http://[domain]:[port]/[page]
    *
    * @see getContents()
    *
    * @return String
    */
   
protected function getStatusContents($page null)
   {
      return 
$this->getContents("status"$page);
   }
}

/***
 * file: Exception.php
 */

/**
 * Exception class for exceptions that occur within the InternetRadio classes
 */
class InternetRadio_Exception extends Exception
{

}

/***
 * file: Output/Interface.php
 */

/**
 * All output-templates must implement this interface to ensure that the render-method is available.
 * This way, any InternetRadio_Abstract class can set an object of this type as template and call
 * the render-method with the data as argument to generate the output
 */
interface InternetRadio_Output_Interface
{
   
/**
    * Implemenation of this method must parse the passed data and convert it to the desired output
    * and then return that output
    *
    * @return String
    */
   
public function render(array $data);
}

/***
 * file: Output/Tag.php
 */

/**
 * @see InternetRadio_Output_Interface
 */
require_once dirname(__FILE__)."/Interface.php";

/**
 * This class serves as the base for all other InternetRadio_Output classes which generate output with tags
 * For example, output in html, xml, etc.
 */
class InternetRadio_Output_Tag implements InternetRadio_Output_Interface
{
   
/**
    * Top-part of the output, which is not variable
    *
    * @see getHeader()
    * @see setHeader()
    *
    * @var String
    */
   
protected $_header;

   
/**
    * Bottom-part of the output, which is not variable
    *
    * @see getFooter()
    * @see setFooter()
    *
    * @var String
    */
   
protected $_footer;

   
/**
    * Body-part of the output, which Is variable and occurs 1 + n times
    *
    * @see getRow()
    * @see setRow()
    *
    * @var String
    */
   
protected $_row;

   
/**
    * This indicates that the data (to be rendered) is passed as key-value pairs
    * (in which the key-part has significance too and needs to be shown)
    *
    * @var boolean
    */
   
protected $_keyValue;

   
/**
    * A positive value indicates that we need to skip a few lines before starting the output
    *
    * @see setOffset()
    *
    * @var integer
    */
   
protected $_offset;

   
/**
    * A positive value indicates we need to limit the amount of rows in the output
    * -1 indicates there is no limit (and so does null)
    *
    * @see setLimit()
    *
    * @var integer
    */
   
protected $_limit;


   
/**
    *
    *
    * @return String
    */
   
public function render(array $data)
   {
      
// initiate variable that will hold the variable-data
      
$body "";
      
// initiate variable that will count the amount of rows we're parsing
      
$count 0;
      
// initiate variable that will count the amount of rows we're adding as variable data
      
$added 0;
      
// calculating the real limit we're gonna use
      
if (!is_null($this->_limit) && $this->_limit 0)
      {
         
// setting the limit to 'limitless'
         
$limit count($data) - (int) $this->_offset $this->_limit;
      }
      else
      {
         
$limit $this->_limit;
      }

      
$this->sanitizeData($data);

      
// looping through the data-array
      
foreach ($data AS $key => $value)
      {
         
// check if we are within the boundaries defined by the offset & limit
         
if ( (is_null($this->_offset) || $count >= $this->_offset) && (is_null($limit) || $added $limit))
         {

            
// if true, we want to display both key and value
            
if ($this->_keyValue)
            {
               
$body .= sprintf($this->_row$key$value);
            }
            else
            {
               
$body .= sprintf($this->_row$value);
            }
            
// row has been added, we increment
            
$added++;
         }
         else
         {
            echo 
"<!-- ".$count." is outside boundaries; offset[".$this->_offset."], limit[".$limit."] //-->\n";
         }
         
// row has been parsed, we increment
         
$count++;
      }
      
// returning the rendered output
      
return $this->getHeader().$body.$this->getFooter();
   }

   protected function 
sanitizeData(&$data)
   {
      
$newData = array();
      foreach (
$data AS $key => $value)
      {
         
// check if the current value is an array itself
         // and in that case we extract it's key and value
         
if (is_array($value))
         {
            list(
$key$value) = array_values($value);
         }
         
$newData[$key] = $value;
      }
      
$data $newData;
   }

   
/**
    * @see $_limit
    * @return void
    */
   
public function setLimit($limit)
   {
      
$this->_limit = (int) $limit;
   }

   
/**
    * @see $_offset
    * @return void
    */
   
public function setOffset($offset)
   {
      
$this->_offset = (int) $offset;
   }

   
/**
    * @see $_header
    * @return String
    */
   
public function getHeader()
   {
      return 
$this->_header;
   }
   
/**
    * @see $_header
    * @return void
    */
   
public function setHeader($str)
   {
      
$this->_header $str;
   }

   
/**
    * @see $_footer
    * @return String
    */
   
public function getFooter()
   {
      return 
$this->_footer;
   }
   
/**
    * @see $_footer
    * @return void
    */
   
public function setFooter($str)
   {
      
$this->_footer $str;
   }

   
/**
    * @see $_row
    * @return String
    */
   
public function getRow()
   {
      return 
$this->_row;
   }
   
/**
    * @see $_row
    * @return void
    */
   
public function setRow($str)
   {
      if (
False === strpos($str"%s"))
      {
         throw new 
Exception("There are no variables in the row of your table. Please use %s as variable");
      }
      
$this->_keyValue preg_match("/%s.*%s/s"$str);
      
$this->_row $str;
   }
}
?>

download

=[Disclaimer]=     © 2005-2018 Excudo.net