本工程 forked from danikula/AndroidVideoCache,版本2.7.1java
由於項目須要,在原ijkplayer播放器的基礎上要加入緩存功能,在調研了一番發現目前比較好的方案就是本地代理方案,其中danikula/AndroidVideoCache最爲出名。可是AndroidVideoCache上面掛了2k+的issues,而且上一次的更新更是在半年前了。因此爲告終合項目實際以及目前已知的問題,針對danikula/AndroidVideoCache作了些定製化優化。git
原 danikula/AndroidVideoCache README 看這裏github
下面會分幾點說下本身的定製優化之處。緩存
AndroidVideoCache會一直鏈接網絡下載數據,直到把數據下載徹底,而且拖動要超過當前已部分緩存的大於當前視頻已緩存大小加上視頻文件的20%,纔會走不緩存分支,而且原來的緩存下載不會當即中止。這樣就形成一個問題,當前用戶若是網絡環境不是足夠好或者當前視頻文件自己比較大時,拖動到沒有緩存的地方須要比較久纔會播放。針對這一點因此作了本身的優化。 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
分支時則會調用父類中的 pauseCache(true);
方法,將父類中stopped標記爲true,中止從代理緩存中返回數據給播放器。具體能夠查看HttpProxyCache
和ProxyCache
兩個類。dom
AndroidVideoCache是依賴於播放器的,因此針對這個侷限進行了修改。離線緩存說白了就是提早下載,不管視頻是否下載完成,均可以將這提早下載好的部分做爲視頻緩存使用。這裏對於下載不在具體展開,下載功能如何實現自行尋找合適的庫。下面對只下載了部分的視頻如何加入到本地代理中進行說明(所有已經下載好的視頻就不須要通過本地代理了) 這裏假設已部分下載的視頻文件後綴爲 .download;socket
添加一個可傳入本地具體路徑FileCache構造函數ide
//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);
}
複製代碼
添加一個可傳入本地視頻文件的HttpProxyCacheServerClients構造函數,大部分修改都有註釋,因此再也不做額外解釋了。優化
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;
}
複製代碼
對,就是這麼簡單,本地部分下載的視頻文件就能夠做爲視頻的緩存了,而且在播放視頻的時候,視頻能夠繼續緩存,將數據續寫到本地部分下載的視頻文件。
這個是咱們的項目須要,對高清以上的高碼率視頻纔去緩存,低碼率視頻則直接在線播放。這部分須要藉助播放器自己的能力。這裏以IjkPlayer爲例,在onPrepare方法中調用HttpProxyCacheServer暴露出來的cancelCache(mVideoUrl)
,實際上是將HttpProxyCache中isForceCancel屬性置爲true,在seekTo以後從新發起代理請求,這時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會從新發起請求本地代理,cancelCache後將不會走緩存分支
if (lastWatchPosition==-1){
seekTo(1);
}else {
seekTo(lastWatchPosition);
}
}
if (mPreparedListener != null) {
mPreparedListener.onPrepared(this);
}
...
}
複製代碼
其他部分修改很少,也不重要,就不細說了。值得一提的是清除了slf4j依賴,全部日誌部分均使用Andrdoid自帶的Log來輸入日誌。