本工程 forked from danikula/AndroidVideoCache,版本2.7.1
前言
因為項目需要瓦侮,在原ijkplayer播放器的基礎上要加入緩存功能袖迎,在調(diào)研了一番發(fā)現(xiàn)目前比較好的方案就是本地代理方案王污,其中danikula/AndroidVideoCache最為出名荔烧。但是AndroidVideoCache上面掛了2k+的issues曲掰,并且上一次的更新更是在半年前了。所以為了結合項目實際以及目前已知的問題符衔,針對danikula/AndroidVideoCache做了些定制化優(yōu)化。
原 danikula/AndroidVideoCache README 看這里
正題
下面會分幾點說下自己的定制優(yōu)化之處糟袁。
1.視頻拖動超過已緩存部分則停止緩存線程下載
AndroidVideoCache會一直連接網(wǎng)絡下載數(shù)據(jù)判族,直到把數(shù)據(jù)下載完全,并且拖動要超過當前已部分緩存的大于當前視頻已緩存大小加上視頻文件的20%项戴,才會走不緩存分支形帮,并且原來的緩存下載不會立即停止。這樣就造成一個問題周叮,當前用戶如果網(wǎng)絡環(huán)境不是足夠好或者當前視頻文件本身比較大時辩撑,拖動到?jīng)]有緩存的地方需要比較久才會播放。針對這一點所以做了自己的優(yōu)化仿耽。
sourceLength * NO_CACHE_BARRIER
用一個較小的常量值代替合冀,并且用戶拖動超過已緩存部分則停止緩存下載線程,使得帶寬可以用于從拖動點開始播放项贺,更快地加載出用戶所需要的部分君躺。
主要改動ProxyCache以及HttpProxyCache兩個文件
//HttpProxyCache.java
public void processRequest(GetRequest request, Socket socket) throws IOException, ProxyCacheException {
OutputStream out = new BufferedOutputStream(socket.getOutputStream());
String responseHeaders = newResponseHeaders(request);
out.write(responseHeaders.getBytes("UTF-8"));
long offset = request.rangeOffset;
if (!isForceCancel && isUseCache(request)) {
Log.i(TAG, "processRequest: responseWithCache");
pauseCache(false);
responseWithCache(out, offset);
} else {
Log.i(TAG, "processRequest: responseWithoutCache");
pauseCache(true);
responseWithoutCache(out, offset);
}
}
/**
* 是否強制取消緩存
*/
public void cancelCache() {
isForceCancel = true;
}
private boolean isUseCache(GetRequest request) throws ProxyCacheException {
long sourceLength = source.length();
boolean sourceLengthKnown = sourceLength > 0;
long cacheAvailable = cache.available();
// do not use cache for partial requests which too far from available cache. It seems user seek video.
long offset = request.rangeOffset;
//如果seek只是超出少許(這里設置為2M)仍然走緩存
return !sourceLengthKnown || !request.partial || offset <= cacheAvailable + MINI_OFFSET_CACHE;
}
...
private void responseWithCache(OutputStream out, long offset) throws ProxyCacheException {
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
int readBytes;
try {
while ((readBytes = read(buffer, offset, buffer.length)) != -1 && !stopped) {
out.write(buffer, 0, readBytes);
offset += readBytes;
}
out.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
這里對==isUseCache #800023==方法進行了修改,在只超出緩存一點點(這里設置成2M)就會停止緩存开缎,避免在線播放以及緩存下載兩個線程同時搶占帶寬晰洒,造成跳轉后需要比較長時間才會加載播放成功。
==responseWithCache #801e00==方法中對while加入stopped標記位判斷啥箭,當進入responseWithoutCache
分支時則會調(diào)用父類中的 pauseCache(true);
方法,將父類中stopped標記為true治宣,停止從代理緩存中返回數(shù)據(jù)給播放器急侥。具體可以查看HttpProxyCache
和ProxyCache
兩個類。
2.脫離播放器實現(xiàn)緩存(離線緩存)
AndroidVideoCache是依賴于播放器的侮邀,所以針對這個局限進行了修改坏怪。離線緩存說白了就是提前下載,無論視頻是否下載完成绊茧,都可以將這提前下載好的部分作為視頻緩存使用铝宵。這里對于下載不在具體展開,下載功能如何實現(xiàn)自行尋找合適的庫华畏。下面對只下載了部分的視頻如何加入到本地代理中進行說明(全部已經(jīng)下載好的視頻就不需要經(jīng)過本地代理了)
這里假設已部分下載的視頻文件后綴為 .download;
2.1 修改FileCache.java
添加一個可傳入本地具體路徑FileCache構造函數(shù)
//FileCache.java
public FileCache(String downloadFilePath) throws ProxyCacheException{
try {
this.diskUsage = new UnlimitedDiskUsage();
this.file = new File(downloadFilePath);
this.dataFile = new RandomAccessFile(this.file, "rw");
} catch (IOException e) {
throw new ProxyCacheException("Error using file " + file + " as disc cache", e);
}
}
加入了一種緩存文件格式鹏秋,則判斷是否緩存完成需要做相應的修改
@Override
public synchronized void complete() throws ProxyCacheException {
if (isCompleted()) {
return;
}
close();
String fileName;
if (file.getName().endsWith(DOWNLOAD_TEMP_POSTFIX)) {
//臨時下載文件
fileName = file.getName().substring(0, file.getName().length() - DOWNLOAD_TEMP_POSTFIX.length());
} else {
fileName = file.getName().substring(0, file.getName().length() - TEMP_POSTFIX.length());
}
File completedFile = new File(file.getParentFile(), fileName);
boolean renamed = file.renameTo(completedFile);
if (!renamed) {
throw new ProxyCacheException("Error renaming file " + file + " to " + completedFile + " for completion!");
}
file = completedFile;
try {
dataFile = new RandomAccessFile(file, "r");
diskUsage.touch(file);
} catch (IOException e) {
throw new ProxyCacheException("Error opening " + file + " as disc cache", e);
}
}
...
private boolean isTempFile(File file) {
return file.getName().endsWith(TEMP_POSTFIX)
|| file.getName().endsWith(DOWNLOAD_TEMP_POSTFIX);
}
2.2 修改HttpProxyCacheServerClients
添加一個可傳入本地視頻文件的HttpProxyCacheServerClients構造函數(shù),大部分修改都有注釋,所以不再作額外解釋了亡笑。
private FileCache mCache;
private String downloadPath=null;
public HttpProxyCacheServerClients(String url, Config config) {
this.url = checkNotNull(url);
this.config = checkNotNull(config);
this.uiCacheListener = new UiListenerHandler(url, listeners);
}
public void processRequest(GetRequest request, Socket socket) {
try {
startProcessRequest();
clientsCount.incrementAndGet();
proxyCache.processRequest(request, socket);
} catch (Exception e) {
e.printStackTrace();
if (e instanceof ProxyCacheException){
uiCacheListener.onCacheError(e);
}
} finally {
finishProcessRequest();
}
}
...
private synchronized void startProcessRequest() throws ProxyCacheException {
if (proxyCache == null){
if (downloadPath==null){
//原proxyCache
proxyCache=newHttpProxyCache();
}else{
//本地已部分下載的視頻文件作為緩存
newHttpProxyCacheForDownloadFile(downloadPath);
}
}
if (isCancelCache){
proxyCache.cancelCache();
}
}
......
public void shutdown() {
listeners.clear();
if (proxyCache != null) {
proxyCache.registerCacheListener(null);
proxyCache.shutdown();
proxyCache = null;
}
clientsCount.set(0);
//清除不必要的緩存
if (mCache != null && isCancelCache && downloadPath == null) {
mCache.file.delete();
}
}
/**
* 生成以已部分下載的視頻為基礎的緩存文件
* @param downloadFilePath
* @return
* @throws ProxyCacheException
*/
private void newHttpProxyCacheForDownloadFile(String downloadFilePath) throws ProxyCacheException {
HttpUrlSource source = new HttpUrlSource(url, config.sourceInfoStorage, config.headerInjector);
mCache = new FileCache(downloadFilePath);
HttpProxyCache httpProxyCache = new HttpProxyCache(source, mCache);
httpProxyCache.registerCacheListener(uiCacheListener);
proxyCache = httpProxyCache;
}
對侣夷,就是這么簡單,本地部分下載的視頻文件就可以作為視頻的緩存了仑乌,并且在播放視頻的時候百拓,視頻可以繼續(xù)緩存琴锭,將數(shù)據(jù)續(xù)寫到本地部分下載的視頻文件。
3.高碼率緩存衙传,低碼率不緩存
這個是我們的項目需要,對高清以上的高碼率視頻才去緩存腺阳,低碼率視頻則直接在線播放落君。這部分需要借助播放器本身的能力。這里以IjkPlayer為例亭引,在onPrepare方法中調(diào)用HttpProxyCacheServer暴露出來的cancelCache(mVideoUrl)
绎速,其實是將HttpProxyCache中isForceCancel屬性置為true纹冤,在seekTo之后重新發(fā)起代理請求,這時isForceCancel=true,將不會走緩存分支购公,而是在線播放知残。具體過程看源代碼。
public void onPrepared(IMediaPlayer mp) {
...
if ( !isLocalVideo && bitrate < MINI_BITRATE_USE_CACHE
&& mCacheManager.getDownloadTempPath(mVideoUrl)==null)
{
bufferPoint = -1;
mOnBufferUpdateListener.update(this, -1);
mCacheManager.cancelCache(mVideoUrl);
//注意:seekTo會重新發(fā)起請求本地代理比庄,cancelCache后將不會走緩存分支
if (lastWatchPosition==-1){
seekTo(1);
}else {
seekTo(lastWatchPosition);
}
}
if (mPreparedListener != null) {
mPreparedListener.onPrepared(this);
}
...
}
4.其余小修改
其余部分修改不多求妹,也不重要,就不細說了佳窑。值得一提的是清除了slf4j依賴制恍,所有日志部分均使用Andrdoid自帶的Log來輸入日志。