Intro
I've been working on a project lately, an Android app for streaming music between android devices, I've had this issue that I tried to resolve over and over again, I couldn't find any solution for it on the internet.I'm working with the MediaPlayer class from the Android libraries, in order to stream music through an URL I pass it as a path(Example: http://192.168.1.18:8855/bacon4every1/5), and of course I have a Proxy Server handling HTTP requests. when I receive a new stream song request the HTTP request header is something like this:
GET /bacon4every1/5 HTTP/1.1
Host: 81.202.80.132:8863
Connection: keep-alive
User-Agent: stagefright/1.2 (Linux;Android 4.1.2)
Accept-Encoding: gzip,deflate
Accept: */*
in order to start streaming to the MediaPlayer, of course, you have to respond with a specific valid HTTP header that looks something like this:
HTTP/1.1 200 OK
Content-Type: audio/mpeg
Accept-Ranges: bytes
Content-Length: 68462313547
It's possible to use both HTTP/1.0 or HTTP/1.1.
The Problem
All that works great, until I seek or skip to another position that hasn't been buffered yet, then the MediaPlayer closes this connection and resets the connection with a new HTTP request with the same request header as before only this time it specifies the Range, MediaPlayer keeps resetting the connection and sending this HTTP request a couple of times until a time-out occurs or a valid HTTP response been received, this is how the request looks like with the seek for partial content streaming:
GET /bacon4every1/5 HTTP/1.1
Host: 81.202.75.134:8851
Connection: keep-alive
User-Agent: stagefright/1.2 (Linux;Android 4.1.2)
Accept-Encoding: gzip,deflate
Accept: */*
Range: bytes=2621085-
The format for the Range field is "Range: <bytes-unit>=<from-byte-a>-[to-byte-b]" I wrote the second argument (to-byte-pos) with brackets because it's optional, if you leave this field empty, it means that the range of the requested stream goes from byte position 'a' to the end of the file or stream. I spent hours trying to figure out what to respond when I get this request because it can only be done in one way and not like the previous response, it's very important to know that in this case the Status of the response now changes, instead of 200 it's now 206 because it's Partial Content and not the whole content of the file being streamed. And it seems like Android MediaPlayer only considers the response header valid if it has this specific format
HTTP/1.1 206 Partial Content
Content-Type: audio/mpeg
Accept-Ranges: bytes
Content-Length: <length>
Content-Range: bytes <range>-<file-size>/*
////<length> = <file-size> - <range>
The important thing is to respond with a Status: 206, it won't work any other way.
This HTTP header response solves the problem of seeking unbuffered stream with HTTP Range requests.
I hope this could be useful for some of you.
Good catch!
ReplyDeleteThank you!
DeleteThank you for the useful blog; I was facing a similar issue with another server streaming to a native android media player. your explanation was helpful.
ReplyDelete