Reputation: 2339
Is it possible to restream an internet radio using PHP?
Radio is available at port 8000. I would like to use my webserver and "transmit" the radio stream to port 80.
Is it possible?
I have already been googling it around and I found http://support.spacialnet.com/forums/viewtopic.php?f=13&t=16858&start=15 but it doesn't work for me. It does actually work. Before I just forget to change MIME type of the stream.
I customized solution from previously mentioned URL (http://support.spacialnet.com/forums/viewtopic.php?f=13&t=16858&start=15). It does work now, but the stream always breaks after in about 8 minutes of listening. Any clue why? (server max. execution time is set to 30 seconds). I tested different streams with various bitrates, but it behaves exactly same every time. Any help?
Upvotes: 3
Views: 3553
Reputation: 1
From a logical standpoint no law can be broken here. An assumption would need be made that one person is listening to one device and that is just not knowable.
We know in homes that multiple people can be watching the same device.
Also multiple devices can be streaming a station in one location but that doesn't equate to the number of listeners. There might be 100 devices and 1 person physically listening to any of them.
Upvotes: 0
Reputation: 2502
I probably shouldn't be answering this question, but I had some free time at work and wanted to play with sockets a bit.
Here is my class, it isn't well-tested(well, it worked on the first run which is suspicious) and may be buggy, but it may give you some usefull ideas. It strips ICY* headers as that example you posted, but that can be easily changed.
I tested it on Ubuntu Totem player and it played well for 10 minutes before I stopped it, but maybe I just got lucky (: At least 8 minutes seems not to be some magical number.
<?php
ob_start();
class RadioProxy {
CONST STREAM_content_type='audio/aac';
CONST STREAM_timeout=1.5;
CONST HTTP_response_header_first='/\s200\s/';
CONST HTTP_response_header_pattern='/^[a-z\-]+:/i';
CONST HTTP_max_line_length=1024;
CONST HTTP_delim="\r\n";
CONST HTTP_max_response_headers=40;
CONST ERROR_max=5;
CONST ERROR_interval=120;
CONST ERROR_usleep=300000;
private $server_name, $server_port;
private $HTTP_headers;
private $STREAM = NULL;
private $STREAM_errors = array();
private $TIMEOUT_seconds, $TIMEOUT_microseconds;
public function __construct($server_name, $server_port, $filename='') {
self::STREAM_set_headers();
$this->server_name = $server_name;
$this->server_port = $server_port;
$this->HTTP_headers = $this->HTTP_generate_headers($filename);
$this->connect();
}
private function connect() {
$HTTP_headers_length = strlen($this->HTTP_headers);
do {
if (!$this->STREAM_connect()) {
continue;
}
if (!$this->STREAM_send_headers()) {
continue;
}
if (!$this->STREAM_skip_headers()) {
continue;
}
if (!$this->STREAM_proxy()) {
continue;
}
} while ($this->ERROR_is_accepteble());
}
private function HTTP_generate_headers($filename) {
$header = '';
self::HTTP_add_header($header, 'GET /' . rawurlencode($filename) . ' HTTP/1.0');
self::HTTP_add_header($header, 'Host: ' . $this->server_name);
self::HTTP_add_header($header, 'User-Agent: WinampMPEG/5.11');
self::HTTP_add_header($header, 'Accept: */*');
self::HTTP_add_header($header, 'Connection: close');
//End of headers
self::HTTP_add_header($header);
return $header;
}
private static function HTTP_add_header(&$header, $new_header_line='') {
$header.=$new_header_line . self::HTTP_delim;
}
private function ERROR_is_accepteble() {
//Delete old errors
array_filter($this->STREAM_errors, 'self::ERROR_remove_old');
$this->STREAM_errors[] = time();
usleep(self::ERROR_usleep);
return count($this->STREAM_errors) <= self::ERROR_max;
}
private static function ERROR_remove_old($error_time) {
return ($error_time - time()) <= self::ERROR_interval;
}
private function STREAM_connect() {
if (!ob_get_level()) {
ob_start();
}
ob_clean();
if ($this->STREAM !== NULL) {
fclose($this->STREAM);
}
$this->STREAM = fsockopen($this->server_name, $this->server_port);
if ($this->STREAM === FALSE) {
return FALSE;
}
$this->TIMEOUT_seconds = floor(self::STREAM_timeout);
$this->TIMEOUT_microseconds = ceil((self::STREAM_timeout - $this->TIMEOUT_seconds) * 1000);
return stream_set_timeout($this->STREAM, $this->TIMEOUT_seconds, $this->TIMEOUT_microseconds);
}
private function STREAM_send_headers() {
return fwrite($this->STREAM, $this->HTTP_headers) === strlen($this->HTTP_headers);
}
private function STREAM_skip_headers() {
$read_expect = array($this->STREAM);
$if_first_header = true;
$header_lines_count = 0;
do {
stream_select($read_expect, $NULL, $NULL, $this->TIMEOUT_seconds, $this->TIMEOUT_microseconds);
$header_line = stream_get_line($this->STREAM, self::HTTP_max_line_length, self::HTTP_delim);
if ($header_line === FALSE) {
return FALSE;
}
if ($if_first_header) {
$if_first_header = false;
if (!preg_match(self::HTTP_response_header_first, $header_line)) {
return FALSE;
}
continue;
}
if (empty($header_line)) {
return TRUE;
}
if (!preg_match(self::HTTP_response_header_pattern, $header_line)) {
return FALSE;
}
$header_lines_count++;
} while ($header_lines_count < self::HTTP_max_response_headers);
return FALSE;
}
private function STREAM_proxy() {
$read_expect = array($this->STREAM);
//No output buffering should be here!
while (@ob_end_clean ());
do {
stream_select($read_expect, $NULL, $NULL, $this->TIMEOUT_seconds, $this->TIMEOUT_microseconds);
} while (fpassthru($this->STREAM));
}
private static function STREAM_set_headers() {
//Clean all output
ob_clean();
header("Content-type: " . self::STREAM_content_type);
ob_flush();
}
}
$TestRadio = new RadioProxy('XXX.XXX.XXX.XXX', XXXX,'XXXX.mp3');
P.S. Don't do this.
Upvotes: 1
Reputation: 15159
It's definitely technically possible. I'd try using wireshark to look at the packets. There might be something missing at the 8 minute mark that is proprietary to SHOUTcast.
You might also try buffering it a bit. Maybe the stream stalls?
Upvotes: 1
Reputation: 3621
I shouldn't be telling you this. But from a purely academic stand-point you probably want to be using fpassthru. This will allow you to load a file (in this case a stream) and dump it out immediately and for as long as it takes. (For a stream, that's forever.)
As to the particular details, that will probably look a lot like the link you provided.
Possible Issue: The maximum run-time of the script may become an issue. I'm not sure. If so, you can always increase it to something you are unlikely to reach in a given listening.
Lastly. Don't do this...
Upvotes: 2