【问题标题】:HTML5 <audio> Safari live broadcast vs notHTML5 <audio> Safari 直播 vs 不直播
【发布时间】:2011-01-01 00:31:52
【问题描述】:

我正在尝试嵌入指向 PHP 文件提供的 MP3 或 OGG 数据的 HTML5 音频元素。当我在 Safari 中查看页面时,会出现控件,但 UI 显示“直播”。当我单击播放时,音频按预期开始。但是,一旦结束,我就无法通过单击播放再次开始播放。即使在音频元素上使用 JS API 并将 currentTime 设置为 0 也会失败并出现索引错误异常。

我怀疑 PHP 脚本的标头是问题所在,尤其是缺少内容长度。但事实并非如此。响应标头包含适当的 Content-Length 以指示音频具有有限大小。此外,在 Firefox 3.5+ 中一切正常。我可以多次单击音频元素上的播放来收听声音重播。

如果我从等式中删除 PHP 脚本并提供 MP3 文件的静态副本,那么在 Safari 中一切正常。

这是否意味着 Safari 对带有查询参数的音频 src URL 的处理方式与没有这些参数的 URL 不同?任何人都有运气让它工作吗?

我的简单示例页面是:

<!DOCTYPE html>
<html>
  <head></head>
  <body>
    <audio controls autobuffer>
      <source src="say.php?text=this%20is%20a%20test&format=.ogg" />
      <source src="say.php?text=this%20is%20a%20test&format=.mp3" />
    </audio>
  </body>
</html>

来自 PHP 脚本的 HTTP 标头:

HTTP/1.x 200 OK
Date: Sun, 03 Jan 2010 15:39:34 GMT
Server: Apache
X-Powered-By: PHP/5.2.10
Content-Length: 8993
Keep-Alive: timeout=2, max=98
Connection: Keep-Alive
Content-Type: audio/mpeg

来自直接文件访问的 HTTP 标头:

HTTP/1.x 200 OK
Date: Sun, 03 Jan 2010 20:06:59 GMT
Server: Apache
Last-Modified: Sun, 03 Jan 2010 03:20:02 GMT
Etag: "a404b-c3f-47c3a14937c80"
Accept-Ranges: bytes
Content-Length: 8993
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: audio/mpeg

我也尝试将 Accept-Ranges 标头硬编码到脚本中,但没有成功。

【问题讨论】:

  • 我不确定您是否收到有关编辑的通知,但我已经编辑了我的答案以包含一个适合我的测试用例,并且似乎与您正在做的事情相匹配。你能检查我的测试用例是否也适合你吗?

标签: audio safari html webkit


【解决方案1】:

您可以发布服务器发送的带有和不带有 PHP 脚本的标头吗?我想知道 PHP 脚本发送的 Content-Type 是否与正常提供文件不同。

source 元素上指定type 属性也是一个好主意,这样浏览器就不必下载两个剪辑来确定是否可以播放它们。

我无法重现您的问题。我试图在 Safari 4.0.4 和当前的 WebKit 每晚使用following test page 重新创建问题。我只是使用 mod_rewrite 来根据参数而不是 PHP 分派到不同的格式,但我认为这不会产生影响,除非 PHP 以某种方式修改了文件。

<!DOCTYPE html>
<title>Auido test</title>
<audio controls autobuffer>
  <source src="gnossienne-no-1?foo=bar&format=.mp4">
  <source src="gnossienne-no-1?foo=bar&format=.ogg">
</audio>

你能试试我的例子,让我知道它是否适合你吗?

编辑啊。在仔细研究了一下之后,问题似乎是由于 Safari 中的 &lt;audio&gt; 元素在尝试确定内容大小时的行为方式很奇怪。

这是 Safari 在遇到指向直接从 Apache 提供的文件的 &lt;audio&gt; 元素时捕获的数据包的摘录。如您所见,它首先尝试获取媒体的前两个字节,大概是为了获取 Content-Length 以及可能的其他标头。然后它会尝试获取整个内容。然后,莫名其妙地,它再次尝试获取前两个字节,但传递适当的缓存标头以获得“304 Not Modified”响应。最后,仍然莫名其妙地,它再次获取文件的最后 3440 个字节。它在单独的 TCP 连接中完成所有这些工作,除了多次获取数据的开销之外,这会增加相当大的开销。

GET /stackoverflow/audio-test/say-noid3?foo=bar&format=.mp3 HTTP/1.1 主持人:ephemera.continuation.org 范围:字节=0-1 连接:关闭 用户代理:Apple Mac OS X v10.6.2 CoreMedia v1.0.0.10C540 接受: */* 接受编码:身份 饼干:[编辑] HTTP/1.1 206 部分内容 日期:2010 年 1 月 5 日星期二 02:12:48 GMT 服务器:阿帕奇 最后修改时间:2010 年 1 月 5 日星期二 02:02:08 GMT ETag:“b2a80ad-11f6-47c6139aaa800” 接受范围:字节 内容长度:2 内容范围:字节 0-1/4598 连接:关闭 内容类型:音频/mpeg # 2字节数据 GET /stackoverflow/audio-test/say-noid3?foo=bar&format=.mp3 HTTP/1.1 主持人:ephemera.continuation.org 范围:字节=0-4597 连接:关闭 用户代理:Apple Mac OS X v10.6.2 CoreMedia v1.0.0.10C540 接受: */* 接受编码:身份 饼干:[编辑] HTTP/1.1 206 部分内容 日期:2010 年 1 月 5 日星期二 02:12:48 GMT 服务器:阿帕奇 最后修改时间:2010 年 1 月 5 日星期二 02:02:08 GMT ETag:“b2a80ad-11f6-47c6139aaa800” 接受范围:字节 内容长度:4598 内容范围:字节 0-4597/4598 连接:关闭 内容类型:音频/mpeg # 4598字节数据 GET /stackoverflow/audio-test/say-noid3?foo=bar&format=.mp3 HTTP/1.1 主持人:ephemera.continuation.org 范围:字节=0-1 连接:关闭 用户代理:Apple Mac OS X v10.6.2 CoreMedia v1.0.0.10C540 接受: */* 接受编码:身份 饼干:[编辑] 如果无匹配:“b2a80ad-11f6-47c6139aaa800” If-Modified-Since: 2010 年 1 月 5 日星期二 02:02:08 GMT HTTP/1.1 304 未修改 日期:格林威治标准时间 2010 年 1 月 5 日星期二 02:12:49 服务器:阿帕奇 连接:关闭 ETag:“b2a80ad-11f6-47c6139aaa800” # 没有数据 GET /stackoverflow/audio-test/say-noid3?foo=bar&format=.mp3 HTTP/1.1 主持人:ephemera.continuation.org 范围:字节=1158-4597 连接:关闭 用户代理:Apple Mac OS X v10.6.2 CoreMedia v1.0.0.10C540 接受: */* 接受编码:身份 饼干:[编辑] HTTP/1.1 206 部分内容 日期:格林威治标准时间 2010 年 1 月 5 日星期二 02:12:49 服务器:阿帕奇 最后修改时间:2010 年 1 月 5 日星期二 02:02:08 GMT ETag:“b2a80ad-11f6-47c6139aaa800” 接受范围:字节 内容长度:3440 内容范围:字节 1158-4597/4598 连接:关闭 内容类型:音频/mpeg # 3440字节数据

无论如何,关于它如何处理 PHP 脚本的输出。在这里,Safari 再次尝试下载前两个字节,但您的脚本忽略了 Range 请求并返回整个内容。显然,WebKit 不喜欢这样,所以它再次尝试,没有Range 请求。同样,您的脚本会发送完整的内容。 Safari 现在再次尝试,添加一个Icy-Metadata 标头,这表明它认为它正在下载流并希望发送流元数据。它最终接受了它的输出,并且&lt;audio&gt; 元素可以播放。

GET /say.php?text=this%20is%20a%20test&format=.mp3 HTTP/1.1 主持人:tts.mindtrove.info 范围:字节=0-1 连接:关闭 用户代理:Apple Mac OS X v10.6.2 CoreMedia v1.0.0.10C540 接受: */* 接受编码:身份 HTTP/1.1 200 正常 日期:格林威治标准时间 2010 年 1 月 5 日星期二 02:14:28 服务器:阿帕奇 X-Powered-By: PHP/5.2.10 内容长度:4598 连接:关闭 内容类型:音频/mpeg # 4598字节数据 GET /say.php?text=this%20is%20a%20test&format=.mp3 HTTP/1.1 主持人:tts.mindtrove.info 连接:关闭 用户代理:Apple Mac OS X v10.6.2 CoreMedia v1.0.0.10C540 接受: */* HTTP/1.1 200 正常 日期:格林威治标准时间 2010 年 1 月 5 日星期二 02:14:28 服务器:阿帕奇 X-Powered-By: PHP/5.2.10 内容长度:4598 连接:关闭 内容类型:音频/mpeg # 4598字节数据 GET /say.php?text=this%20is%20a%20test&format=.mp3 HTTP/1.1 主持人:tts.mindtrove.info 接受: */* 用户代理:Apple Mac OS X v10.6.2 CoreMedia v1.0.0.10C540 冰冷的元数据:1 连接:关闭 HTTP/1.1 200 正常 日期:格林威治标准时间 2010 年 1 月 5 日星期二 02:14:28 服务器:阿帕奇 X-Powered-By: PHP/5.2.10 内容长度:4598 连接:关闭 内容类型:音频/mpeg # 4598字节数据

总而言之,Safari(或更准确地说,QuickTime,Safari 使用它来处理所有媒体和媒体下载)似乎有一种完全脑残的媒体下载方法。您发回数据的方式中的某些东西,可能是您没有响应Range 请求的事实,使它认为您正在发送流媒体,导致它重复下载内容(尽管即使面对服务器确实响应了Range 请求,但它仍然执行的请求比实际需要的请求多)。

我的建议是尝试对Range 的请求做出适当的回应;在提供媒体时,浏览器可能会使用它们来尝试最小化带宽,只缓冲它们需要能够播放的内容(尽管有 autobuffer 属性表示您希望它们缓冲整个内容,浏览器可能会忽略它)。我建议使用 X-Sendfile 让 Apache 处理提供文件、缓存和范围请求,但你似乎在 Dreamhost 上,它没有安装 mod_xsendfile,所以你将不得不滚动你的自己处理Range

【讨论】:

  • 将标题添加到原始问题中,因为它们不适合评论。我最初指定了类型,但将其取出以防出现问题。将它们添加回来以进行良好的衡量,但不会改变 Safari 中的损坏行为。
  • 很好的帮助。感谢您花时间和我一起解决这个问题。您的发现可能适合提交错误报告,但我确信 Apple 会简单地说 PHP 脚本需要 Range 处理。如果他们在某个地方记录它可能会很好。
  • 这仍然是一个问题。 :(
  • 哇,这个问题现在,6 年后,仍然存在于 iOS 9.2 的 safari 中。感谢您的明确答复。为我节省了一天的挫败感。
  • 这真的很有帮助,我建议将此作为我的堆栈溢出问题的答案stackoverflow.com/questions/55830509/…
【解决方案2】:

作为记录,虽然 Pochang 和 Chris 都认为您需要 Content-Range 标头来解决 Safari 中的此问题是正确的,但 Chrome 需要一个额外的标头,必须包含一个额外的标头才能设置 currentTime 才能正常工作:

header( 'Accept-Ranges: bytes');

请注意,您实际上不必正确响应请求的 Range 标头,您只需将其包含在响应中即可。

【讨论】:

  • 谢谢,Brendan,帮了大忙!
【解决方案3】:

老话题,但在 2019 年仍然有效。我们终于找到了解决方案……下面的 PHP 脚本示例将考虑 Safari 的“Range”标头请求。

$bytes_total = strlen($data);
if (isset($_SERVER['HTTP_RANGE'])) {
    $byte_range = explode('-',trim(str_ireplace('bytes=','',$_SERVER['HTTP_RANGE'])));
    $byte_from = $byte_range[0];
    $byte_to = ($byte_range[1]+1);
    $data = substr($data,$byte_from,$byte_to);
    header('HTTP/1.1 206 Partial Content');
}
else {
    $byte_from = 0;
    $byte_to = $bytes_total;
}
$length = strlen($data); 
header('Content-type: '.$content_type);
header('Accept-Ranges: bytes');
header('Content-length: ' . $length);
header('Content-Range: bytes '.$byte_from.'-'.$byte_to.'/'.$bytes_total);
header('Content-Transfer-Encoding: binary');
print($data);

【讨论】:

  • 这将向 Range:0-1 请求返回 3 个字节,因为您将 +1 添加到 byte_to。不会吗?无论如何,您建议的解决方案对我不起作用 - 仍然在 Safari 中看到直播。您是否在 Safari 中测试过上述内容?
【解决方案4】:

我遇到了同样的问题。 关键点是 Content-Range 标头。 将其添加到 mp3 输出 php 后一切正常。

【讨论】:

    【解决方案5】:

    Pochang 是正确的。在 PHP 响应中包含 Content-Range 标头将导致 Safari 正常运行。它还允许在 Safari 中寻找 (media.currentTime = 0;) 而没有可怕的 INDEX_SIZE_ERR,但在 Chrome 中却不行。

    标头的PHP代码是:

    $len = strlen( $data );
    $shortlen = $len - 1;
    header( 'Content-Range: bytes 0-'.$shortlen.'/'.$len);
    

    【讨论】:

    • 这里的变量到底是什么??? $data 是您的音乐文件吗? $length 来自哪里?
    【解决方案6】:

    在 2020 年,这仍然是一个实际问题。

    仅添加Content-Range 标头不起作用。

    下面是我的实现(基于这里的一些答案)。

            $content_length = strlen($media_total);
            $total_bytes = $content_length;
            $content_length_1 = $content_length - 1;
    
            if (isset($_SERVER['HTTP_RANGE'])) {
    
                $byte_range = explode('-',trim(str_ireplace('bytes=','',$_SERVER['HTTP_RANGE'])));
    
                $byte_from = $byte_range[0];
                $byte_to = intval($byte_range[1]);
                $byte_to = $byte_to == 0 ? $content_length_1 : $byte_to;
    
                $media_total = substr($media_total,$byte_from,$byte_to);
    
                $content_length = strlen($media_total);
    
                header('HTTP/1.1 206 Partial Content');
            }
            else {
                $byte_from = 0;
                $byte_to = $content_length_1;
            }
    
            $content_range = 'bytes '.$byte_from.'-' . $byte_to . '/' . $total_bytes;
    
            header('Accept-Ranges: bytes');
            header("Content-Range: ".$content_range);
            header("Content-type: ".$type);
            header("Content-length: ".$content_length);
            header('Content-Transfer-Encoding: binary');
    
            echo $media_total;
    
            exit;
    

    这里的原始问题:Timing problem for generated audio in some browsers

    【讨论】:

    • 嗨,这个sn-p的代码应该添加到哪里?谢谢
    猜你喜欢
    • 2011-08-17
    • 1970-01-01
    • 2013-06-03
    • 1970-01-01
    • 1970-01-01
    • 2011-09-11
    • 1970-01-01
    • 2015-10-10
    • 2015-03-26
    相关资源
    最近更新 更多