Skip to content

Ch-Ch-Changes!

There have been a few changes for me lately. In the computer department anyway.

Change 1. CakePHP to Ruby on Rails
I was hesitant to make this switch because I was familiar with PHP and not at all familiar with Ruby. I wished I had switched sooner. Rails is very easy to learn, due to the simplicity of the Ruby language and the involvement of the community. I now see Rails as the first choice for any app; CakePHP is a good alternative when there is a PHP-only environment.

Change 2. Windows to OS X
With the switch to Rails, I began watching tons of screencasts to learn. I noticed that every single one was created on a Mac. I was having tons of trouble with my computer at the time, and I started wanting a Mac. I loaded up OSX86 and I was addicted. My XP machine died, and I made up my mind that my next computer would be a Mac.
At risk of sounding fanboi-ish…I love it. It is so stable and quick. No BS.
Terminal pwns Command Prompt.

Change 3. Dreamhost to Webfaction
I love Dreamhost for CakePHP development. Running Rails on it is not bad. However, trying to run EDGE Rails is hell. Webfaction is spectacular. I think I am paying less each month. The control panel is beautiful and genius. You can set up a PHP app on “domain.com/blog”, a Rails app on “domain.com/forum” and a static page on “domain.com/photos” in 2 minutes. They are no joke.

Lastly, I would like to add that I was peer pressured into all of these choices by Ryan.
Apparently you do get wiser with age, eh? hehehe

Shoutcast on iPhone (continued)

Last week, Michael of dnbradio.com commented on my post about Streaming music to the iPhone. I emailed him, asking him about how he implemented my code. He told me that he implemented the header faking in his iPhone interface for DnbRadio. I am pleased with the fact that I was able to help him out! The streaming works beautifully on both WiFi AND EDGE!

Read about his project, then head over to http://www.dnbradio.com/iphone using your iPhone to test out his implementation out for yourself.

Streaming Shoutcast on the iPhone

UPDATE 11/5/07 After inserting that first line of code

set_time_limit(10 * 60);

I seem to have less choppy music. It turns out my server only allowed any PHP script to run for only 60 seconds. This disconnected Safari, and initiated another request from it…causing the overlapping music problem I explained below. I haven’t had time to fully test it, but in theory setting a longer time limit would tone this problem down quite a bit. Ideally, you’d give Safari every byte it asked for, and not bail out because the script is taking too long.


The first post in a brand new blog!

I frequently listen to a certain online radio station, Cerritos All Stars. Since I’m always around the house listening to music on my iPhone, I wanted a way to listen to the station instead of just my music library. Of course I started Google searching ASAP. I found several things:

  • There is no Shoutcast client for the iPhone yet
  • iPhone’s Safari can play mp3 files which are located online, but not Shoutcast streams

On a Macrumors forum, some people were talking about tricking Safari into thinking that a Shoutcast stream is just a plain mp3 file on a web server. The method they tried was using PHP to fake some HTTP headers: Content-type, Content-Length. After that, just read bytes from the Shoutcast server and pass them on to Safari. This almost works. Safari brings starts up the built-in Quicktime player and seems ready to play, but instead the “play” symbol with a strikeout line through it shows up.

Further on in the forum thread, someone mentioned that Safari downloads the mp3 in chunks. This is to enable simultaneous playing and buffering. This is why the aforementioned method did not work.

  • Safari first requests the script
  • The script sends mp3 type headers, Safari likes them and starts up its Quicktime player
  • Safari asks for bytes 0 and 1 of the mp3, to test for file-resuming capabilities
  • The script sends out the same headers
  • Safari thinks, “impostor!”

So after doing a little reading about HTTP responses and all that good stuff, I realized that the PHP script needed to be a little more flexible. It needed to pretend to be resuming a file transfer when Safari asked it to. So a little modifying, and bingo.

  • Safari first requests the script
  • The script sends mp3 type headers, Safari likes them and starts up its Quicktime player
  • Safari asks for bytes 0 and 1 of the mp3, to test for file-resuming capabilities
  • The script sends HTTP 206 and related headers
  • The script sends two bytes of the Shoutcast stream
  • Safari thinks, “sweet, gimme more!”
  • Safari asks for bytes 0 - x
  • The script sends more bytes of the stream
  • Quicktime plays bytes of the stream

The quick and dirty code:

set_time_limit(10 * 60); //this could take a while, allowing 10 minutes. just added recently see UPDATE at the top of post

$bytes_to_send = 480000 * 130; //stream about about 2 hours of music

$headers = http_get_request_headers(); //get the HTTP request headers safari has sent

//if safari is only asking for a portion of the "mp3"
if (isset($headers['Range'])) {
	$exploded_range = explode('=', $headers['Range']);
	$limits = explode('-', $exploded_range[1]);
	$length = ($limits[1] - $limits[0]) + 1; //the content length
	$content_range = 'bytes ' . $limits[0] . '-' . $limits[1]; //the content range

	//send fake HTTP headers to safari, telling it that we're sending only the portion of the "mp3" it asked for
	header('HTTP/1.1 206 Partial Content');
	header('Accept-Ranges: bytes');
	header('Content-Length: ' . $length);
	header('Content-Range: ' . $content_range . '/' . $bytes_to_send);
	header('Content-type: audio/mpeg');

	//open the stream to the shoutcast server, set as resource $fp
	$fp = fsockopen("stream.cerritosallstars.com", "80", $errno, $errstr, 30) or die("Unable to connect to server!");

	//HTTP commands that will initiate the shoutcast server sending stream data
	$buf = "GET / HTTP/1.0\r\nIcy-MetaData:0\r\n\r\n";

	//send HTTP commands in string $buf to stream $fp
	fwrite($fp, $buf);
	//get next line from stream
	$buf = fgets($fp, 1024);		

	//get next few lines and discard them, this is only
	//shoutcast data that would sound like noise if iphone played them
	$buf = fgets($fp, 1024);
	$buf = fgets($fp, 1024);
	$buf = fgets($fp, 1024);
	$buf = fgets($fp, 1024);
	$buf = fgets($fp, 1024);
	$buf = fgets($fp, 1024);
	$buf = fgets($fp, 1024);
	$buf = fgets($fp, 1024);

	//break if EOF
	if ($buf == "\r\n") {
		break;
	}

	$bytes_sent = 0;

	//while pointer is not at EOF, and not too many bytes are sent...
	while (!feof($fp) AND ($bytes_sent < $length)) {
		//read 1 byte of stream
		$buf = fread($fp, 1);

		//output byte to iphone;
		echo $buf;
		$bytes_sent++;
	}
	fclose($fp);
	exit();
}

//else, it is the initial request. safari is asking for the whole "mp3", and seeing how big it is ($bytes_to_send)
else {
	header('Accept-Ranges: bytes');
	header('Content-Length: ' . $bytes_to_send);
	header('Content-type: audio/mpeg');

	echo 'blah';
	exit();
}
exit();

So in the end, music played. However, this is a pretty sketchy way of doing things. Because of the way Quicktime buffers, seconds of music are not heard, and seconds of music are repeated. Know what I mean? Every request that happens at the same time will be filled with the same byte. The script can definitely get bytes from the present fine but when Safari requests bytes from the future, it still gets bytes from the present. Here is another way to view the issue.

  • Safari asks for bytes 1 - 3
  • Script serves bytes 1, 2, 3 and Safari plays them
  • While playing byte 2, Safari asks for bytes 4 - 6
  • CRAP, byte 3 will be the same as byte 4

A few seconds of repeated music are heard. I can live with this, but theres no way any final solution could have this problem.

I would also like to point out that PHP and Apache have been ported to the iPhone. There might be a way to run the script locally on the iPhone, eliminating the need for another server and extra bandwidth. And another reminder, this would only be ideal on WiFi…iPhone can’t make or receive calls while using EDGE.

If anyone has an elegant way of listening to Shoutcast on iPhone, please leave a comment!