ExoPlayer的缓存-- 四 Cache 的使用
文章目录
缓存ID
默认的CacheID
同一文件 内容服务器 生成的链接有时效性,如果用URL做缓存ID,会出现同一首无法命中缓存的情况。Exoplayer 的缓存默认使用 URL
public static final CacheKeyFactory DEFAULT_CACHE_KEY_FACTORY =
(dataSpec) -> dataSpec.key != null ? dataSpec.key : generateKey(dataSpec.uri);
每首歌曲有一个SongID, 如果使用SongID, 每首歌曲的有伴奏 原唱 视频三种文件,也有冲突的可能。
因此使用URL 的path 作为SongID 是比较合适的。
改造后的CacheID
Exoplayer 在创建CacheDataSourceFactory 的时候 传入CacheKeyFactory
return new CacheDataSourceFactory(
cache,
new DefaultHttpDataSourceFactory("user-agent"),
new FileDataSource.Factory(),
new CacheDataSinkFactory(cache, CacheDataSink.DEFAULT_FRAGMENT_SIZE),
CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR, null,
new CacheKeyFactory() {
@Override
public String buildCacheKey(DataSpec dataSpec) {
return dataSpec.key != null ? dataSpec.key : dataSpec.uri.getLastPathSegment();
}
});
}
缓存的时候 生成DownloadRequest 直接使用URL 的path 最为ID参数传入
DownloadRequest request = CacheDownloadManager.buildDownloadRequest(uri.getLastPathSegment(), uri);
加密文件的处理
缓存下载 DefaultHttpDataSourceFactory
K歌播放的文件时加密类型的,在缓存的时候 直接使用DefaultHttpDataSourceFactory 下载流。
因此缓存的文件是加密的。
private static synchronized HttpDataSource.Factory getHttpDataSourceFactory( ) {
if (sHttpDataSourceFactory == null) {
CookieManager cookieManager = new CookieManager();
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
CookieHandler.setDefault(cookieManager);
sHttpDataSourceFactory = new DefaultHttpDataSourceFactory("user-agent");
}
return sHttpDataSourceFactory;
}
在播放的时候需要先解密数据,使用 CryptoHttpDataSourceFactory,但是CryptoHttpDataSourceFactory 使用缓存后保存在缓存中的是解密后的数据,播放中导致数据无法识别。
CryptoWrapDataSource
那能不能在播放的时候 送到播放器的数据解密但是下载保存的时候是原始数据呢。Exoplayer 的DataSource 设计使用了装饰模式,参考这个思路,在CacheDataSource 外面再装饰一层解密的CryptoWrapDataSource。 这样Cache 缓存的时候是加密的数据,送到Exoplayer 的CryptoWrapDataSource 是解密以后的数据。
return new ProgressiveMediaSource.Factory(
new CryptoWrapDataSource.Factory(buildCacheDataSourceFactory(cache)),
.createMediaSource(uri);
文件分片大小
默认的分片大小为5M,CacheDataSink.DEFAULT_FRAGMENT_SIZE
在生成DownloadManager 的时候,传入自定义参数,CacheDataSinkFactory 定义 分片大小。
DownloaderConstructorHelper downloaderConstructorHelper = new DownloaderConstructorHelper(
getDownloadCache(context),
getHttpDataSourceFactory(),
null,
new CacheDataSinkFactory(
getDownloadCache(context),
(sParameters != null && sParameters.mSegmentSize > 0) ? sParameters.mSegmentSize:CacheDataSink.DEFAULT_FRAGMENT_SIZE
),
null
);
DefaultDownloaderFactory downloaderFactory = new DefaultDownloaderFactory(downloaderConstructorHelper);
sDownloadManager = new DownloadManager(
context,
new DefaultDownloadIndex(getDatabaseProvider(context)),
downloaderFactory
);
下载网速的计算
在Exoplayer 的官方文档中 对下载速度的 需要通过DownloadManager 定期查询。DownloadService 支持Notification 通知,默认实现了一个 ForegroundNotificationUpdater 定期通知 Notification。
下载进度更新不会触发对
DownloadManager.Listener
. 要更新显示下载进度的 UI 组件,您应该DownloadManager
以所需的更新速率定期查询。DownloadService
包含一个示例,它会定期更新服务前台通知。
参考这个实现 实现我们的网速监听。
注册自定义的DownloadManagerListener
mDownloadManagerListener = new DownloadManagerListener(mHandlerListener);
CacheDownloadService.getDownloadManager(mContext).addListener(mDownloadManagerListener);
onDownloadChanged 中监听是否有下载开始,如果有下载开始,开始抛出消息计算网速。
private class DownloadManagerListener implements com.google.android.exoplayer2.offline.DownloadManager.Listener {
private Handler mHandler;
public DownloadManagerListener(Handler handler){
mHandler = handler;
}
@Override
public void onInitialized(com.google.android.exoplayer2.offline.DownloadManager downloadManager) {
mDownloadManager = downloadManager;
initHandler();
}
@Override
public void onDownloadChanged(com.google.android.exoplayer2.offline.DownloadManager downloadManager, Download download) {
if(mHandler != null){
if (download.state == Download.STATE_DOWNLOADING) {
Message message = Message.obtain();
message.what = MSG_START;
message.obj = download;
if (download.contentLength > 0) {
mHandler.sendMessage(message);
}
} else if (download.state == Download.STATE_COMPLETED) {
Message message = Message.obtain();
message.what = MSG_FINISH;
message.obj = download;
mHandler.sendMessage(message);
}
}
}
initHandler() 中 初始化一个线程 用于处理消息和和计算网速
private void initHandler(){
mHandlerThreadListener = new HandlerThread("DownloadManagerListener");
mHandlerThreadListener.start();
mHandlerListener = new Handler(mHandlerThreadListener.getLooper(), new Handler.Callback(){
@Override
public boolean handleMessage(@NonNull Message msg) {
switch (msg.what) {
case MSG_START: {
Download download = (Download) msg.obj;
DownloadInfo downloadInfo = mHashDownloadInfo.get(download.request.id);
if (downloadInfo == null) {
downloadInfo = new DownloadInfo();
downloadInfo.id = download.request.id;
downloadInfo.updateTimeMs = download.startTimeMs;
downloadInfo.bytesDownloaded = download.getBytesDownloaded();
mHashDownloadInfo.put(downloadInfo.id, downloadInfo);
}
if (!mRunning) {
mRunning = true;
mHandlerListener.sendEmptyMessage(MSG_UPDATE);
}
}
break;
case MSG_UPDATE: {
List<Download> downloadList = mDownloadManager.getCurrentDownloads();
if (downloadList != null && downloadList.size() > 0) {
long nowMs = Clock.DEFAULT.elapsedRealtime();
for (Download dl : downloadList) {
DownloadInfo downloadInfo = mHashDownloadInfo.get(dl.request.id);
long speed = (dl.getBytesDownloaded() - downloadInfo.bytesDownloaded)
/ ((nowMs - downloadInfo.updateTimeMs) / 1000);
downloadInfo.updateTimeMs = nowMs;
downloadInfo.bytesDownloaded = dl.getBytesDownloaded();
for (NetworkSpeedListener li : sListener) {
li.onProgress(dl, speed, dl.getPercentDownloaded());
}
}
mHandlerListener.sendEmptyMessageDelayed(MSG_UPDATE, 1000);
} else {
mRunning = false;
}
}
break;
case MSG_FINISH:
Download download = (Download) msg.obj;
DownloadInfo downloadInfo = mHashDownloadInfo.get(download.request.id);
long nowMs = Clock.DEFAULT.elapsedRealtime();
long speed = (download.getBytesDownloaded() - downloadInfo.bytesDownloaded )
/ (nowMs - downloadInfo.updateTimeMs);
mHashDownloadInfo.remove(((Download) msg.obj).request.id);
for (NetworkSpeedListener li : sListener) {
li.onProgress((Download) msg.obj, speed, download.getPercentDownloaded());
}
break;
}
return false;
}
});
}
下载参数的设置 Builder 设计模式 的使用
下载参数的设置使用了 Builder 设计模式
定义基类 ParametersBase
- 基类中定义 各种属性
- 基类中定义 setXXX
- 默认构造函数ParametersBase(ParametersBase base) 这样变量的 赋值 操作都放在 基类中操作,做到统一
class ParametersBase {
protected long mMaxCacheSize = 512 * 1025 * 1025;
protected long mSegmentSize = mMaxCacheSize;
protected String mCachePath;
protected int mMaxParallelDownloads;
ParametersBase(ParametersBase base){
this.mMaxCacheSize = base.mMaxCacheSize;
this.mSegmentSize = base.mSegmentSize;
this.mCachePath = base.mCachePath;
this.mMaxParallelDownloads = base.mMaxParallelDownloads;
}
ParametersBase() {
}
......
}
Builder 类
- Builder 类中增加 setXXX 函数
- build 函数。 build 函数中 直接使用基类构造函数 生成 CacheDownloadParameters
public static class Builder extends ParametersBase{
public Builder(){
super();
}
public Builder(ParametersBase base) {
super(base);
}
......
public CacheDownloadParameters build(){
CacheDownloadParameters parameters = new CacheDownloadParameters(this);
return parameters;
}
CacheDownloadParameters
- 继承基类构造函数
- buildUpon 函数 使用 基类构造函数 生成
public class CacheDownloadParameters extends ParametersBase {
private CacheDownloadParameters(ParametersBase base) {
super(base);
}
public Builder buildUpon(){
return new Builder(this);
}
}