鴻蒙開源第三方組件——VideoCache視頻緩存組件

目錄:android

一、組件效果圖展現git

二、Sample解析github

三、Library解析數據庫

四、《鴻蒙開源第三方組件》系列文章合集設計模式

前言緩存

    基於安卓平臺的視頻緩存組件VideoCache( https://github.com/danikula/AndroidVideoCache),實現了鴻蒙化遷移和重構,代碼已經開源到(https://gitee.com/isrc_ohos/android-video-cache_ohos),歡迎各位下載使用並提出寶貴意見!服務器

背景網絡

       用戶在網速波動較大的環境下瀏覽視頻時,常常會遇到因爲網速較慢引發的持續加載或播放失敗的狀況。VideoCache組件實現了視頻緩存功能,播放視頻的同時,對視頻源進行緩存。出現網速較慢的狀況時,手機讀取提早緩存好的視頻數據,能夠保證視頻的正常播放,給予用戶更流暢的觀看體驗。app

組件效果圖展現dom

一、主菜單界面: 視頻播放

       安裝軟件後,只須要在鴻蒙設備上單擊HarmonyVideoCache軟件圖標,打開軟件便可進入主菜單界面,進入主菜單界面後會自動開始播放視頻,以下圖所示。 

鴻蒙開源第三方組件——VideoCache視頻緩存組件

圖 1 視頻播放的主菜單界面

二、驗證緩存

       等待視頻播放完成後,能夠手動關閉手機的數據鏈接和WIFI鏈接。

鴻蒙開源第三方組件——VideoCache視頻緩存組件

圖 2 關閉網絡鏈接

        在關閉了網絡鏈接以後,回到VideoCache應用中,點擊播放按鈕, 會發現視頻是能夠經過本地緩存從新播放的。注意到圖1和圖3的區別,在圖1中任務欄能夠看到有WIFI鏈接顯示,圖3 中沒有WIFI鏈接。

鴻蒙開源第三方組件——VideoCache視頻緩存組件

圖 3 緩存播放視頻

Sample解析

        如圖4所示,該組件在本地與遠程服務器之間創建了代理服務器。當本地發送視頻網絡請求至代理服務器時,代理服務器與遠程服務器之間經過代理Socket鏈接,並將遠程服務器的視頻數據回寫到代理服務器的緩存中,本地播放視頻時從代理服務器的緩存中讀取數據(圖4援引自https://www.jianshu.com/p/4745de02dcdc)。下面詳細介紹視頻緩存的步驟。 

鴻蒙開源第三方組件——VideoCache視頻緩存組件

圖4  VideoCache組件的視頻緩存原理

一、實例化HttpProxyCacheServer類的對象

       HttpProxyCacheServer類可用於處理來自視頻播放器的播放請求,當本地有緩存時,向視頻播放器返回一個本地IP地址(LocalURL:以127.0.0.1開頭),用於視頻的播放。

private HttpProxyCacheServer mCacheServerProxy=null;
public void onStart(Intent intent) {
        ...
        if (mCacheServerProxy == null) {
            Context context = this;
	    //實例化HttpProxyCacheServer對象
            mCacheServerProxy = new HttpProxyCacheServer(context);
        } 
       ...    
}

二、定義緩存監聽器CacheListener

       CacheListener 用於監聽文件緩存的進度,方便開發者經過判斷緩存進度,執行各種操做。

      onCacheAvailable()方法是設置CacheListener 監聽器時須要重寫的方法,此方法的參數中:cacheFile表示緩存文件的地址;url表示網絡視頻的URL;percentsAvailable表示緩存進度,取值爲1~100,取值爲100時表示所有視頻緩存完成。

      基於percentAvailable變量,大多數視頻播放器有如下設計:設置一個變量用於保存當前的視頻播放進度。在緩存監聽器CacheListener 中,比較當前緩存進度與當前播放進度的差值,若是超出了預設值,能夠執行特定操做以暫停緩存,直至兩者的差值小於預設值,從新啓動緩存。

private CacheListener mCacheListener = new CacheListener() {
    @Override
    public void onCacheAvailable(File cacheFile, String url, int percentsAvailable) {
    //打印實時緩存進度
    HiLog.info(new HiLogLabel(3,0,"cache"),"Saving……,percent:"+String.valueOf(percentsAvailable));
    //當進度達到100時,可進行一些特殊操做,此處僅以log打印爲例
    if (percentsAvailable == 100 && !cacheFile.getPath().endsWith(".download")) {
            HiLog.info(new HiLogLabel(3,0,"cache"),"Download already!");
        }
    }
};

 3. 獲取LocalURL

       將網絡視頻的URL與步驟2中的監聽器對象mCacheListener傳入HttpProxyCacheServer類的註冊方法中,便可對緩存進行監聽。後經過 HttpProxyCacheServer類的getProxyUrl()方法獲取網絡視頻URL對應的LocalUrl。

//註冊下載緩存監聽
 mCacheServerProxy.registerCacheListener(mCacheListener,URL);
//獲取LocalURL
localUrl = mCacheServerProxy.getProxyUrl(URL);

四、 使用LocalUrl做爲視頻來源進行播放,緩存功能便可實現。

Library解析

        整個library分爲五個部分:file、headers、slice、sourcestorage以及22個類文件,如圖2所示。

鴻蒙開源第三方組件——VideoCache視頻緩存組件

圖5  library的組成結構

1、file

        在file文件夾下的類主要涉及文件緩存相關的功能: 鴻蒙開源第三方組件——VideoCache視頻緩存組件

圖6 file文件夾的組成結構

一、FileCache類

      類中規定了緩存文件的命名格式(後加.download)和存儲的路徑,完成了緩存文件的建立。

//定義緩存文件的後綴格式
private static final String TEMP_POSTFIX = ".download";
public FileCache(File file, DiskUsage diskUsage) throws ProxyCacheException {
        ...
        File directory = file.getParentFile();
        Files.makeDir(directory);
        boolean completed = file.exists();
        //文件的保存格式:根目錄文件+文件名+以前定義的文件後綴格式
        this.file = completed ? file : new File(file.getParentFile(), file.getName() + TEMP_POSTFIX);
        //文件權限設置。緩存完成,文件只能讀取;未緩存完成,文件可讀可寫。
        this.dataFile = new RandomAccessFile(this.file, completed ? "r" : "rw");
    } catch (IOException e) {
        throw new ProxyCacheException("Error using file " + file + " as disc cache", e);
    }

二、Files類

       此類是對JAVA中原有的File類的封裝,原File類僅可處理一個文件,Files類可同時對多個文件進行處理。

      以下代碼中,getLruListFiles()方法的參數是一個directory,在方法中對directory(文件夾路徑)下的全部文件進行拆分,返回了一個File參數類型的List列表,後續可對列表中的各個File文件進行處理。

static List<File> getLruListFiles(File directory) {
    //經過list對Files內的文件進行處理
    List<File> result = new LinkedList<>();
    File[] files = directory.listFiles();
    //爲各file創建LastModifiedComparator
    //LastModifiedComparator可用於根據文件的上次修改的日期文件進行排序
    if (files != null) {
        result = Arrays.asList(files);
        Collections.sort(result, new LastModifiedComparator());
    }
    return result;
}

三、LruDiskUsage類

        此類主要用於控制緩存文件的大小,它與Videocache平行開了一個線程,實時記錄緩存文件的數量、大小、存儲空間等,超過預設的閾值時,執行特定的優化操做。

private void trim(List<File> files) {
    long totalSize = countTotalSize(files);  //緩存文件的總大小
    int totalCount = files.size();            //緩存文件的總數量
    for (File file : files) {
        //未超過緩存文件的(總大小 & 總數量)的閾值時,接收緩存
        boolean accepted = accept(file, totalSize, totalCount);
        if (!accepted) {
	  long fileSize = file.length(); // 單一文件的大小
            boolean deleted = file.delete();  //文件是否爲預備刪除的文件
	  //若是是準備刪除的文件
            if (deleted) {
                totalCount--;  // 緩存文件的總數量-1
                totalSize -= fileSize;  //緩存文件的總大小 - 預備刪除的單一文件的大小
                LOG.info("Cache file " + file + 
                    " is deleted because it exceeds cache limit");
            } else {
                LOG.error("Error deleting file " + file + " for trimming cache");
            }
        }
    }
}

四、 Md5FileNameGenerator類

       此類實現了爲輸入文件路徑,生成對應的MD5值的功能。MD5值是一種被"壓縮"的保密格式,能夠確保信息完整傳輸。

public class Md5FileNameGenerator implements FileNameGenerator {
    private static final int MAX_EXTENSION_LENGTH = 4;
    @Override
    public String generate(String url) {
        //獲取文件名的後綴
        String extension = getExtension(url); 
        //獲取MD5值
        String name = ProxyCacheUtils.computeMD5(url);
        Boolean isEmpty = false;
        //文件後綴名爲空時,設置isEmpty 標誌位爲true
        if (extension == null || extension.length() == 0) 
            isEmpty = true;
        return isEmpty ? name : name + "." + extension;
    }

五、TotalCountLruDiskUsage類、TotalSizeLruDiskUsage類和UnlimitedDiskUsage類

         LruDiskUsage類是標題中前兩個類的父類,同時控制緩存文件的大小和數量,須要判斷當前緩存文件的(總大小 & 總數量)未超過閾值時,纔會緩存新的文件。   TotalCountLruDiskUsage類和TotalSizeLruDiskUsage類分別只對緩存文件總數量或者緩存文件總大小進行限制,知足一個條件即可以緩存新的文件。

        TotalCountLruDiskUsage類和TotalSizeLruDiskUsage類各有兩個方法:一個方法用於設定緩存文件的閾值;一個方法用於判斷當前緩存數據是否超過了設定的閾值。

       當不須要進行磁盤的緩存限制時使用UnlimitedDiskUsage類,其自己是一個空的類,不對緩存文件的數量和大小作任何限制。

//控制緩存文件的總數量
public class TotalCountLruDiskUsage extends LruDiskUsage {
    private final int maxCount;
    //設置緩存文件的總數量的閾值
    public TotalCountLruDiskUsage(int maxCount) {
        if (maxCount <= 0) {
            throw new IllegalArgumentException("Max count must be positive number!");
        }
        this.maxCount = maxCount;
    }

    //當前緩存文件的總數量小於設定的閾值時,新文件accept
    @Override
    protected boolean accept(File file, long totalSize, int totalCount) {
        return totalCount <= maxCount;
    }
}

//控制制緩存文件的總大小
public class TotalSizeLruDiskUsage extends LruDiskUsage {
    private final long maxSize;
    //設置制緩存文件的總大小的閾值
    public TotalSizeLruDiskUsage(long maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("Max size must be positive number!");
        }
        this.maxSize = maxSize;
    }

    //當前緩存文件的總大小小於設定的閾值時,新文件accept
    @Override
    protected boolean accept(File file, long totalSize, int totalCount) {
        return totalSize <= maxSize;
    }
}

 2、headers

        文件中涉及到的功能很少,僅有一個接口文件和一個能實現URL和文件路徑hashmap匹配功能的類文件,上述功能在HttpProxyCacheServer類中被調用。

鴻蒙開源第三方組件——VideoCache視頻緩存組件

圖7 headers文件夾的組成結構

3、slice

        鴻蒙程序的slice控件用於三方件遷移中的可視化調試,在這裏咱們對其不做進一步的分析。

鴻蒙開源第三方組件——VideoCache視頻緩存組件

圖8 slice文件夾的組成結構

4、sourcestorage

         sourcestorage用於在數據庫中存儲SourInfo。SourInfo可用於存儲http請求源的一些信息,如URL,數據長度Length,請求資源的類型MIME等。sourcestorage中的類主要在上述的HttpProxyCacheServer類中被調用。

鴻蒙開源第三方組件——VideoCache視頻緩存組件

圖9 sourcestorage文件夾的組成結構

       DatabaseSourceInfoStorage類用於作數據庫的初始化工做,數據庫裏面存的字段主要是URL、Length、MIME,SourceInfo類是對這3個字段的封裝。類中包含了三個接口:get()、 put()、release(),可供外部調用,三個接口都是對SourceInfo的操做,主要用來查找和保存緩存的信息。

      其他三個類是根據DatabaseSourceInfoStorage類進行的工廠模式的生成,若是對這部分不明白的同窗能夠在網上搜索「設計模式-工廠模式」進行學習。

class DatabaseSourceInfoStorage extends DatabaseHelper implements SourceInfoStorage {
    //數據庫中存儲SourInfo:URL、Length、MIME
    private static final String TABLE = "SourceInfo";
    private static final String COLUMN_ID = "_id";
    private static final String COLUMN_URL = "url";
    private static final String COLUMN_LENGTH = "length";
    private static finavl String COLUMN_MIME = "mime";
    private static final String[] ALL_COLUMNS = new String[]{COLUMN_ID, COLUMN_URL,
                                                         COLUMN_LENGTH, COLUMN_MIME};
    //建立數據庫的SQL
    private static final String CREATE_SQL =
            "CREATE TABLE " + TABLE + " (" +
                    COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
                    COLUMN_URL + " TEXT NOT NULL," +
                    COLUMN_MIME + " TEXT," +
                    COLUMN_LENGTH + " INTEGER" +
                    ");";

    private final RdbStore myRdbStore;
    //鏈接的數據庫名字
    private final StoreConfig config = 
                             StoreConfig.newDefaultConfig("AndroidVideoCache.db");
}

//數據庫get指令,經過URL獲取SourceInfo 
public SourceInfo get(String url) {
    checkNotNull(url);
    ResultSet cursor = null;
    try{
        RdbPredicates predicates = new RdbPredicates(TABLE);
        predicates.equalTo(COLUMN_URL, url);
        cursor = this.myRdbStore.query(predicates, null);
        return cursor == null || !cursor.goToFirstRow() ? null : convert(cursor);
    } finally {
        if (cursor != null) {
            cursor.close();
        }
    }
}
//數據庫put指令,將url和SourceInfo在數據庫中登記綁定 
public void put(String url, SourceInfo sourceInfo) {
    checkAllNotNull(url, sourceInfo);
    SourceInfo sourceInfoFromDb = get(url);
    boolean exist = sourceInfoFromDb != null;
    RdbPredicates predicates = new RdbPredicates(TABLE);
    if (exist) {
        predicates.contains(COLUMN_URL, url);
        this.myRdbStore.update(convert(sourceInfo), predicates);
    } else {
        this.myRdbStore.insert(TABLE, convert(sourceInfo));
    }
}
//release指令:釋放數據庫控制流
@Override
public void release() {
    this.myRdbStore.close();
}

5、主功能文件

      這部分文件主要用於整合上述四個部分的功能,向外部提供VideoCache接口。

      主要功能類以下圖所示,他們的外部調用方法在Sample中已經詳細說明,主要使用到的就是HttpProxyCacheServer類,下面對其內部實現進行詳細的講解。

鴻蒙開源第三方組件——VideoCache視頻緩存組件

圖10主要功能類主文件

一、構造函數

       在構造函數中主要進行了全局變量的初始化和對PROXY_HOST(VideoCache代理接口,也就是LocalURL所屬的代理接口)進行訪問,判斷是否能夠直接ping通。

private HttpProxyCacheServer(Config config) {
    this.config = checkNotNull(config);
    try {
	//初始化各類全局變量
        InetAddress inetAddress = InetAddress.getByName(PROXY_HOST);
        this.serverSocket = new ServerSocket(0, 8, inetAddress);
        this.port = serverSocket.getLocalPort();
        IgnoreHostProxySelector.install(PROXY_HOST, port);
        CountDownLatch startSignal = new CountDownLatch(1);
        this.waitConnectionThread = new Thread(new WaitRequestsRunnable(startSignal));
        this.waitConnectionThread.start();
        startSignal.await(); // freeze thread, wait for server starts
	//獲取對PROXY_HOST& port的ping,判斷是否能夠ping通
        this.pinger = new Pinger(PROXY_HOST, port);
        LOG.info("Proxy cache server started. Is it alive? " + isAlive());
    } catch (IOException | InterruptedException e) {
        socketProcessor.shutdown();
        throw new IllegalStateException("Error starting local proxy server", e);
    }
}

二、registerCacheListener函數

      這個函數主要實現的功能是對URL進行註冊監聽。

public void registerCacheListener(CacheListener cacheListener, String url) {
    checkAllNotNull(cacheListener, url);
    synchronized (clientsLock) {
        try {
	  //對url獲取Clients,併爲其註冊CacheListener
            getClients(url).registerCacheListener(cacheListener);
        } catch (ProxyCacheException e) {
            LOG.warn("Error registering cache listener", e);
        }
    }
}

三、getProxyUrl函數

        該函數實現了將(已經註冊過的)URL轉化爲cached LocalURL的功能。

public String getProxyUrl(String url) {
    return getProxyUrl(url, true);
}

public String getProxyUrl(String url, boolean allowCachedFileUri) {
    if (allowCachedFileUri && isCached(url)) {
        File cacheFile = getCacheFile(url);
        touchFileSafely(cacheFile);
        return Uri.getUriFromFile(cacheFile).toString();
    }
    return isAlive() ? appendToProxyUrl(url) : url;
}

           當傳入一個網絡視頻的URL時,該方法會對該URL進行判斷,若是能夠在代理服務器上進行緩存,則提供正確的LocalURL返回值,不然返回原URL。

項目貢獻人

         呂澤 鄭森文 朱偉 陳美汝 張馨心

做者:朱偉ISRC

想了解更多內容,請訪問51CTO和華爲合做共建的鴻蒙社區:https://harmonyos.51cto.com

相關文章
相關標籤/搜索