android media player - how to disable range request? (broken audio streaming on Nexus 7)
Asked Answered
C

3

22

I have a audio streaming app, which runs a local proxy server. The local proxy server makes a http connection to a internet streaming source, gets and buffers locally the streaming data. Then, inside in the app, I use MediaPlayer to connect to the local proxy server, using the method

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

Everything was fine (with plenty of Android devices and different OS versions - 1.5...4.0), until Nexus 7 release.

In Nexus 7, the media player refuses to play the source from the local proxy server.

When I took a look at the logs, seems like the MediaPlayer uses range requests internally. My local proxy server doesn't handle that. It returns HTTP/1.0 200 OK and the data. However, the media player doesn't like that and throws an exception:

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)

According to the http specs, if the server responds with HTTP/1.0 instead 1.1, the client must not fire a range request (1.0 doesn't support that anyway),

also if the server doesn't support the range request, it should be fine, if it responds with 200 OK (and this is what I'm doing), but the MediaPlayer implementation on Nexus 7 doesn't like that.

I took a look at this thread : HTTP: How should I respond to "Range: bytes=" when Range is unsupported?

,where they claim that the response with 200 OK must be good enough, but unfortunately it doesn't help.

I'm not sure if this is a problem with Jelly Bean, or a problem with Nexus 7 implementation specifically, but it's still a problem for me which I have to resolve.

Again, there are NO range requests on plenty other Android devices, using the same app. For some reason these range requests are happening now on Nexus 7. (It may happen on other Android devices as well, but again, never happened to me so far).

Any possible way to disable the range requests for MediaPlayer?

If there are none, can anybody suggest a quick fix for my proxy server logic (what exactly it has to return, if it receive this range request?), without changing my other logic, if possible?

Seems like maybe I have to return something like "HTTP/1.0 206 OK\r\nPartial Content\r\n\r\n", but probably there should be some value at the end of the Partial Content - not sure what is should be this one.

Your help would be appreciated.

Thanks..

Cumulonimbus answered 23/7, 2012 at 3:33 Comment(3)
I can confirm it's Jelly Bean issue (not only Nexus 7). It happened also on different Nexus devices, recently updated from Ice Cream Sandwich to Jelly Bean. Does anybody was able to use the MediaPlayer against a local proxy server at Jelly Bean?Cumulonimbus
Can confirm this issue too (on Jelly Bean). We're not using a proxy though, we access the audio stream directly through the media player.Jonson
This looks like a regression in Jelly Bean. I've created an issue on the bugtracker: code.google.com/p/android/issues/detail?id=35790Jonson
J
13

We've finally solved this in a clean way. In our case we have full control over the streaming server, but i guess you could do this with a local proxy as well. Since Build.VERSION_CODES.ICE_CREAM_SANDWICH it's possible to set headers for the MediaPlayer object. As our app enables the user to seek inside the audio stream, we've implemented this Range header on the client. If the server responds with the proper headers, the MediaPlayer will not try multimple times to request the stream.

That's what our server headers look like now for the android client:

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

The important part is the 206 status code (partial content). If you don't send this header, the android client will try to re-request the source, no matter what.

If your player doesn't allow seeking in the stream, you could simply always set the Range header to 0-some arbitrary large number.

Jonson answered 27/11, 2012 at 8:18 Comment(0)
A
8

I work on a large scale audio streaming app (that uses a local host HTTP proxy to stream audio to MediaPlayer) and I ran into this issue as soon as I got a JellyBean device in my hands at Google I/O 2012.

When quality testing our application on different devices (and with information received from our automated crash logs and user submitted logs), we noticed that certain MediaPlayer implementations behaved in what I would call an erratic (and sometimes downright psychotic) manner.

Without going into too much detail, this is what I saw: some implementations would make multiple requests (some times 5+) for the same URL. These requests were all slightly different from each other in that each one was for a different byte range (usually for the first or last 128 bytes). The conclusion was that the MediaPlayer was trying to find embedded metadata, then would after some point it would give up and just make a regular non-range request.

That is not what the stock JellyBean MediaPlayer implementation is doing, it just an example of the whacky-ness and general fragmentation of the media framework on Android. However, the solution to the above situation was also the solution to the JellyBean problem, and that is:

Have your local proxy respond with chunked encoding

This replaces the Content-Length header with a Tranfer-Encoding: chunked header. This means the requesting client will not know the total length of the resource and thus cannot make a range-request, it just has to deal with the chunks as they are received.

Like I said this is a hack, but it works. It is not without its side effects: media player buffer progress will be incorrect since it doesn't know the length of the audio (is one of them), to get around that you will have to use your own buffering computation (you are streaming from somewhere through your proxy to MediaPlayer, right? So you will know the total length).

Alternately answered 27/9, 2012 at 16:37 Comment(1)
This is an intriguing approach to a problem I’ve also run into on Jelly Bean devices. My server streams MP3s and responds to range requests. MediaPlayer makes a single request for most files, but some files cause this erratic behavior (the number of requests and their byte offsets vary, but behavior for a given file seems deterministic). Chunked transfer-encoding didn’t help. MediaPlayer made a range request shortly after the first response and ignored that header. Packet trace: pastebin.com/LiN6sKYH Is there something I’m missing? Does the streaming endpoint have to be local?Hordein
F
7

When you seek or skip or the connection is lost and MediaPlayer keeps reconnecting to the proxy server, you must send this response with Status 206 after you get the request and range(int) from the client.

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";
Fayre answered 21/1, 2013 at 2:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.