NanoHttpd server cannot stream large videos on android
Asked Answered
O

4

12

NanoHttpd server code can be found here.

I'm starting a new Thread in a service that uses NanoHttpd server to stream large videos (about 150mb) but it just pauses right while the loading dialog is shown. I tried increasing and decreasing the buffer reads to no avail. It seems that the server cannot run properly on an Android device.

Same code works fine when I start the server via desktop application. I can stream more than 150mb. When running the server from the phone, I only tried 20mb files and they were good too. However I need to stream much more than that.

Oliana answered 29/2, 2012 at 5:7 Comment(0)
O
2

Solved my own problem. The issue is that the MediaPlayer(s) (wmp, vlc, android player), issue a GET request with a specified RANGE. Served that request correctly, problem solved.

Oliana answered 6/3, 2012 at 7:8 Comment(6)
Would you mind sharing your code? I'm looking to create an app that streams videos from an InputStream, and I'd love to see if I can get any pointers from your code. I'd be happy to award you a bounty for your help. Thanks in advance!Pricket
Streaming video from an InputStream was actually my first shot at the problem, but as it looks when I tried it on Android's media player, it's not actually consuming the InputStream using its read() methods, instead it only uses the InputStream as referenceOliana
You can actually use the NanoHttpd server code from the link in my question, and it should work well enough for you to stream videos from most players. My case was specific though, such that I needed to do some byte manipulation before writing out data to the client.Oliana
I've encountered exactly the same problem. Could you post the code anyway? I wonder what have you changed since I think my implementation has no flaws.Boracic
If you share your code I will upvote your answer.Already given star for your question.I will appreciate your help..Aam
If what you're done makes the NanoHttpd webserver a better media streamer, feel free to submit code back to the project to improve things there.Tamatamable
L
15

In case others come across this and want to see what the actual code is in this solution, I'm posting my code here. I'm using my Android device to stream a video file from the SD card for a Chromecast request. Using this code, I'm able to start the stream in the middle and/or seek to a specific location in the stream.

@Override
@SuppressWarnings("deprecation")
public Response serve(String uri, Method method, Map<String, String> headers, Map<String, String> params, Map<String, String> files) {
    String mimeType = getMimeType();
    String currentUri = getCurrentUri();
    if (currentUri != null && currentUri.equals(uri)) {
        String range = null;
        Log.d(TAG, "Request headers:");
        for (String key : headers.keySet()) {
            Log.d(TAG, "  " + key + ":" + headers.get(key));
            if ("range".equals(key)) {
                range = headers.get(key);
            }
        }
        try {
            if (range == null) {
                return getFullResponse(mimeType);
            } else {
                return getPartialResponse(mimeType, range);
            }
        } catch (IOException e) {
            Log.e(TAG, "Exception serving file: " + filePath, e);
        }
    } else {
        Log.d(TAG, "Not serving request for: " + uri);
    }

    return new Response(Response.Status.NOT_FOUND, mimeType, "File not found");
}

private Response getFullResponse(String mimeType) throws FileNotFoundException {
    cleanupStreams();
    fileInputStream = new FileInputStream(filePath);
    return new Response(Response.Status.OK, mimeType, fileInputStream);
}

private Response getPartialResponse(String mimeType, String rangeHeader) throws IOException {
    File file = new File(filePath);
    String rangeValue = rangeHeader.trim().substring("bytes=".length());
    long fileLength = file.length();
    long start, end;
    if (rangeValue.startsWith("-")) {
        end = fileLength - 1;
        start = fileLength - 1
                - Long.parseLong(rangeValue.substring("-".length()));
    } else {
        String[] range = rangeValue.split("-");
        start = Long.parseLong(range[0]);
        end = range.length > 1 ? Long.parseLong(range[1])
                : fileLength - 1;
    }
    if (end > fileLength - 1) {
        end = fileLength - 1;
    }
    if (start <= end) {
        long contentLength = end - start + 1;
        cleanupStreams();
        fileInputStream = new FileInputStream(file);
        //noinspection ResultOfMethodCallIgnored
        fileInputStream.skip(start);
        Response response = new Response(Response.Status.PARTIAL_CONTENT, mimeType, fileInputStream);
        response.addHeader("Content-Length", contentLength + "");
        response.addHeader("Content-Range", "bytes " + start + "-" + end + "/" + fileLength);
        response.addHeader("Content-Type", mimeType);
        return response;
    } else {
        return new Response(Response.Status.RANGE_NOT_SATISFIABLE, HTML_MIME_TYPE, rangeHeader);
    }
}
Lout answered 12/5, 2014 at 23:13 Comment(3)
can you please update this answer with complete server implementation?Hynes
Wow, this is perfect code!. It is working as expected while use VideoView, but i wanted to play video using MediaExtractor. Can you please help me with client side code ?Flatt
Thanks, this helped me implement this in my custom version of NanoHTTPDPink
T
5

More of an FYI than anything, but, the newest version of NanoHttpd (available at http://github.com/NanoHttpd/nanohttpd) has been optimized to better support large uploads with a reduced memory footprint. The code you're using holds the incoming upload in memory, the newer version writes to disk. Check it out and see if it might solve memory issues.

Tamatamable answered 1/6, 2013 at 19:44 Comment(1)
great! thanks for the heads-up. will look into this when i get back to my desk.Oliana
P
4

This is a issue due to HTTP header not initialized, so if user call GET with range request firstly, then call another GET request without range field, the previous range field will still keep there, but actually the second GET request don't expected to read it from the range.

Android MediaPlayer is such case for mp4 file with moov box at the end, which will cause read data actually not we want.

To resolve this issue, you can try below patch:

diff --git a/core/src/main/java/fi/iki/elonen/NanoHTTPD.java b/core/src/main/java/fi/iki/elonen/NanoHTTPD.java
index ce292a4..aba21c4 100644
--- a/core/src/main/java/fi/iki/elonen/NanoHTTPD.java
+++ b/core/src/main/java/fi/iki/elonen/NanoHTTPD.java
@@ -1039,6 +1039,7 @@ public abstract class NanoHTTPD {
          */
         private void decodeHeader(BufferedReader in, Map<String, String> pre, Map<String, String> parms, Map<String, St
             throws ResponseException {
+            headers.put("range","bytes=0-");
             try {
                 // Read the request line
                 String inLine = in.readLine();

With this fix,it works fine for me on android 5.0 device.

Portulaca answered 15/12, 2014 at 4:36 Comment(1)
This is great answer. I used NanoHTTPD to stream video, it works fine in many phones, but did not work for Samsung S4. This fix helped me, thanksSpeedboat
O
2

Solved my own problem. The issue is that the MediaPlayer(s) (wmp, vlc, android player), issue a GET request with a specified RANGE. Served that request correctly, problem solved.

Oliana answered 6/3, 2012 at 7:8 Comment(6)
Would you mind sharing your code? I'm looking to create an app that streams videos from an InputStream, and I'd love to see if I can get any pointers from your code. I'd be happy to award you a bounty for your help. Thanks in advance!Pricket
Streaming video from an InputStream was actually my first shot at the problem, but as it looks when I tried it on Android's media player, it's not actually consuming the InputStream using its read() methods, instead it only uses the InputStream as referenceOliana
You can actually use the NanoHttpd server code from the link in my question, and it should work well enough for you to stream videos from most players. My case was specific though, such that I needed to do some byte manipulation before writing out data to the client.Oliana
I've encountered exactly the same problem. Could you post the code anyway? I wonder what have you changed since I think my implementation has no flaws.Boracic
If you share your code I will upvote your answer.Already given star for your question.I will appreciate your help..Aam
If what you're done makes the NanoHttpd webserver a better media streamer, feel free to submit code back to the project to improve things there.Tamatamable

© 2022 - 2024 — McMap. All rights reserved.