Search code examples
androidinputstreamandroid-mediaplayernanohttpd

How to download and stream audio in Android?


I'm trying to implement a feature in an app I'm making which enables the user to listen to an online stream. What I need to do is to download the file along with playing it.

I've figured out so much that I need a local HTTP server which I used NanoHTTPD. Now the tricky part is how to actually download and stream the audio at the same time.

This is the code I've came up with so far:

public class LocalHttpServer extends NanoHTTPD {
    public static final int SERVER_PORT = 5987;
    private String mUrl;
    private InputStream input;
    private FileOutputStream output;

    public LocalHttpServer(String url) {
        super(SERVER_PORT);
        mUrl = url;
    }

    private File createFile(String url) {
        File path = new File(MyApplication.getContext().getFilesDir(), "audio/");
        path.mkdirs();

        return new File(path, Util.md5(url));
    }

    @Override
    public Response serve(IHTTPSession session) {
        input = null;
        output = null;
        HttpURLConnection connection = null;
        try {
            URL url = new URL(mUrl);
            connection = (HttpURLConnection) url.openConnection();
            connection.connect();
            if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
                return new Response(Response.Status.BAD_REQUEST, "audio/mpeg3", null, 0);
            }
            int fileLength = connection.getContentLength();

            input = connection.getInputStream();
            output = new FileOutputStream(createFile(mUrl));
            new Thread(new Runnable() {
                @Override
                public void run() {
                    byte data[] = new byte[4096];
                    int count;
                    try {
                        while ((count = input.read(data)) != -1) {
                            output.write(data, 0, count);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            if (output != null)
                                output.close();
                            if (input != null)
                                //input.close(); don't close it
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
            return new Response(Response.Status.OK, "audio/mpeg3", input, fileLength);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new Response(Response.Status.BAD_REQUEST, "audio/mpeg3", null, 0);
    }
}

The problem with it is when fed to a MediaPlayer, the unexpected end of stream exception occurs.


Solution

  • Since nobody posted an answer to my question I bring here another solution which I used to accomplish a somewhat similar task.

    I figured I could use an AsyncTask to download file and when the download reached the 10% of total start the MediaPlayer using interface callbacks. And along the update the MediaPlayer whenever another 20% has been downloaded.

    Here is the source for the said AsyncTask: https://gist.github.com/2hamed/63a31bd55fc6514d12b5

    public class DownloadAndPlayAsyncTask extends AsyncTask<String, Integer, Integer> {
        private static final String TAG = "DownloadAndPlayAsync";
        DownloadCallback downloadCallback;
        File tempFile, fullFile;
    
        private void createFiles(String url) {
            tempFile = Util.getTempFilePath(url);
            fullFile = Util.getFilePath(url);
        }
    
        public void setOnDownloadCallback(DownloadCallback callback) {
            downloadCallback = callback;
        }
    
        @Override
        protected Integer doInBackground(String... strings) {
    
            if (Util.isFileDownloaded(strings[0])) {
                createFiles(strings[0]);
                return 1;
            }
            InputStream input = null;
            OutputStream output = null;
            HttpURLConnection connection = null;
            try {
                URL url = new URL(strings[0]);
                connection = (HttpURLConnection) url.openConnection();
                connection.connect();
    
                // expect HTTP 200 OK, so we don't mistakenly save error report
                // instead of the file
                if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
                    Log.d(TAG, "Server returned HTTP " + connection.getResponseCode()
                            + " " + connection.getResponseMessage());
                    return -1;
                }
    
                // this will be useful to display download percentage
                // might be -1: server did not report the length
                int fileLength = connection.getContentLength();
                createFiles(strings[0]);
                // download the file
                input = connection.getInputStream();
                output = new FileOutputStream(tempFile);
    
                byte data[] = new byte[4096];
                long total = 0;
                int count;
                while ((count = input.read(data)) != -1) {
                    // allow canceling with back button
                    if (isCancelled()) {
                        input.close();
                        return null;
                    }
                    total += count;
                    // publishing the progress....
                    if (fileLength > 0) // only if total length is known
                        publishProgress((int) (total * 100 / fileLength));
                    output.write(data, 0, count);
                }
            } catch (Exception e) {
                e.printStackTrace();
                return -1;
            } finally {
                try {
                    if (output != null)
                        output.close();
                    if (input != null)
                        input.close();
                } catch (IOException ignored) {
                }
    
                if (connection != null)
                    connection.disconnect();
            }
            return 0;
        }
    
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }
    
        @Override
        protected void onPostExecute(Integer result) {
            super.onPostExecute(result);
            if (result == 0) {
                try {
                    Util.copy(tempFile, fullFile);
                    tempFile.delete();
                    if (downloadCallback != null) {
                        downloadCallback.onFinished(fullFile.getAbsolutePath());
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } else if (result == 1) {
                if (downloadCallback != null) {
                    downloadCallback.onFinished(fullFile.getAbsolutePath());
                }
            } else {
                if (downloadCallback != null) {
                    downloadCallback.onFailed();
                }
            }
        }
    
        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            if (downloadCallback != null) {
                downloadCallback.onProgressUpdate(values[0], tempFile.getAbsolutePath());
            }
        }
    
        @Override
        protected void onCancelled(Integer result) {
            super.onCancelled(result);
        }
    
        @Override
        protected void onCancelled() {
            super.onCancelled();
        }
    
        public interface DownloadCallback {
            void onProgressUpdate(int progress, String filePath);
    
            void onFinished(String fullFile);
    
            void onFailed();
        }
    }
    

    if you notice there is the DownloadCallback at the end of the code.