AndroidVideoCache優(yōu)化

本工程 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ù)給播放器急侥。具體可以查看HttpProxyCacheProxyCache兩個類。

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來輸入日志。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末神凑,一起剝皮案震驚了整個濱河市净神,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌溉委,老刑警劉巖鹃唯,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異薛躬,居然都是意外死亡俯渤,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門型宝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來八匠,“玉大人絮爷,你說我怎么就攤上這事±媸鳎” “怎么了坑夯?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵桃犬,是天一觀的道長氧映。 經(jīng)常有香客問我,道長懊直,這世上最難降的妖魔是什么指巡? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任淑履,我火速辦了婚禮,結果婚禮上藻雪,老公的妹妹穿的比我還像新娘秘噪。我一直安慰自己,他們只是感情好勉耀,可當我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布指煎。 她就那樣靜靜地躺著,像睡著了一般便斥。 火紅的嫁衣襯著肌膚如雪至壤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天枢纠,我揣著相機與錄音像街,去河邊找鬼。 笑死晋渺,一個胖子當著我的面吹牛宅广,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播些举,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼俭厚!你這毒婦竟也來了户魏?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤挪挤,失蹤者是張志新(化名)和其女友劉穎叼丑,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扛门,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡鸠信,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了论寨。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片星立。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡爽茴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出绰垂,到底是詐尸還是另有隱情室奏,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布劲装,位于F島的核電站胧沫,受9級特大地震影響,放射性物質發(fā)生泄漏占业。R本人自食惡果不足惜绒怨,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望谦疾。 院中可真熱鬧南蹂,春花似錦、人聲如沸餐蔬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽樊诺。三九已至仗考,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間词爬,已是汗流浹背秃嗜。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留顿膨,地道東北人锅锨。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像恋沃,于是被迫代替她去往敵國和親必搞。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,500評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 原文鏈接http://www.cnblogs.com/kenshincui/p/4186022.html 音頻在i...
    Hyman0819閱讀 21,720評論 4 74
  • 發(fā)現(xiàn) 關注 消息 iOS 第三方庫、插件底燎、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,119評論 4 61
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,111評論 1 32
  • 管理者對于表揚和批評要有度刃榨,表揚時一定要是對于某種行為進行表揚弹砚,讓員工知道自己的行為對大家有什么好的影響,批...
    孫倩閱讀 257評論 0 0
  • 年末喇澡,回到了外面寒風刺骨迅栅,但室內(nèi)溫暖如春的東北小城。上海的冬天讓我心有余悸晴玖,在室內(nèi)開著空調(diào)读存,兩只腳仍舊凍得沒...
    阿卞閱讀 391評論 0 1