获取媒体播放器缓存访问权限

18

我想在完全加载完成后将渐进式流式传输的mp3文件移动到SD卡中。有没有实现这一目标的办法。

我发现 MediaPlayer 在渐进式流式传输时会完全下载整个文件,然后我们可以跳转到文件的任何部分。我希望将完全流式传输的文件移动到外部存储器中,以便未来回放不浪费数据和电池。


2
最佳答案在这里,如果还有人在寻找解决方案: https://dev59.com/nGfWa4cB1Zd3GeqPhXut#12044709 - Taras
3个回答

19

这个想法是创建一个代理,让媒体播放器可以从中读取数据,而不是直接从网络读取数据。

我使用了danikula/AndroidVideoCache,它非常简单易用。我用它来处理音频而不是视频,但其原理是一样的。


2
这应该是正确的答案。直接易用。我希望我能给10个赞。 - MetaSnarf
我该如何加密缓存文件? - vishal patel

11

对原帖的评论指出了正确的方向,但我认为稍微详细地解释一下可能会更有帮助...

我所做的是使用Apache HTTP库构建一个轻量级代理服务器。应该有很多例子可以获得这部分的基础知识。为MediaPlayer提供适当的本地主机URL,以便它打开到您的代理的套接字。当MediaPlayer发出请求时,使用代理发送一个等效请求到实际的媒体主机。您将在代理的packetReceived方法中收到byte[]数据,我使用它来构建一个HttpGet并使用AndroidHttpClient将其发送出去。

您将收到HttpResponse,并且可以使用其中的HttpEntity来访问流式传输的字节数据。我正在使用一个ReadableByteChannel,如下所示:

HttpEntityWrapper entity = (HttpEntityWrapper)response.getEntity();
ReadableByteChannel src = Channels.newChannel(entity.getContent());

当您读取数据时,您可以随意处理它(例如将其缓存到SD卡上的文件中)。要将正确的内容传递给MediaPlayer,请从客户端Socket获取SocketChannel,首先直接将响应头写入该通道,然后继续写实体的字节数据。我在while循环中使用NIO ByteBuffer(client是Socket,buffer是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
    }
}

在将响应传递给播放器之前,您可能需要更改响应中的主机标头,以便它看起来像是您的代理发送的。但我正在处理一个专有的MediaPlayer实现,所以行为可能会有所不同。希望这能帮到您。


2
你能分享一些更多的代码来创建代理并拦截数据吗? - anz
我们如何拦截MediaPlayer的请求? - anz

5

虽然已经很晚了,但我发现大多数人仍然需要一个解决方案。我的解决方案基于 JakeWharton's DiskLruCache

  • AsyncTask 用于读取文件或从网络下载并缓存它

  • Callback 用于从缓存中获取 InputStram/FileDescriptor

步骤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) {
        // https://dev59.com/HXRC5IYBdhLWcg3wVvct
        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();
    }
}

步骤2:
public interface OnCacheCallback {

    void onSuccess(FileInputStream stream);

    void onError();
}

示例

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

注意:

这是针对音频文件缓存的测试样例,虽然经过测试但还比较粗糙,如果您发现任何问题请通知我:)


你是指这个 // Application class where i did open DiskLruCache 是什么意思? - Naz141
在您的Application类(扩展为Application)或任何您想要的地方初始化DiskLruCache :) - Haris Qurashi
如果您能向我展示您编写的DiskLruCache初始化代码片段,谢谢。 - Naz141
2
DiskLruCache diskLruCache = DiskLruCache.open(getCacheDir(), 1, 1, 50 * 1024 * 1024); - Haris Qurashi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接