I have recently come to love Drupal. A content management system that easily allows you to do all sorts of fancy things with web pages without doing too much coding. I thought this would be great for my online radio station I enjoy bragging about that I run with a cohort. The general idea is to have the XML stream that the Shoutcast radio station produces read by the RSS reader in Drupal and then be able to have nodes for each of the songs that is played so there can be commenting, voting, etc. All seems well and easy right?
UPDATE: April 19th, 2010
Although the information below might still be very pertinent to your situation, it is not at all for my radio station. So some of the links might not work. But the concepts and ideas should still work.
Problem 1: Poor XML Formatting
Your shoutcast radio station will provide you with a link like http://radiostation:9999/admin.cgi?pass=password&mode=viewxml&page=0 which will give you all of the juicy information that you want to know about your radiostation in what appears to be an XML format. Well the FeedAPI drupal module will ask you for an RSS feed and if you try to give it this URL, it just wont work.
So the plan is to make the XML file from the URL above be made available as a correctly formatted RSS feed. There is a lot of information in the XML file but for this exercise, I am only concerned with the song history which will tell me the currently playing song and the last ten. So every minute I have a php script run which will read the XML url and then produce a history.rss file. Here are the contents…
<?php
// Used to convert the shoutcast XML file into RSS readable feeds
// A cron job will run every minute to keep this up to date.
//
// I have no clue about RSS by the way
// shoutcast info
$servername = 'shoutcastradiourl';
$port = 'shoutcast port';
$password = 'shoutcast password';
$file_path = 'path to where the file will be written';
// initialize
$xml = '';
$fp = fsockopen($servername, $port, $errno, $errstr, 10);
If (!$fp) {
$error = "$errstr ($errno)";
return(0);
} else {
fputs($fp, "GET /admin.cgi?pass=".$password."&mode=viewxml HTTP/1.0\r\n");
fputs($fp, "User-Agent: Mozilla\r\n\r\n");
while (!feof($fp)) {
$xml .= fgets($fp, 512);
}
fclose($fp);
}
// Shoutcast adds this ugly string at the beginning of the XML file for some reason
$kill = '<!DOCTYPE SHOUTCASTSERVER [<!ELEMENT SHOUTCASTSERVER (CURRENTLISTENERS,PEAKLISTENERS,MAXLISTENERS,REPORTEDLISTENERS,AVERAGETIME,SERVERGENRE,SERVERURL,SERVERTITLE,SONGTITLE,SONGURL,IRC,ICQ,AIM,WEBHITS,STREAMHITS,STREAMSTATUS,BITRATE,CONTENT,VERSION,WEBDATA,LISTENERS,SONGHISTORY)><!ELEMENT CURRENTLISTENERS (#PCDATA)><!ELEMENT PEAKLISTENERS (#PCDATA)><!ELEMENT MAXLISTENERS (#PCDATA)><!ELEMENT REPORTEDLISTENERS (#PCDATA)><!ELEMENT AVERAGETIME (#PCDATA)><!ELEMENT SERVERGENRE (#PCDATA)><!ELEMENT SERVERURL (#PCDATA)><!ELEMENT SERVERTITLE (#PCDATA)><!ELEMENT SONGTITLE (#PCDATA)><!ELEMENT SONGURL (#PCDATA)><!ELEMENT IRC (#PCDATA)><!ELEMENT ICQ (#PCDATA)><!ELEMENT AIM (#PCDATA)><!ELEMENT WEBHITS (#PCDATA)><!ELEMENT STREAMHITS (#PCDATA)><!ELEMENT STREAMSTATUS (#PCDATA)><!ELEMENT BITRATE (#PCDATA)><!ELEMENT CONTENT (#PCDATA)><!ELEMENT VERSION (#PCDATA)><!ELEMENT WEBDATA (INDEX,LISTEN,PALM7,LOGIN,LOGINFAIL,PLAYED,COOKIE,ADMIN,UPDINFO,KICKSRC,KICKDST,UNBANDST,BANDST,VIEWBAN,UNRIPDST,RIPDST,VIEWRIP,VIEWXML,VIEWLOG,INVALID)><!ELEMENT INDEX (#PCDATA)><!ELEMENT LISTEN (#PCDATA)><!ELEMENT PALM7 (#PCDATA)><!ELEMENT LOGIN (#PCDATA)><!ELEMENT LOGINFAIL (#PCDATA)><!ELEMENT PLAYED (#PCDATA)><!ELEMENT COOKIE (#PCDATA)><!ELEMENT ADMIN (#PCDATA)><!ELEMENT UPDINFO (#PCDATA)><!ELEMENT KICKSRC (#PCDATA)><!ELEMENT KICKDST (#PCDATA)><!ELEMENT UNBANDST (#PCDATA)><!ELEMENT BANDST (#PCDATA)><!ELEMENT VIEWBAN (#PCDATA)><!ELEMENT UNRIPDST (#PCDATA)><!ELEMENT RIPDST (#PCDATA)><!ELEMENT VIEWRIP (#PCDATA)><!ELEMENT VIEWXML (#PCDATA)><!ELEMENT VIEWLOG (#PCDATA)><!ELEMENT INVALID (#PCDATA)><!ELEMENT LISTENERS (LISTENER*)><!ELEMENT LISTENER (HOSTNAME,USERAGENT,UNDERRUNS,CONNECTTIME, POINTER, UID)><!ELEMENT HOSTNAME (#PCDATA)><!ELEMENT USERAGENT (#PCDATA)><!ELEMENT UNDERRUNS (#PCDATA)><!ELEMENT CONNECTTIME (#PCDATA)><!ELEMENT POINTER (#PCDATA)><!ELEMENT UID (#PCDATA)><!ELEMENT SONGHISTORY (SONG*)><!ELEMENT SONG (PLAYEDAT, TITLE)><!ELEMENT PLAYEDAT (#PCDATA)><!ELEMENT TITLE (#PCDATA)>]>';
// pick out the ugly string
list($dirty, $clean_before) = explode($kill, $xml);
// Gotta remove ampersands apparently for proper RSS feeding
$clean_before = str_replace("&", "&", $clean_before);
// Lets parse this clean XML and create some RSS files
$history = new SimpleXMLElement($clean_before);
// Create the RSS header
$history_file = '<?xml version="1.0" encoding="utf-8"?>'."\n";
$history_file .= '<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:shoutcast="http://roachclipradio.com">'."\n";
$history_file .= "\t<channel>\n";
$history_file .= "\t\t<title>Roachclipradio Last 10 Songs</title>\n";
$history_file .= "\t\t<link>http://roachclipradio.com</link>\n";
$history_file .= "\t\t<description>These are the last then songs played on roachclipradio.com</description>\n";
$history_file .= "\t\t<atom:link href=\"http://roachclipradio.com/history.rss\" rel=\"self\" type=\"application/rss+xml\" />\n";
// Create the RSS items
foreach ($history->SONGHISTORY->SONG as $song) {
$history_file .= "\t\t<item>\n";
$history_file .= "\t\t\t<title>".$song->TITLE."</title>\n";
$history_file .= "\t\t\t<description>".$song->PLAYEDAT."</description>\n";
$history_file .= "\t\t\t<link>http://roachclipradio.com/".$song->TITLE."</link>\n";
$history_file .= "\t\t\t<guid isPermaLink=\"false\">".$song->PLAYEDAT."</guid>\n";
//$history_file .= "\t\t\t<shoutcast:file>file.mp3</shoutcast:file>\n";
$history_file .= "\t\t</item>\n";
}
// Create the RSS footer
$history_file .= "\t</channel>\n";
$history_file .= "</rss>\n";
// Lets create this file
$myFile = $file_path . "history.rss";
$fh = fopen($myFile, 'w') or die("can't open file");
fwrite($fh, $history_file);
fclose($fh);
?>
The script works like this:
- Read XML file
- Remove ugly string in front of file and replace any bogeys
- Parse into XML
- Create RSS header
- Loop over wanted information and create RSS items
- Close RSS
- Write to file
So now if your produced file is web readable, you can check it out online and even validate it with your favorite RSS validator. See http://roachclipradio.com/history.rss to see the output.
Problem 2: Using FeedAPI to read the stream
Now you have a proper RSS stream to pass to FeedAPI, it is dead easy to create a stream. Check out the screencasts on their webpage to see how this can be done. I created a content type called “song” which is created for each new item that comes into the stream. The only information that you have from Shoutcast past the name of the song is the unix time stamp of when it was played. Feel free to use the FeedAPI Mapper to get this information, and maybe more into your newly created node.
Problem 3: Auto refreshing stream content
There are some very flashy AJAX solutions to get your Shoutcast information showing on your webpage. These can be found all over the shoutcast forums and in fact the latest sc_trans shoutcast comes with some excellent examples. But to use these would require too much Drupal backend coding and I really did not want to adventure there.
Download the Block Refresh module. You will need to look for the link to some other webpage to get the one for Drupal 6. After installing this, you will get an option to have blocks defined in the block editor to have a definable refresh period.
Run the Drupal cron every minute. This may seem excessive but this will force Drupal to refresh the feed that you have created. This in combination with the block auto refresh provides enough live response for a radio station in my opinion.
I found that some themes would not allow the block refresh to work while in the header. You need to double check you have the latest version to make sure it still works with Views.
Future Changes
Have the XML creating script do a lot more. It would be nice if I could pass the path to the filename of the song into the RSS feed and then have that mapped in the node. This would allow me to create streaming buttons for each object so they could be listened to whenever and not just from the Shoutcast stream. I actually tried this but the problem is that Shoucast is using the id3 tags to display the song title which comes out as <artist> – <album> – <title> but if an information bit is missing, it will be omitted. Due to the messy nature of my mp3s, it is really hard to compare a given filename to its Shoutcast generated filename. The getID3 library is also very slow for this sort of thing.
Extend the FeedAPI parser to allow for a shoutcast schema or something so that I can pass more information than just the standard RSS item information points.
Conclusion
Don’t you love when you have searched for hours and all your search terms are just showing previously selected links? Well that is what happened when I was trying to integrate my Shoutcast station with Drupal. Hopefully this can get you a little closer to having the two more closely blended. Check out an example at http://roachclipradio.com to see it all in action.




Who is a nerd now?
I am completly Agree with you! Great Article!