I recently discovered that Apache seemingly supports streaming mp3 files. You simply drop an mp3 into the htdocs directory, point Winamp or Window Media Player to it, and it streams the file. That's all fine and dandy, but I want to keep my mp3 files outside the htdocs directory. Now I need a script to handle the file, send the appropriate headers, and then read the file stream directly to the client requesting it.
I logged the HTTP requests made by Winamp and Windows Media Player, and they look something like this:
Accept: /
Connection: close
Host: music.greysphere.net
Icy-MetaData: 1
User-Agent: WinampMPEG/5.0
Accept: /
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Host: music.greysphere.net:81
User-Agent: NSPlayer/10.0.0.3646 WMFSDK/10.0
So my script responds by sending some headers, then the actual audio data, like so:
define("MUSIC_DIR", "E:\\Music");
define("SUB_DIR", $_GET['dir']);
define("FILE", $_GET['file']);
$song = str_replace("\\\\'", "'", MUSIC_DIR . urldecode(SUB_DIR) . urldecode(FILE));
// -- send the headers --
header('Content-Type: audio/mpeg');
header('Content-Length: '. filesize($song));
header('Accept-Ranges: bytes');
// -- send audio data --
$handle = fopen($song, "rb");
fpassthru($handle);
fclose($handle);
Okay, that works pretty well. What I want to do after that is allow clients to seek to various positions in the mp3, much like they could do locally. In Windows Media Player, you can do this without any problems, and no additional headers are sent. However, Winamp sends requests for the new position as such:
Accept: /
Connection: close
Host: music.greysphere.net
Range: bytes=2944368-
User-Agent: WinampMPEG/5.0
This is the problem. I simply cannot find a way to allow seeking to the appropriate position. I have tried responding to the Range request by advancing the pointer with fseek(). Here is what I did:
define("MUSIC_DIR", "E:\\Music");
define("SUB_DIR", $_GET['dir']);
define("FILE", $_GET['file']);
$song = str_replace("\\\\'", "'", MUSIC_DIR . urldecode(SUB_DIR) . urldecode(FILE));
// -- get and process received headers --
$headers = apache_request_headers();
$headers['Range'] ?
$range = str_replace(array("bytes=", "-"), "", $headers['Range']) :
$range = 0;
// -- send the headers --
header('Content-Type: audio/mpeg');
header('Content-Length: '. (filesize($song) - $range));
header('Accept-Ranges: bytes');
header('Content-Range: bytes ' . $range . '-' . filesize($song) . '/' .filesize($song));
// -- send audio data --
$handle = fopen($song, "rb");
if ($range) fseek($handle, $range);
fpassthru($handle);
fclose($handle);
The results are not quite what I expected. When seeking, one of two (almost) random things happen:
I hear the desired segment for a moment, the stream restarts from the beginning, and the progress bar goes back to the beginning; Winamp does not send a Range request in the outgoing header
I can listen continuously to the desired segment, but the progress bar goes back to the beginning anyway; Winamp sends a Range request in the header
[/list=1]I say "almost" because the cases in which I can actually listen to the desired segment are ones in which I seeked to the same part of the song. I haven't a clue why this is.
If anyone has any thoughts on what I'm doing wrong, please let me know. I've been racking my brains for quite some time but I just can't figure it out on my own. Thanks in advance.