How to download and stream audio in Android?

1.6k views Asked by At

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.

1

There are 1 answers

0
2hamed On BEST ANSWER

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.