Getting access to media player cache
I want to move a progressively streamed mp3 file to sd card once it is completely loaded. Is there any way of achieving that.
I've seen that the MediaPlayer
completely downloads the whole file while progressive streaming and then we can seek to any part of file. I want to move a fully streamed file to external storage so that future playback do not waste data and battery.
The idea is to create a proxy that the media player can read from, instead of reading data directly from the web.
I used danikula/AndroidVideoCache which is very simple to build/use. I used it for Audio not Video, but its just the same.
The comment on the original post points you in the right direction, but I thought it may be helpful to expound a bit...
What I've done is build a lightweight proxy server using the Apache HTTP libraries. There should be plenty of examples out there to get the basics of this part. Provide the MediaPlayer an appropriate localhost URL so that it opens a socket to your proxy. When the MediaPlayer makes a request, use the proxy to send an equivalent request to the actual media host. You will receive byte[] data in the proxy's packetReceived method, which I use to build an HttpGet and send it on its way with AndroidHttpClient.
You will get back an HttpResponse and you can use the HttpEntity inside to access the streaming byte data. I'm using a ReadableByteChannel, like so:
HttpEntityWrapper entity = (HttpEntityWrapper)response.getEntity();
ReadableByteChannel src = Channels.newChannel(entity.getContent());
Do whatever you'd like with the data as you read it back (like cache it in a file on the SD card). To pass the correct stuff on to the MediaPlayer, get the SocketChannel from the client Socket, first write the response headers directly to that channel, and then proceed to write the entity's byte data. I'm using an NIO ByteBuffer in a while loop (client is a Socket and buffer is a ByteBuffer).
int read, written;
SocketChannel dst = client.getChannel();
while (dst.isConnected() &&
dst.isOpen() &&
src.isOpen() &&
(read = src.read(buffer)) >= 0) {
try {
buffer.flip();
// This is one point where you can access the stream data.
// Just remember to reset the buffer position before trying
// to write to the destination.
if (buffer.hasRemaining()) {
written = dst.write(buffer);
// If the player isn't reading, wait a bit.
if (written == 0) Thread.sleep(15);
buffer.compact();
}
}
catch (IOException ex) {
// handle error
}
}
You may need to alter the host header in the response before passing it along to the player so that it looks like your proxy is the sender, but I'm dealing with a proprietary implementation of the MediaPlayer so behaviour could be a bit different. Hope that helps.
Its late but i found that most of the people still need a solution. My solution based on JakeWharton's DiskLruCache. We need two things
AsyncTask to read file or download from network and cache it
Callback to get InputStram/FileDescriptor from cache
Step 1:
import android.content.Context;
import android.os.AsyncTask;
import org.apache.commons.io.IOUtils;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
// you can use FileDescriptor as
// extends AsyncTask<String, Void, FileDescriptor>
public class AudioStreamWorkerTask extends AsyncTask<String, Void, FileInputStream> {
private OnCacheCallback callback = null;
private Context context = null;
public AudioStreamWorkerTask(Context context, OnCacheCallback callback) {
this.context = context;
this.callback = callback;
}
@Override
protected FileInputStream doInBackground(String... params) {
String data = params[0];
// Application class where i did open DiskLruCache
DiskLruCache cache = MyApplication.getDiskCache(context);
if (cache == null)
return null;
String key = hashKeyForDisk(data);
final int DISK_CACHE_INDEX = 0;
long currentMaxSize = cache.getMaxSize();
float percentageSize = Math.round((cache.size() * 100.0f) / currentMaxSize);
if (percentageSize >= 90) // cache size reaches 90%
cache.setMaxSize(currentMaxSize + (10 * 1024 * 1024)); // increase size to 10MB
try {
DiskLruCache.Snapshot snapshot = cache.get(key);
if (snapshot == null) {
Log.i(getTag(), "Snapshot is not available downloading...");
DiskLruCache.Editor editor = cache.edit(key);
if (editor != null) {
if (downloadUrlToStream(data, editor.newOutputStream(DISK_CACHE_INDEX)))
editor.commit();
else
editor.abort();
}
snapshot = cache.get(key);
} else
Log.i(getTag(), "Snapshot found sending");
if (snapshot != null)
return (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
} catch (IOException e) {
e.printStackTrace();
}
Log.i(getTag(), "File stream is null");
return null;
}
@Override
protected void onPostExecute(FileInputStream fileInputStream) {
super.onPostExecute(fileInputStream);
if (callback != null) {
if (fileInputStream != null)
callback.onSuccess(fileInputStream);
else
callback.onError();
}
callback = null;
context = null;
}
public boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
HttpURLConnection urlConnection = null;
try {
final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
InputStream stream = urlConnection.getInputStream();
// you can use BufferedInputStream and BufferOuInputStream
IOUtils.copy(stream, outputStream);
IOUtils.closeQuietly(outputStream);
IOUtils.closeQuietly(stream);
Log.i(getTag(), "Stream closed all done");
return true;
} catch (final IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null)
IOUtils.close(urlConnection);
}
return false;
}
private String getTag() {
return getClass().getSimpleName();
}
private String hashKeyForDisk(String key) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(key.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
private String bytesToHexString(byte[] bytes) {
// http://stackoverflow.com/questions/332079
StringBuilder sb = new StringBuilder();
for (byte aByte : bytes) {
String hex = Integer.toHexString(0xFF & aByte);
if (hex.length() == 1)
sb.append('0');
sb.append(hex);
}
return sb.toString();
}
}
Step 2:
public interface OnCacheCallback {
void onSuccess(FileInputStream stream);
void onError();
}
Example
final String path = "http://www.example.com/test.mp3";
new AudioStreamWorkerTask (TestActivity.this, new OnCacheCallback() {
@Override
public void onSuccess(FileInputStream fileInputStream) {
Log.i(getClass().getSimpleName() + ".MediaPlayer", "now playing...");
if (fileInputStream != null) {
// reset media player here if necessary
mediaPlayer = new MediaPlayer();
try {
mediaPlayer.setDataSource(fileInputStream.getFD());
mediaPlayer.prepare();
mediaPlayer.setVolume(1f, 1f);
mediaPlayer.setLooping(false);
mediaPlayer.start();
fileInputStream.close();
} catch (IOException | IllegalStateException e) {
e.printStackTrace();
}
} else {
Log.e(getClass().getSimpleName() + ".MediaPlayer", "fileDescriptor is not valid");
}
}
@Override
public void onError() {
Log.e(getClass().getSimpleName() + ".MediaPlayer", "Can't play audio file");
}
}).execute(path);
Note:
This is tested but rough sample for audio file caching, there may be some problems if you find anything please inform me :)