22

我有一个音频流应用程序,它运行本地代理服务器。本地代理服务器与互联网流媒体源建立 http 连接,在本地获取和缓冲流数据。然后,在应用程序内部,我使用 MediaPlayer 连接到本地代理服务器,使用方法

mediaPlayer.setDataSource(...); // the url of the local proxy server

一切都很好(有很多 Android 设备和不同的操作系统版本 - 1.5...4.0),直到 Nexus 7 发布。

在 Nexus 7 中,媒体播放器拒绝播放来自本地代理服务器的源。

当我查看日志时,似乎 MediaPlayer 在内部使用范围请求。我的本地代理服务器无法处理。它返回 HTTP/1.0 200 OK 和数据。但是,媒体播放器不喜欢这样并抛出异常:

Caused by: libcore.io.ErrnoException
?:??: W/?(?): [ 07-18 00:08:35.333  4962: 5149 E/radiobee ]
?:??: W/?(?): : sendto failed: ECONNRESET (Connection reset by peer)
?:??: W/?(?):   at libcore.io.Posix.sendtoBytes(Native Method)
?:??: W/?(?):   at libcore.io.Posix.sendto(Posix.java:146)
?:??: W/?(?):   at libcore.io.BlockGuardOs.sendto(BlockGuardOs.java:
?:??: W/?(?):   at libcore.io.IoBridge.sendto(IoBridge.java:473)
We requested a content range, but server didn't support that. (responded with 200)

根据 http 规范,如果服务器以 HTTP/1.0 而不是 1.1 响应,则客户端不得触发范围请求(无论如何 1.0 都不支持),

此外,如果服务器不支持范围请求,它应该没问题,如果它以 200 OK 响应(这就是我正在做的),但 Nexus 7 上的 MediaPlayer 实现不喜欢那样。

我看了一下这个线程: HTTP:当 Range 不受支持时,我应该如何响应“Range:bytes=”?

,他们声称 200 OK 的响应必须足够好,但不幸的是它没有帮助。

我不确定这是 Jelly Bean 的问题,还是 Nexus 7 实现的问题,但这对我来说仍然是一个我必须解决的问题。

同样,使用相同应用程序的许多其他 Android 设备上也没有范围请求。出于某种原因,这些范围请求现在正在 Nexus 7 上发生。(它也可能发生在其他 Android 设备上,但同样,到目前为止,我从未发生过)。

任何可能的方法来禁用 MediaPlayer 的范围请求?

如果没有,任何人都可以建议我的代理服务器逻辑的快速修复(如果它收到此范围请求,它究竟必须返回什么?),如果可能的话,不改变我的其他逻辑?

似乎我可能必须返回类似“HTTP/1.0 206 OK\r\nPartial Content\r\n\r\n”的内容,但可能在部分内容的末尾应该有一些值 - 不确定应该是什么成为这个。

您的帮助将不胜感激。

谢谢..

4

3 回答 3

13

我们终于以干净的方式解决了这个问题。在我们的例子中,我们可以完全控制流媒体服务器,但我想你也可以使用本地代理来做到这一点。因为Build.VERSION_CODES.ICE_CREAM_SANDWICH可以为MediaPlayer对象设置标题。由于我们的应用程序使用户能够在音频流中进行搜索,因此我们Range在客户端上实现了此标头。如果服务器以正确的标头响应,MediaPlayer则不会尝试多次请求流。

这就是我们的服务器标头现在对于 android 客户端的样子:

Content-Type: audio/mpeg
Accept-Ranges: bytes
Content-Length: XXXX
Content-Range: bytes XXX-XXX/XXX
Status: 206

重要的部分是206状态码(部分内容)。如果您不发送此标头,android 客户端将尝试重新请求源,无论如何。

如果您的播放器不允许在流中搜索,您可以简单地将Range标题设置为0-some arbitrary large number.

于 2012-11-27T08:18:21.103 回答
8

我在开发一个大型音频流应用程序(使用本地主机 HTTP 代理将音频流式传输到 MediaPlayer),在 Google I/O 2012 上我一拿到 JellyBean 设备就遇到了这个问题。

当在不同设备上对我们的应用程序进行质量测试时(以及从我们的自动崩溃日志和用户提交的日志中收到的信息),我们注意到某些 MediaPlayer 实现的行为方式我称之为不稳定(有时甚至是彻头彻尾的精神病)。

没有过多的细节,这就是我所看到的:一些实现会为同一个 URL 发出多个请求(有时 5+)。这些请求彼此都略有不同,因为每个请求都针对不同的字节范围(通常针对第一个或最后 128 个字节)。结论是 MediaPlayer 正在尝试查找嵌入的元数据,然后在某些时候它会放弃并只是发出一个常规的非范围请求。

这不是现有的 JellyBean MediaPlayer 实现正在做的事情,它只是 Android 上媒体框架的古怪和一般碎片化的一个例子。但是,上述情况的解决方案也是解决 JellyBean 问题的方法,即:

让您的本地代理使用分块编码进行响应

这将 Content-Length 标头替换为 Tranfer-Encoding: 分块标头。这意味着请求客户端将不知道资源的总长度,因此无法发出范围请求,它只需要在收到块时处理它们。

就像我说的那样,这是一个 hack,但它确实有效。它并非没有副作用:媒体播放器缓冲区进度将不正确,因为它不知道音频的长度(是其中之一),要解决这个问题,您必须使用自己的缓冲计算(您正在流式传输从某个地方通过你的代理到 MediaPlayer,对吧?所以你会知道总长度)。

于 2012-09-27T16:37:03.177 回答
7

当您寻求或跳过或连接丢失并且 MediaPlayer 不断重新连接到代理服务器时,您必须在从客户端获得请求和 range(int) 后发送状态为 206的此响应。

String headers += "HTTP/1.0 206 OK\r\n";
headers += "Content-Type: audio/mpeg\r\n";
headers += "Accept-Ranges: bytes\r\n";
headers += "Content-Length: " + (fileSize-range) + "\r\n";
headers += "Content-Range: bytes "+range + "-" + fileSize + "/*\r\n";
headers += "\r\n";
于 2013-01-21T02:28:27.320 回答