android seeking problem with proxy stream and stagefright player
I want to play an audiostream from a url that is only valid for a temporary amount of time. This doesn't play very well just by using the built in stagefright streaming functionality because of the buffering mechanism (the url will 开发者_运维百科be dead by the time the buffer is filled for the second time), so I have implemented a proxy stream, similar to what is done in the npr app
http://code.google.com/p/npr-android-app/source/browse/trunk/Npr/src/org/npr/android/news/StreamProxy.java
This actually works very nicely, with one exception, any seek call effectively breaks the proxy stream. I am having a hard time determining exactly how the seeking works in stagefright. Everytime I seek I get a message of
01-12 13:35:57.201: ERROR/(4870): Connection reset by peer
01-12 13:35:57.201: ERROR/(4870): java.net.SocketException: Connection reset by peer
01-12 13:35:57.201: ERROR/(4870): at org.apache.harmony.luni.platform.OSNetworkSystem.writeSocketImpl(Native Method)
01-12 13:35:57.201: ERROR/(4870): at org.apache.harmony.luni.platform.OSNetworkSystem.write(OSNetworkSystem.java:723)
01-12 13:35:57.201: ERROR/(4870): at org.apache.harmony.luni.net.PlainSocketImpl.write(PlainSocketImpl.java:578)
01-12 13:35:57.201: ERROR/(4870): at org.apache.harmony.luni.net.SocketOutputStream.write(SocketOutputStream.java:59)
01-12 13:35:57.201: ERROR/(4870): at com.soundcloud.utils.StreamProxy.processRequest(StreamProxy.java:209)
Then a pause for a few seconds, thafter which stagefright retries to connect to the same url, and usually throws an error (I would imagine because the proxy stream hasn't been reset). Another potential problem is that it seems as though the proxy stream will always read the data source linearly:
while (isRunning && (readBytes = data.read(buff, 0, buff.length)) != -1)
And I am only guessing, but I would think that in order to support seeking, the proxy would have to be able to provide an offset when reading from the buffer. Is there a way to figure out the requested offset from the socket client (the intended seek position)?
My experience with sockets is limited. Does anybody have suggestions on implementation here?
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.1 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";
And when you receive a request from MediaPlayer that does not contain Range in the HTTP header , then it is requesting a new stream file, in this case your response header should look like this:
String headers = "HTTP/1.1 200 OK\r\n";
headers += "Content-Type: audio/mpeg\r\n";
headers += "Accept-Ranges: bytes\r\n";
headers += "Content-Length: " + fileSize + "\r\n";
headers += "\r\n";
Enjoy!
The MediaPlayer is issuing HTTP range requests, which specifies the byte offsets that the player expects. You can read these range values from the headers of the request submitted to your proxy server. You can then open send just these ranges back to the MediaPlayer.
Not sure if you ever figured this out but all I needed to do was pass the headers from the original request onto the proxied request:
private HttpResponse download(String url, Header[] headers) {
DefaultHttpClient seed = new DefaultHttpClient();
SchemeRegistry registry = new SchemeRegistry();
registry.register(
new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
SingleClientConnManager mgr = new MyClientConnManager(seed.getParams(),
registry);
DefaultHttpClient http = new DefaultHttpClient(mgr, seed.getParams());
HttpGet method = new HttpGet(url);
for (Header header : headers) {
method.addHeader(header);
}
HttpResponse response = null;
try {
Log.d(getClass().getName(), "starting download");
response = http.execute(method);
Log.d(getClass().getName(), "downloaded");
}catch(java.net.UnknownHostException e)
{
Intent i=new Intent("org.prx.errorInStream");
mContext.sendBroadcast(i);
}
catch (ClientProtocolException e) {
Log.e(getClass().getName(), "Error downloading", e);
} catch (IOException e) {
Log.e(getClass().getName(), "Error downloading", e);
}
return response;
}
private void processRequest(HttpRequest request, Socket client)
throws IllegalStateException, IOException {
if (request == null) {
return;
}
Log.d(getClass().getName(), "processing");
String url = request.getRequestLine().getUri();
HttpResponse realResponse = download(url, request.getAllHeaders());
if (realResponse == null) {
return;
}
...
}
As mentioned by @Roberto, you have to modify methods download
and processRequest
in order to bypass HTTP request headers. But to actually get it done, or at least in my case, I have to further modify method private HttpRequest readRequest(Socket client)
to parse the headers from socket as shown below.
private HttpRequest readRequest(Socket client) {
HttpRequest request = null;
InputStream is;
String firstLine;
List<Header> headers = new ArrayList<Header>();
try {
is = client.getInputStream();
BufferedReader reader = new BufferedReader(
new InputStreamReader(is), 8192);
firstLine = reader.readLine();
/**
* read HTTP request headers from socket
* */
String line;
while (((line = reader.readLine()) != null) && !(line.equals(""))) { // HTTP header ends at ""
try {
StringTokenizer st = new StringTokenizer(line, ":");
String h = st.nextToken();
String v = st.nextToken();
Header header = new BasicHeader(h, v);
headers.add(header);
} catch (NoSuchElementException e) {
}
}
} catch (IOException e) {
Log.e(LOG_TAG, "Error parsing request", e);
return request;
}
if (firstLine == null) {
Log.i(LOG_TAG, "Proxy client closed connection without a request.");
return request;
}
/**
* retrieve REAL url
* */
StringTokenizer st = new StringTokenizer(firstLine);
String method = st.nextToken();
String uri = st.nextToken();
Log.d(LOG_TAG, uri);
String realUri = uri.substring(1);
Log.d(LOG_TAG, realUri);
request = new BasicHttpRequest(method, realUri);
/**
* add headers back to HTTP request
* */
for (Header header : headers) {
request.addHeader(header);
}
return request;
}
精彩评论