phpapachemp3audio-streaminginternet-radio

Is it possible to restream an internet radio using PHP? (PHP guru needed)


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?


Solution

  • 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.