Question

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.

Was it helpful?

Solution 4

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.

OTHER TIPS

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);
    }
}

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.

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top