ExoPlayer缓存与数据加载逻辑
Exoplayer android 缓存
在ExoPlayer当中的数据获取,无论是预缓存,还是直接获取播放数据,逻辑都是统一的,具体的不同操作,是通过包装不同的DataSource来实现的。
1、DataSource & DataSink 流操作组件
1.1、Basic Info
DataSource: 类型:interface, 官方释义:
A component from which streams of data can be read.
读取流数据的组件
public interface DataSource {
void addTransferListener(TransferListener transferListener);
long open(DataSpec dataSpec) throws IOException;
int read(byte[] buffer, int offset, int readLength) throws IOException;
@Nullable Uri getUri();
void close() throws IOException;
}
DataSink: 类型:interface, 官方释义:
A component to which streams of data can be written.
(写入流数据组件)
public interface DataSink {
long open(DataSpec dataSpec) throws IOException;
int write(byte[] buffer, int offset, int readLength) throws IOException;
void close() throws IOException;
}
1.2、Some Implementation of DataSink
- CacheDataSink: open时根据传入DataSpec创建FileOutputStream,在read时进行写入。
1.3、Some implementations of DataSource
- DefaultHttpDataSource, 在open中使用httpUrlConnection打开一个网络流,在read中对网络流进行读取。
- OkHttpDataSource,与DefaultHttpDataSource功能一致,使用的网络框架是okHttp。
- TeeDataSource,在成员变量中包含一个dataSource对象,与一个DataSink, 可以在dataSource对象进行数据读取时,DataSink同步进行文件写入。
- CacheDataSource. 在有缓存时,使用缓存,在没有缓存时,使用网络DataSource。
- StatsDataSource: dataSource包装类,用于传输数据总体跟踪
- FileDataSource: 文件读取
2、ExoPlayer 预缓存逻辑(CacheUtils)
2.1、使用DataSource简析
使用的DataSource为CacheDataSource
public class CacheDataSource {
DataSource upstreamDataSource;
DataSource cacheWriteDataSource;
DataSource cacheReadDataSource;
}
upstreamDataSource负责网络内容获取,cache中为DefaultHttpDataSource对象。
cacheReadDataSource负责缓存文件读取,cache中为FileDataSource对象。
cacheWriteDataSource一般持有一个upStream和一个DataSink对象,网络获取并且写入本地文件的操作由其统一调度, 这里一般为TeeDataSource
2.2、整体流程

整个读取不断重复dataSource.open dataSource.read过程
- 对于有缓存的情况,直接读取使用FileDataSource读取文件
- 对于已经有别的线程在做同样的缓存任务时,会直接使用DefaultHttpDataSource读取,并且只读取不存储
- 对于没有缓存,并且没有其他线程在做缓存任务时,使用TeeDataSource,内部使用DefaultHttpDataSource获取线上数据,并使用CacheDataSink写入缓存文件中
3、播放视频时数据加载
播放视频时数据加载核心逻辑也在CacheDataSource,关键数据加载的核心逻辑是完全复用的。
3.1、CacheDataSource在视频播放加载时与cache时的差异
- 使用框架维护的单线程线程池
- 网络数据获取使用的是OkHttpDataSource
- CacheDataSource被再包装了一层,在read是获取到byte数组由render等进行消费并渲染数据。
- 在上面流程图的第二种情况,当相同的视频有别的任务正在进行缓存时,由于com.shopee.sz.mmsplayer.player.exoplayer.config.ExoPlayerConfig#createCacheSource 设置了CacheDataSource.FLAG_BLOCK_ON_CACHE。 在第二种情景时会block阻塞,直到之前的缓存任务结束唤醒该线程进行数据读取。 可能引发问题:由于缓存大小最大为300k,不会超过fragmentSize, 所以播放时要完全等到这300k缓存完成,理论上可以不等到300k完全缓存完成就去渲染首帧。
4、ExoPlayer内部缓存任务优先级编排支持
当前我们使用的是2.10.8版本,维护一个优先级队列,只有队列中的优先级最高的任务可执行,优先级低于最高优先级的任务会被阻塞,当任务完成后我们主动去操作优先级队列,这时会尝试唤醒能运行的任务。 这里要注意这里是任务所在线程阻塞,所以线程池的线程数需要大一些。
5、结论
- ExoPlayerConfig在创建CacheDataSourceFactory时可将 CacheDataSource.FLAG_BLOCK_ON_CACHE标志位去掉,防止在网络极端的情况由于缓存慢首帧渲染被阻塞。 去掉该标志位可能会造成播放该视频时没法边播边缓存,在回放跟返回上一个视频时需要重新加载。
- 缓存任务可取消,目前已支持,若视频被划走后,如有缓存任务正在进行,建议马上把对应的缓存任务取消
优先级策略能否优化??????????? 待补充
上层可做缓存任务编排,可以优先缓存下一个视频。
这里有几个思考点,
a、一次缓存几个任务,
b、任务优先级以何种方式编排,当正在进行的缓存任务优先级变化如何调整等,
初步方案:维护缓存队列,一次最多运行n个缓存任务(n 结合具体业务场景分析), 。
a、对于正在播放的视频,若有对应缓存任务,马上停止,若在缓存队列中要从队列中移除。
b、对于划走的视频,若有缓存任务在进行,也马上停止,若在缓存队列中要从队列中移除。
- 当前使用的SZThreadPool.NetThread 线程池,核心线程数为3个,队列为无限队列,对于IO密集型任务,可将核心线程数调整为 CPU核心数 * 2