【问题标题】:Skipping an MP3 causes player to break in Webkit browsers跳过 MP3 会导致播放器在 Webkit 浏览器中中断
【发布时间】:2014-02-27 14:28:29
【问题描述】:

我有一个 PHP 脚本充当用户和 MP3 之间的代理。这是为了防止外部嵌入或盗链,因此我可以计算每个 MP3 文件的播放次数。

我在 Google Chrome 和 Safari(Opera、Firefox 和 Internet Explorer 完美运行)中遇到问题,它可以很好地播放 MP3,我也可以搜索,但次数有限。在搜索了几次后,播放器会自行变灰(原生 chrome 播放器)或显示错误(audio.jsJWPlayer 6)。我可以再次按播放,它会重新启动,但在出现此类错误后,audio.js 不会再次显示播放按钮。

JWPlayer 在控制台中抛出这个错误:

MediaError
code: 2

__proto__: MediaError
MEDIA_ERR_ABORTED: 1
MEDIA_ERR_DECODE: 3
MEDIA_ERR_ENCRYPTED: 5
MEDIA_ERR_NETWORK: 2
MEDIA_ERR_SRC_NOT_SUPPORTED: 4

在 chromes 原生播放器中,控制台报告“加载资源失败”,我的服务器错误日志显示没有错误。

我目前使用的代码来自 Los Cherone 的回答: Make mp3 seekable PHP

我之前的解决方案是这段代码我不再使用了,但在这里供参考:

<?php 
$track = '/public_html/mp3-previews/' . $_GET['stream'];

if(file_exists($track)) {
    header('Content-type: audio/mpeg');
header('Accept-Ranges: bytes');
    header('Content-length: ' . filesize($track));
    print file_get_contents($track);
} else {
    echo "no file";
}
?>

这两个代码都会导致此错误,但值得指出的是,这两个代码都流式传输音频,并且它们都允许我搜索几次。

我最初的想法是函数 apache_request_headers 导致了问题,因为我运行的是 PHP 5.3,而我的主机正在运行 PHP 作为 Fast CGI。所以对于 Los Cherone 的代码,我使用的是 workaround,但为什么我的第一次尝试也不起作用?

编辑:这不是问题。我已经更新了我的 PHP 版本,这仍然是我的问题。

这是我的托管服务器示例: http://tabbidesign.com/audio/

Los Cherone 的代码和我自己的本地 Apache 服务器与 PHP 5.4.9 的组合完美运行,不会出现此问题。示例如下:http://tabbicat.info/proving/audio/

这可能是什么原因造成的?

【问题讨论】:

  • 您的代码说它可以接受字节范围,但实际上完全无法提供字节范围...应该是Accept-ranges: none
  • 在您欺骗音频标签要求子文件范围后,不要加载和提供整个文件。
  • @dandavis 原来你是完全正确的,我用 Fiddler 看看发生了什么,我得到 Content-Length 不匹配。您能否提供有关代码错误位置的任何指示。我已经完成了它并研究了这些功能,但据我所知,它只是发送请求的字节,除非没有请求范围。我需要 MP3 是可搜索的。谢谢!
  • 我注意到的一件事是您的 Content-Length 标头始终表示完整的文件大小,而不是响应的实际长度。我知道有效的节点服务器也会发出“Connection:keep-alive”,不确定哪个重要。

标签: javascript php google-chrome safari webkit


【解决方案1】:

我已经设法解决了我的问题。

在尝试了其他传输方法,用 Fiddler 分析了我的网络流量并听取了上面 cmets 的一些建议后,我对代码进行了一些更改。

由于某种原因,脚本未在如下所示的循环中发送正确数量的字节:

if ($start) fseek($fp,$start);
    while($length){
        set_time_limit(0);
        $read = 8192;
        $length -= $read;
        print(fread($fp,$read));
    }
    fclose($fp);

在 Firefox、Opera 和 IE 中没有问题的原因是因为它们在播放过程中缓存了整个文件,因此向后跳过不会再次请求文件。 Webkit(Chrome 和 Safari)每次都再次请求文件发送内容范围。

$file_name  = '/home/sites/public_html/mp3-previews/' . $_GET['stream'];
stream($file_name, 'audio/mpeg');


function stream($file, $content_type = 'application/octet-stream') {
@error_reporting(0);

// Make sure the files exists, otherwise we are wasting our time
if (!file_exists($file)) {
    header("HTTP/1.1 404 Not Found");
    exit;
}

// Get file size
$filesize = sprintf("%u", filesize($file));

// Handle 'Range' header
if(isset($_SERVER['HTTP_RANGE'])){
    $range = $_SERVER['HTTP_RANGE'];
}elseif($apache = apache_request_headers()){
    $headers = array();
    foreach ($apache as $header => $val){
        $headers[strtolower($header)] = $val;
    }
    if(isset($headers['range'])){
        $range = $headers['range'];
    }
    else $range = FALSE;
} else $range = FALSE;

//Is range
if($range){
    $partial = true;
    list($param, $range) = explode('=',$range);
    // Bad request - range unit is not 'bytes'
    if(strtolower(trim($param)) != 'bytes'){ 
        header("HTTP/1.1 400 Invalid Request");
        exit;
    }
    // Get range values
    $range = explode(',',$range);
    $range = explode('-',$range[0]); 
    // Deal with range values
    if ($range[0] === ''){
        $end = $filesize - 1;
        $start = $end - intval($range[0]);
    } else if ($range[1] === '') {
        $start = intval($range[0]);
        $end = $filesize - 1;
    }else{ 
        // Both numbers present, return specific range
        $start = intval($range[0]);
        $end = intval($range[1]);
        if ($end >= $filesize || (!$start && (!$end || $end == ($filesize - 1))))       $partial = false; // Invalid range/whole file specified, return whole file
    }
    $length = $end - $start + 1;
}
// No range requested
else $partial = false; 

// Send standard headers
header("Content-Type: $content_type");
header("Content-Length: $length");
header('Accept-Ranges: bytes');
//header('Connection: keep-alive');

//header('Cache-Control: no-cache, no-store, must-revalidate'); // HTTP 1.1.
//header('Pragma: no-cache'); // HTTP 1.0.
//header('Expires: 0'); // Proxies.

// send extra headers for range handling...
if ($partial) {

    header('HTTP/1.1 206 Partial Content');

    header("Content-Range: bytes $start-$end/$filesize");
    if (!$fp = fopen($file, 'rb')) {
        header("HTTP/1.1 500 Internal Server Error");
        exit;
    }
    if ($start) fseek($fp,$start);
        print(fread($fp,$length));
        fclose($fp);
}
//just send the whole file
else readfile($file);
exit;
}

我已发送 Content-Length 标头中请求的实际输出大小,并删除了以 8000 字节部分发送文件的循环。

现在我确信如果我尝试处理大文件 PHP 肯定会耗尽内存,因为我正在处理整个文件而不是 8000 字节块,但我每次只处理 4MB 文件所以现在这是好的。

【讨论】:

  • 在尝试实现 HTTP 1.1 的部分内容功能时,这个答案极大地帮助了我。对我而言,关键是当您指出将 Content-Length 标头设置为所请求范围的大小时,而不是文件本身的大小。我将 Content-Length 设置为文件大小,但无法弄清楚 Content-Range 导致不匹配的原因。
  • 非常感谢。这让我发疯了。它还解决了我遇到的一个相关问题,即当使用 net::ERR_CONTENT_LENGTH_MISMATCH 流式传输由 PHP 提供的长时间(8 分钟以上)MP3 时,Chrome 会简单地崩溃。我仍然在浏览器控制台中收到这些错误,但它一直在播放。
猜你喜欢
  • 1970-01-01
  • 2023-04-09
  • 2013-01-10
  • 2021-05-23
  • 2021-09-17
  • 2015-06-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多