DUICUO

HarmonyOS オープンソースサードパーティコンポーネント - VideoCache ビデオキャッシュコンポーネント

[[389195]]

詳細については、以下をご覧ください。

51CTOとHuaweiが共同で構築したHarmonyOSテクノロジーコミュニティ。

https://harmonyos..com

序文

AndroidプラットフォームベースのVideoCacheコンポーネント(https://github.com/danikula/AndroidVideoCache)は、HarmonyOSへの移行とリファクタリングが完了しました。コードはオープンソースです(https://gitee.com/isrc_ohos/android-video-cache_ohos)。ぜひダウンロードしてご利用いただき、貴重なフィードバックをお寄せください。

背景

ネットワーク速度が変動する環境で動画を閲覧する場合、ネットワーク速度の低さが原因で読み込みが途切れたり、再生に失敗するといった状況に遭遇することがよくあります。VideoCacheコンポーネントは動画キャッシュ機能を実装し、動画再生中に動画ソースをキャッシュします。ネットワーク速度が遅い場合、スマートフォンは事前にキャッシュされた動画データを読み込むことで、正常な動画再生を確保し、よりスムーズな視聴体験を提供します。

コンポーネントレンダリング

1. メインメニューインターフェース: ビデオ再生

ソフトウェアをインストールしたら、HarmonyOSデバイス上のHarmonyVideoCacheソフトウェアアイコンをクリックしてソフトウェアを起動し、メインメニューインターフェースに入ります。メインメニューインターフェースに入ると、下の画像のようにビデオが自動的に再生されます。

図1. ビデオ再生のメインメニューインターフェース

2. キャッシュを検証する

ビデオの再生が終了したら、携帯電話のデータと Wi-Fi 接続を手動でオフにすることができます。

図2. ネットワーク接続の切断

ネットワーク接続をオフにした後、VideoCacheアプリケーションに戻り、再生ボタンをクリックしてください。ローカルキャッシュを使用してビデオを再生できることがわかります。図1と図3の違いに注目してください。図1ではタスクバーにWi-Fi接続が表示されていますが、図3ではWi-Fi接続がありません。

図3. キャッシュされたビデオの再生

サンプル分析

図4に示すように、このコンポーネントはローカルサーバーとリモートサーバーの間にプロキシサーバーを構築します。ローカルマシンがプロキシサーバーにビデオネットワークリクエストを送信すると、プロキシサーバーはプロキシソケットを介してリモートサーバーに接続し、リモートサーバーからのビデオデータをプロキシサーバーのキャッシュに書き戻します。ローカルでビデオを再生する際、データはプロキシサーバーのキャッシュから読み込まれます(図4はhttps://www.jianshu.com/p/4745de02dcdcから引用)。ビデオキャッシュの手順については、以下で詳しく説明します。

図4. VideoCacheコンポーネントのビデオキャッシュ原理

1. HttpProxyCacheServerクラスのオブジェクトをインスタンス化する

HttpProxyCacheServerクラスは、ビデオプレーヤーからの再生リクエストを処理するために使用できます。ローカルキャッシュが存在する場合、ビデオ再生時にローカルIPアドレス(LocalURL:127.0.0.1から始まる)をビデオプレーヤーに返します。

  1. プライベート HttpProxyCacheServer mCacheServerProxy= null ;
  2. パブリックvoid onStart(インテント インテント) {
  3. ...
  4. mCacheServerProxy == null の場合{
  5. コンテキスト context = this;
  6. // HttpProxyCacheServer オブジェクトをインスタンス化する
  7. mCacheServerProxy = 新しい HttpProxyCacheServer(コンテキスト);
  8. }
  9. ...
  10. }

2. キャッシュ リスナー (CacheListener) を定義します。

CacheListener はファイル キャッシュの進行状況を監視するために使用され、開発者がキャッシュの進行状況を判断しながらさまざまな操作を実行するのに便利です。

onCacheAvailable() メソッドは、CacheListener を設定する際にオーバーライドする必要があるメソッドです。このメソッドのパラメータには、cacheFile はキャッシュファイルのアドレス、url はネットワークビデオの URL、percentsAvailable はキャッシュの進行状況を表す 1 から 100 までの値が含まれます。値が 100 の場合、すべてのビデオがキャッシュされたことを示します。

ほとんどのビデオプレーヤーは、`percentAvailable` 変数に基づいて、次のような設計になっています。現在のビデオ再生の進行状況を格納する変数が設定されます。`CacheListener` では、現在のキャッシュの進行状況と現在の再生の進行状況の差が比較されます。差が設定値を超えた場合、特定の操作が実行され、差が設定値未満になるまでキャッシュが一時停止され、差が設定値未満になった時点でキャッシュが再開されます。

  1. プライベートCacheListener mCacheListener = 新しいCacheListener() {
  2. @オーバーライド
  3. パブリックvoid onCacheAvailable(ファイル cacheFile、文字列 url、 int percentsAvailable) {
  4. //リアルタイムのキャッシュの進行状況を印刷する
  5. HiLog.info(new HiLogLabel(3,0, "cache" ), "保存中...,パーセント:" +String.valueOf(percentsAvailable));
  6. // 進捗が100%に達すると、特別な操作を実行できます。ここではログ出力のみを例に挙げています。
  7. if (percentsAvailable == 100 && !cacheFile.getPath().endsWith( ".download" )) {
  8. HiLog.info(new HiLogLabel(3,0, "cache" ), "すでにダウンロード済みです!" );
  9. }
  10. }
  11. };

3. LocalURLを取得する

オンラインビデオのURLと手順2で取得したリスナーオブジェクト「mCacheListener」を`HttpProxyCacheServer`クラスの登録メソッドに渡すことで、キャッシュを監視できます。その後、`HttpProxyCacheServer`クラスの`getProxyUrl()`メソッドを使用して、オンラインビデオのURLに対応するLocalUrlを取得できます。

  1. //ダウンロードキャッシュリスナーを登録する
  2. mCacheServerProxy.registerCacheListener(mCacheListener,URL);
  3. //ローカルURLを取得する
  4. localUrl = mCacheServerProxy.getProxyUrl(URL);

4. 再生時のビデオソースとして LocalUrl を使用すると、キャッシュ機能が実装されます。

ライブラリ分析

ライブラリ全体は、図 2 に示すように、ファイル、ヘッダー、スライス、ソース ストレージ、および 22 個のクラス ファイルの 5 つの部分に分かれています。

図5. ライブラリの構成構造

I. ファイル

ファイル フォルダー内のクラスは、主にファイル キャッシュ関連の機能に関係します。

図6. ファイルフォルダの構造

1. FileCacheクラス

このクラスは、キャッシュ ファイルの命名形式 (「.download」を追加) と保存パスを指定し、キャッシュ ファイルの作成を完了します。

  1. // キャッシュファイルのファイル拡張子の形式を定義する
  2. プライベート静的最終文字列 TEMP_POSTFIX = ".download" ;
  3. パブリックFileCache(File file, DiskUsage diskUsage)はProxyCacheExceptionをスローします{
  4. ...
  5. ファイルディレクトリ = file.getParentFile();
  6. Files.makeDir(ディレクトリ);
  7. ブール値 完了 = file.exists();
  8. //ファイル保存形式: ルートディレクトリファイル + ファイル名 + 以前に定義したファイル拡張子形式
  9. this.file = 完了しましたか? file : new File(file.getParentFile(), file.getName() + TEMP_POSTFIX);
  10. //ファイルの権限設定。キャッシュが完了すると、ファイルは読み取り専用になります。キャッシュが完了しない場合は、ファイルの読み取りと書き込みが可能になります。
  11. this.dataFile = 新しい RandomAccessFile(this.file、完了? "r" : "rw" );
  12. } キャッチ (IOException e) {
  13. throw new ProxyCacheException( "ファイル " + file + " をディスク キャッシュとして使用するとエラーが発生しました" , e);
  14. }

2. ファイルクラス

このクラスは、JavaのオリジナルのFileクラスのラッパーです。オリジナルのFileクラスは1つのファイルしか処理できませんが、Filesクラスは複数のファイルを同時に処理できます。

以下のコードでは、getLruListFiles() メソッドのパラメータはディレクトリです。このメソッドは、ディレクトリ(フォルダパス)以下のすべてのファイルを分割し、File パラメータの型のリストを返します。リスト内の個々のファイルは、その後処理されます。

  1. static List<File> getLruListFiles(ファイルディレクトリ) {
  2. //リストを使用してファイル内のファイルを処理する
  3. List<File> 結果 = 新しい LinkedList<>();
  4. ファイル[] files = directory.listFiles();
  5. // ファイルごとにLastModifiedComparatorを作成する
  6. //LastModifiedComparator を使用すると、ファイルの最終変更日に基づいてファイルを並べ替えることができます。
  7. if (ファイル!= null ) {
  8. 結果 = Arrays.asList(ファイル);
  9. Collections.sort(結果、新しいLastModifiedComparator());
  10. }
  11. 結果を返します
  12. }

3. LruDiskUsageクラス

このタイプは主にキャッシュファイルのサイズを制御するために使用されます。Videocacheと並列スレッドを実行し、キャッシュファイルの数、サイズ、ストレージ容量をリアルタイムで記録します。設定されたしきい値を超えると、特定の最適化操作が実行されます。

  1. プライベートvoidトリム(List<File>ファイル) {
  2. long totalSize = countTotalSize(files); // キャッシュされたファイルの合計サイズ
  3. int totalCount = files.size (); // キャッシュされたファイルの総数
  4. for (ファイル file : files) {
  5. // キャッシュされたファイルの合計サイズと合計数が超過していない場合はキャッシュを受け取ります。
  6. ブール型 accepted = accept(ファイル、totalSize、totalCount);
  7. もし (!承認済み) {
  8. long fileSize = file.length(); // 単一ファイルのサイズ
  9. boolean deleted = file.delete (); // ファイルが削除される予定かどうかを確認します。
  10. //削除しようとしているファイルの場合
  11. (削除された場合){
  12. totalCount --; // キャッシュされたファイルの総数 - 1  
  13. totalSize -= fileSize; // キャッシュされたファイルの合計サイズ - 削除される単一ファイルのサイズ
  14. LOG.info( "キャッシュファイル " + ファイル +
  15. 「キャッシュ制限を超えたため削除されます」 );
  16. }それ以外{
  17. LOG.error( "キャッシュをトリミングするためにファイル " + file + " を削除中にエラーが発生しました" );
  18. }
  19. }
  20. }
  21. }

4. Md5FileNameGeneratorクラス

このクラスは、入力ファイルパスに対応するMD5値を生成する機能を実装します。MD5値は、情報伝送の整合性を保証する「圧縮」された機密形式です。

  1. パブリッククラスMd5FileNameGeneratorはFileNameGeneratorを実装します{
  2. プライベート静的最終int MAX_EXTENSION_LENGTH = 4;
  3. @オーバーライド
  4. パブリック文字列生成(文字列 url) {
  5. //ファイル名の拡張子を取得する
  6. 文字列拡張子 = getExtension(url);
  7. //MD5値を取得する
  8. 文字列= ProxyCacheUtils.computeMD5(url);
  9. ブール値 isEmpty = false ;
  10. // ファイル拡張子が空の場合は、isEmpty フラグをtrueに設定します。  
  11. 拡張子 == null || extension.length() == 0 の場合
  12. 空かどうかはtrue です
  13. return isEmpty ? name :名前+ "." + 拡張子;
  14. }

5. TotalCountLruDiskUsageクラス、TotalSizeLruDiskUsageクラス、およびUnlimitedDiskUsageクラス

`LruDiskUsage` クラスは、タイトルで述べた最初の 2 つのクラスの親クラスです。このクラスは、キャッシュされるファイルのサイズと数の両方を制御します。新しいファイルは、現在キャッシュされているファイルの(合計サイズと合計数)がしきい値を超えない場合にのみキャッシュされます。`TotalCountLruDiskUsage` クラスと `TotalSizeLruDiskUsage` クラスは、それぞれキャッシュされるファイルの総数または合計サイズのみを制限します。どちらかの条件が満たされた場合、新しいファイルはキャッシュされます。

TotalCountLruDiskUsage クラスと TotalSizeLruDiskUsage クラスにはそれぞれ 2 つのメソッドがあります。1 つのメソッドはキャッシュされたファイルのしきい値を設定するために使用され、もう 1 つのメソッドは現在のキャッシュされたデータが設定されたしきい値を超えているかどうかを判断するために使用されます。

ディスクキャッシュが不要な場合は、UnlimitedDiskUsageクラスを使用してください。これは、キャッシュされるファイルの数やサイズに制限を設けない空のクラスです。

  1. //キャッシュファイルの総数を制御する
  2. パブリッククラス TotalCountLruDiskUsage は LruDiskUsage を拡張します {
  3. プライベート最終int maxCount;
  4. //キャッシュされたファイルの総数のしきい値を設定する
  5. パブリックTotalCountLruDiskUsage( int maxCount) {
  6. 最大カウント <= 0 の場合
  7. 新しい IllegalArgumentException をスローします ( "最大数は正の数でなければなりません!" );
  8. }
  9. this.maxCount = 最大カウント;
  10. }
  11.  
  12. //現在キャッシュされているファイルの総数が設定されたしきい値より少ない場合、新しいファイルを受け入れます。
  13. @オーバーライド
  14. 保護されたブール値の accept(ファイル file, long totalSize, int totalCount) {
  15. totalCount <= maxCountを返します
  16. }
  17. }
  18.  
  19. //キャッシュファイルの合計サイズを制御する
  20. パブリッククラス TotalSizeLruDiskUsage は LruDiskUsage を拡張します {
  21. プライベート最終long maxSize;
  22. //キャッシュファイルの合計サイズのしきい値を設定する
  23. パブリックTotalSizeLruDiskUsage(long maxSize) {
  24. 最大サイズ <= 0 の場合
  25. 新しい IllegalArgumentException をスローします ( "最大サイズは正の数である必要があります!" );
  26. }
  27. this.maxSize = 最大サイズ;
  28. }
  29.  
  30. //現在キャッシュされているファイルの合計サイズが設定されたしきい値より小さい場合は、新しいファイルを受け入れます。
  31. @オーバーライド
  32. 保護されたブール値の accept(ファイル file, long totalSize, int totalCount) {
  33. totalSize <= maxSizeを返します
  34. }
  35. }

2. ヘッダー

このファイルには、インターフェースファイルと、URLとファイルパスのハッシュマップマッチングを実行できるクラスファイルなど、いくつかの機能のみが含まれています。これらの機能は、HttpProxyCacheServerクラスで呼び出されます。

図7. ヘッダーフォルダの構造

III. スライス

HarmonyOS プログラムのスライス コントロールは、サードパーティ コンポーネントの移行中に視覚的にデバッグするために使用されるため、ここではこれ以上分析しません。

図8. スライスフォルダの構造

IV. ソースストレージ

Sourcestorageは、SourInfoをデータベースに保存するために使用されます。SourInfoは、HTTPリクエストのソースに関する情報(URL、データ長、リクエストされたリソースのMIMEタイプなど)を保存するために使用できます。Sourcestorage内のクラスは、主に前述のHttpProxyCacheServerクラス内で呼び出されます。

図9. ソースストレージフォルダの構造

DatabaseSourceInfoStorageクラスは、データベースの初期化に使用されます。データベースに保存される主なフィールドは、URL、長さ、MIMEタイプです。SourceInfoクラスは、これら3つのフィールドをカプセル化します。このクラスには、外部から呼び出すことができるget()、put()、release()という3つのインターフェースが含まれています。これら3つのインターフェースはすべてSourceInfoを操作し、主にキャッシュされた情報の取得と保存に使用されます。

残りの3つのクラスは、DatabaseSourceInfoStorageクラスをベースにしたファクトリーパターンを使用して生成されます。この部分が理解できない場合は、「デザインパターン - ファクトリーパターン」でオンライン検索して詳細を確認してください。

  1. クラス DatabaseSourceInfoStorage は DatabaseHelper を拡張し、SourceInfoStorage を実装します {
  2. // データベースには、SourInfo: URL、長さ、MIMEが格納されます
  3. プライベート静的最終文字列TABLE = "SourceInfo" ;
  4. プライベート静的最終文字列 COLUMN_ID = "_id" ;
  5. プライベート静的最終文字列 COLUMN_URL = "url" ;
  6. プライベート静的最終文字列 COLUMN_LENGTH = "長さ" ;
  7. プライベート静的finavl文字列COLUMN_MIME = "mime" ;
  8. プライベート静的最終String[] ALL_COLUMNS = 新しいString[]{COLUMN_ID, COLUMN_URL,
  9. COLUMN_LENGTH、COLUMN_MIME};
  10. // データベースを作成するためのSQL
  11. プライベート静的最終文字列 CREATE_SQL =
  12. 「テーブルを作成」 ​​+テーブル+ 「(」 +
  13. COLUMN_ID + "INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
  14. COLUMN_URL + "テキストがNULLではありません," +
  15. COLUMN_MIME + " テキスト," +
  16. COLUMN_LENGTH + "整数" +
  17. ");" ;
  18.  
  19. プライベート最終 RdbStore myRdbStore;
  20. // 接続するデータベース名
  21. プライベート最終StoreConfig構成=
  22. StoreConfig.newDefaultConfig( "AndroidVideoCache.db" );
  23. }
  24.  
  25. // データベース GET コマンド、URL 経由で SourceInfo を取得します
  26. パブリックSourceInfo get(String url) {
  27. checkNotNull(url);
  28. ResultSetカーソル= null ;
  29. 試す{
  30. RdbPredicates 述語 = 新しい RdbPredicates( TABLE );
  31. predicates.equalTo(COLUMN_URL, url);
  32. カーソル= this.myRdbStore.query(述語、 null );
  33. 戻る カーソル== null || !カーソル.goToFirstRow() ? null : convert (カーソル);
  34. ついに {
  35. if (カーソル!= null ) {
  36. カーソルを閉じる( ) ;
  37. }
  38. }
  39. }
  40. // データベース PUT コマンドは、URL と SourceInfo をデータベースに登録してバインドします。
  41. パブリックvoid put(String url, SourceInfo sourceInfo) {
  42. checkAllNotNull(url, ソース情報);
  43. ソース情報 sourceInfoFromDb = get(url);
  44. ブール値 exist = sourceInfoFromDb != null ;
  45. RdbPredicates 述語 = 新しい RdbPredicates( TABLE );
  46. もし(存在する){
  47. 述語。 (COLUMN_URL、url)が含まれます
  48. this.myRdbStore. update ( convert (sourceInfo), predicates);
  49. }それ以外{
  50. this.myRdbStore. insert ( TABLE convert (sourceInfo) ) ;
  51. }
  52. }
  53. //release ディレクティブ: データベース制御フローを解放する
  54. @オーバーライド
  55. パブリックvoidリリース() {
  56. this.myRdbStore.close ();
  57. }

V. メイン関数ファイル

このドキュメントのセクションは、主に上記の 4 つの部分の機能を統合し、VideoCache インターフェイスを外部に提供するために使用されます。

主な機能クラスを下図に示します。これらの外部呼び出しメソッドについてはサンプルで詳しく説明しています。メインクラスとしてHttpProxyCacheServerがあり、その内部実装については後ほど詳しく説明します。

図10 メイン関数クラスファイル

1. コンストラクター

コンストラクターは主にグローバル変数を初期化し、PROXY_HOST (LocalURL が属するプロキシ インターフェイスである VideoCache のプロキシ インターフェイス) にアクセスして、直接 ping できるかどうかを判断します。

  1. プライベートHttpProxyCacheServer(Config config) {
  2. this.config = checkNotNull(config);
  3. 試す {
  4. // さまざまなグローバル変数を初期化する
  5. InetAddress inetAddress = InetAddress.getByName(PROXY_HOST);
  6. this.serverSocket = 新しい ServerSocket(0, 8, inetAddress);
  7. this.port = serverSocket.getLocalPort();
  8. IgnoreHostProxySelector.install(PROXY_HOST、ポート);
  9. CountDownLatch startSignal = 新しい CountDownLatch(1);
  10. this.waitConnectionThread = 新しいスレッド(新しい WaitRequestsRunnable(startSignal));
  11. this.waitConnectionThread.start();
  12. startSignal.await(); // スレッドをフリーズし、サーバーの起動待つ
  13. // PROXY_HOST& ポートへの ping を取得し、到達可能かどうかを判断します。
  14. this.pinger = 新しい Pinger(PROXY_HOST、ポート);
  15. LOG.info( "プロキシキャッシュサーバーが起動しました。動作していますか? " + isAlive());
  16. } キャッチ (IOException | InterruptedException e) {
  17. socketProcessor.shutdown();
  18. throw new IllegalStateException( "ローカル プロキシ サーバーの起動エラー" , e);
  19. }
  20. }

2. registerCacheListener関数

この関数の主な機能は、URL のリスナーを登録することです。

  1. パブリックvoid registerCacheListener(CacheListener cacheListener, String url) {
  2. checkAllNotNull(cacheListener、url);
  3. 同期済み (clientsLock) {
  4. 試す {
  5. // URL からクライアントを取得し、それらの CacheListener を登録します。
  6. getClients(url).registerCacheListener(cacheListener);
  7. } キャッチ (ProxyCacheException e) {
  8. LOG.warn( "キャッシュリスナーの登録エラー" , e);
  9. }
  10. }
  11. }

3. getProxyUrl関数

この関数は、(すでに登録されている)URL をキャッシュされた LocalURL に変換します。

  1. パブリック文字列 getProxyUrl(文字列 url) {
  2. getProxyUrl(url, true )を返します
  3. }
  4.  
  5. パブリック文字列 getProxyUrl(文字列 url, ブール値 allowCachedFileUri) {
  6. if (allowCachedFileUri && isCached(url)) {
  7. ファイル cacheFile = getCacheFile(url);
  8. キャッシュファイルを安全にタッチします。
  9. Uri.getUriFromFile(cacheFile).toString()を返します
  10. }
  11. isAlive()を返しますか? appendToProxyUrl(url): url;
  12. }

ネットワークビデオのURLが渡されると、このメソッドはURLがプロキシサーバーにキャッシュ可能かどうかを判断します。キャッシュ可能な場合は正しいLocalURL値を返します。キャッシュできない場合は元のURLを返します。

プロジェクト貢献者

呂澤、鄭仙文、朱偉、チェン・メイル、チャン・シンシン

詳細については、以下をご覧ください。

51CTOとHuaweiが共同で構築したHarmonyOSテクノロジーコミュニティ。

https://harmonyos..com