Sunday, January 20, 2013

Android MediaPlayer Stream-HTTP Status: 206 Partial Content and Range Requests


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.