是否可以使用 PHP 重新播放网络电台? (需要 PHP 大师)

Posted

技术标签:

【中文标题】是否可以使用 PHP 重新播放网络电台? (需要 PHP 大师)【英文标题】:Is it possible to restream an internet radio using PHP? (PHP guru needed) 【发布时间】:2011-12-21 09:02:30 【问题描述】:

是否可以使用 PHP 重新播放网络广播?

无线电在端口 8000 可用。我想使用我的网络服务器并将无线电流“传输”到端口 80。

有可能吗?

我已经在谷歌上搜索了它,我找到了http://support.spacialnet.com/forums/viewtopic.php?f=13&t=16858&start=15 但它对我不起作用。 它确实有效。在我忘记更改流的 MIME 类型之前。

我从前面提到的 URL (http://support.spacialnet.com/forums/viewtopic.php?f=13&t=16858&start=15) 定制了解决方案。它现在确实有效,但在大约 8 分钟的收听后,流总是会中断。任何线索为什么? (服务器最大执行时间设置为 30 秒)。我用不同的比特率测试了不同的流,但它每次的行为都完全相同。有什么帮助吗?

【问题讨论】:

你有原主播的权限吗? 如果他们播放文案音乐或任何需要支付版税的材料,您实际上可能触犯了法律。即使他们有权播放它(例如,由于支付版税),你也可能没有。 我一定会知道是否需要许可。但无论如何,这将是第二步。第一步是确定它是否技术上可行。事实上,我认为许可不会是任何问题,因为我不想重新播放任何商业广播,这将是法定/公共广播。但感谢您的评论。 这确实是一个网络问题,而不是编程问题。你应该看看像 netcat 这样简单的东西。 这不是编程问题吗?他在问源代码! 【参考方案1】:

我不应该告诉你这个。但从纯粹的学术角度来看,您可能希望使用fpassthru。这将允许您加载一个文件(在这种情况下是一个流)并立即将其转储出来,只要它需要。 (对于流来说,这是永远的。)

至于具体细节,这可能看起来很像您提供的链接。

可能的问题:脚本的最长运行时间可能会成为问题。我不知道。如果是这样,您可以随时将其增加到您在给定听力中不太可能达到的程度。

最后。不要这样做...

【讨论】:

为什么会出现问题?无论如何,我绝对不想增加最大运行时间。我不想破坏一些东西。我只是在试验,所以不要恨我:) 在不增加最大运行时间的情况下试一试。我很确定它不需要它,因为我们在 CMS 中将它用于大型视频,而且时间限制很短。 我从前面提到的 url (support.spacialnet.com/forums/…) 定制了解决方案。它现在正在工作,但在大约 8 分钟的收听后,流总是会中断。任何线索为什么? (最长执行时间设置为 30 秒) 不确定。可能是两端中断连接的东西。 我在不同的机器和不同的互联网连接上测试了它,我什至测试了不同比特率的无线电流(128kb/s 和 32kb/s),但问题还是一样...【参考方案2】:

我可能不应该回答这个问题,但我有一些空闲时间在工作,想玩一下套接字。

这是我的课程,它没有经过很好的测试(嗯,它在第一次运行时工作很可疑)并且可能有错误,但它可能会给你一些有用的想法。它会像您发布的示例那样去除 ICY* 标头,但这可以很容易地更改。

我在 Ubuntu Totem 播放器上测试了它,在我停止它之前它播放了 10 分钟,但也许我很幸运(:至少 8 分钟似乎不是什么神奇的数字。

<?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');

附:不要这样做。

【讨论】:

感谢您的努力,我稍后会检查它。为什么你们一直说我不应该这样做?这是一种不好的做法,是否危险,是法律问题还是真正的问题是什么?这似乎是一个愚蠢的问题,但我真的不明白。谢谢 好吧,除了法律问题之外,您的问题似乎是一个非常简单的网络问题(en.wikipedia.org/wiki/Port_forwarding)或/和 url 重写问题并用 PHP 解决它是一个非常糟糕的做法。 @tomexx 到底什么不起作用?它根本不起作用,还是你还有 8 分钟的限制?您是否将流的 MIME 类型更改为适合您的类型?我需要更多信息来更好地帮助您:您的操作系统类型/版本、您用于测试此流的程序、您的 PHP/Web 服务器版本、您尝试重新传输的流等。我使用 119.59.98.4 端口 8050 流进行了测试/Ubuntu 11.04/PHP 5.3.3-1ubuntu9.6 和 Suhosin-Patch/Apache 2.2.16 (Ubuntu)/Totem 播放器。 @tomexx,如果你现在可以测试这个,我认为在一些聊天中继续这个讨论会更有效率(所以内置聊天/gmail/ICQ/Skype/?)。跨度> @tomexx,很抱歉。正如我所说,这只是您可以使用您的应用程序的一般方向,而不是防弹解决方案。您发布的那个解决方案确实让我认为流式传输音乐的 HTTP 服务器可以回答仅在此处描述的标头(icy* 和 content-type*),但实际上它似乎可以发回任何 HTTP 标头(服务器、缓存控制等等。)。我通过创建更软的正则表达式检查解决了这个问题,但是如果您有任何问题,您可以禁用对接收到的标头的检查(“preg_match(self::HTTP_response_header_*, $header_line)”)或阅读有关无线电流的 RFC。【参考方案3】:

这在技术上绝对是可行的。我会尝试使用wireshark来查看数据包。在 8 分钟标记处可能缺少 SHOUTcast 专有的内容。

您也可以尝试缓冲一下。也许流停了?

【讨论】:

以上是关于是否可以使用 PHP 重新播放网络电台? (需要 PHP 大师)的主要内容,如果未能解决你的问题,请参考以下文章

是否需要使用 Remote Display API 构建的应用程序才能满足重新连接和连续播放的要求?

是否可以使用 html5 播放广播网络广播流?

是否可以在 iOS 5 中为电影播放器​​设置皮肤?

页面重新加载后保存草稿,无需通过js或php注册[关闭]

华为mate30无法播放小程序里视频

无线网络连接已断开?