当前位置:首页 >探索 >鸿蒙开源第三方组件 实现了鸿蒙化迁移和重构

鸿蒙开源第三方组件 实现了鸿蒙化迁移和重构

2024-06-30 20:26:25 [百科] 来源:避面尹邢网

鸿蒙开源第三方组件-VideoCache视频缓存组件

作者:朱伟ISRC 开源 文章由鸿蒙社区产出,鸿蒙想要了解更多内容请前往:51CTO和华为官方战略合作共建的开源鸿蒙技术社区https://harmonyos.51cto.com

[[389195]]

想了解更多内容,请访问:

鸿蒙开源第三方组件 实现了鸿蒙化迁移和重构

51CTO和华为官方合作共建的第方鸿蒙技术社区

鸿蒙开源第三方组件 实现了鸿蒙化迁移和重构

https://harmonyos.51cto.com

鸿蒙开源第三方组件 实现了鸿蒙化迁移和重构

 前言

基于安卓平台的视频缓存组件VideoCache( https://github.com/danikula/AndroidVideoCache),实现了鸿蒙化迁移和重构,组件代码已经开源到(https://gitee.com/isrc_ohos/android-video-cache_ohos),鸿蒙欢迎各位下载使用并提出宝贵意见!

背景

用户在网速波动较大的开源环境下浏览视频时,经常会遇到由于网速较慢引起的第方持续加载或播放失败的情况。VideoCache组件实现了视频缓存功能,组件播放视频的鸿蒙同时,对视频源进行缓存。开源出现网速较慢的第方情况时,手机读取提前缓存好的组件视频数据,可以保证视频的鸿蒙正常播放,给予用户更流畅的开源观看体验。

组件效果图展示

1、第方主菜单界面: 视频播放

安装软件后,只需要在鸿蒙设备上单击HarmonyVideoCache软件图标,打开软件即可进入主菜单界面,进入主菜单界面后会自动开始播放视频,如下图所示。

图 1 视频播放的主菜单界面

2、验证缓存

等待视频播放完成后,可以手动关闭手机的数据连接和WIFI连接。

图 2 关闭网络连接

在关闭了网络连接之后,回到VideoCache应用中,点击播放按钮, 会发现视频是可以通过本地缓存重新播放的。注意到图1和图3的区别,在图1中任务栏可以看到有WIFI连接显示,图3 中没有WIFI连接。

图 3 缓存播放视频

Sample解析

如图4所示,该组件在本地与远程服务器之间建立了代理服务器。当本地发送视频网络请求至代理服务器时,代理服务器与远程服务器之间通过代理Socket连接,并将远程服务器的视频数据回写到代理服务器的缓存中,本地播放视频时从代理服务器的缓存中读取数据(图4援引自https://www.jianshu.com/p/4745de02dcdc)。下面详细介绍视频缓存的步骤。

图4 VideoCache组件的视频缓存原理

1、实例化HttpProxyCacheServer类的对象

HttpProxyCacheServer类可用于处理来自视频播放器的播放请求,当本地有缓存时,向视频播放器返回一个本地IP地址(LocalURL:以127.0.0.1开头),用于视频的播放。

  1. private HttpProxyCacheServer mCacheServerProxy=null; 
  2. public void onStart(Intent intent) {  
  3.         ... 
  4.         if (mCacheServerProxy == null) {  
  5.             Context context = this; 
  6.         //实例化HttpProxyCacheServer对象 
  7.             mCacheServerProxy = new HttpProxyCacheServer(context); 
  8.         }  
  9.        ...     

2、定义缓存监听器CacheListener

CacheListener 用于监听文件缓存的进度,方便开发者通过判断缓存进度,执行各类操作。

onCacheAvailable()方法是设置CacheListener 监听器时需要重写的方法,此方法的参数中:cacheFile表示缓存文件的地址;url表示网络视频的URL;percentsAvailable表示缓存进度,取值为1~100,取值为100时表示全部视频缓存完成。

基于percentAvailable变量,大多数视频播放器有以下设计:设置一个变量用于保存当前的视频播放进度。在缓存监听器CacheListener 中,比较当前缓存进度与当前播放进度的差值,如果超出了预设值,可以执行特定操作以暂停缓存,直至二者的差值小于预设值,重新启动缓存。

  1. private CacheListener mCacheListener = new CacheListener() {  
  2.     @Override 
  3.     public void onCacheAvailable(File cacheFile, String url, int percentsAvailable) {  
  4.     //打印实时缓存进度 
  5.     HiLog.info(new HiLogLabel(3,0,"cache"),"Saving……,percent:"+String.valueOf(percentsAvailable)); 
  6.     //当进度达到100时,可进行一些特殊操作,此处仅以log打印为例 
  7.     if (percentsAvailable == 100 && !cacheFile.getPath().endsWith(".download")) {  
  8.             HiLog.info(new HiLogLabel(3,0,"cache"),"Download already!"); 
  9.         } 
  10.     } 
  11. }; 

3. 获取LocalURL

将网络视频的URL与步骤2中的监听器对象mCacheListener传入HttpProxyCacheServer类的注册方法中,即可对缓存进行监听。后通过 HttpProxyCacheServer类的getProxyUrl()方法获取网络视频URL对应的LocalUrl。

  1. //注册下载缓存监听 
  2.  mCacheServerProxy.registerCacheListener(mCacheListener,URL); 
  3. //获取LocalURL 
  4. localUrl = mCacheServerProxy.getProxyUrl(URL); 

4、 使用LocalUrl作为视频来源进行播放,缓存功能即可实现。

Library解析

整个library分为五个部分:file、headers、slice、sourcestorage以及22个类文件,如图2所示。

图5 library的组成结构

一、file

在file文件夹下的类主要涉及文件缓存相关的功能:

图6 file文件夹的组成结构

1、FileCache类

类中规定了缓存文件的命名格式(后加.download)和存储的路径,完成了缓存文件的创建。

  1. //定义缓存文件的后缀格式 
  2. private static final String TEMP_POSTFIX = ".download"; 
  3. public FileCache(File file, DiskUsage diskUsage) throws ProxyCacheException {  
  4.         ... 
  5.         File directory = file.getParentFile(); 
  6.         Files.makeDir(directory); 
  7.         boolean completed = file.exists(); 
  8.         //文件的保存格式:根目录文件+文件名+之前定义的文件后缀格式 
  9.         this.file = completed ? file : new File(file.getParentFile(), file.getName() + TEMP_POSTFIX); 
  10.         //文件权限设置。缓存完成,文件只能读取;未缓存完成,文件可读可写。 
  11.         this.dataFile = new RandomAccessFile(this.file, completed ? "r" : "rw"); 
  12.     } catch (IOException e) {  
  13.         throw new ProxyCacheException("Error using file " + file + " as disc cache", e); 
  14.     } 

2、Files类

此类是对JAVA中原有的File类的封装,原File类仅可处理一个文件,Files类可同时对多个文件进行处理。

如下代码中,getLruListFiles()方法的参数是一个directory,在方法中对directory(文件夹路径)下的所有文件进行拆分,返回了一个File参数类型的List列表,后续可对列表中的各个File文件进行处理。

  1. static List<File> getLruListFiles(File directory) {  
  2.     //通过list对Files内的文件进行处理 
  3.     List<File> result = new LinkedList<>(); 
  4.     File[] files = directory.listFiles(); 
  5.     //为各file建立LastModifiedComparator 
  6.     //LastModifiedComparator可用于根据文件的上次修改的日期文件进行排序 
  7.     if (files != null) {  
  8.         result = Arrays.asList(files); 
  9.         Collections.sort(result, new LastModifiedComparator()); 
  10.     } 
  11.     return result; 

3、LruDiskUsage类

此类主要用于控制缓存文件的大小,它与Videocache平行开了一个线程,实时记录缓存文件的数量、大小、存储空间等,超过预设的阈值时,执行特定的优化操作。

  1. private void trim(List<File> files) {  
  2.     long totalSize = countTotalSize(files);  //缓存文件的总大小 
  3.     int totalCount = files.size();            //缓存文件的总数量 
  4.     for (File file : files) {  
  5.         //未超过缓存文件的(总大小 & 总数量)的阈值时,接收缓存 
  6.         boolean accepted = accept(file, totalSize, totalCount); 
  7.         if (!accepted) {  
  8.       long fileSize = file.length(); // 单一文件的大小 
  9.             boolean deleted = file.delete();  //文件是否为预备删除的文件 
  10.       //如果是准备删除的文件 
  11.             if (deleted) {  
  12.                 totalCount--;  // 缓存文件的总数量-1 
  13.                 totalSize -= fileSize;  //缓存文件的总大小 - 预备删除的单一文件的大小 
  14.                 LOG.info("Cache file " + file +  
  15.                     " is deleted because it exceeds cache limit"); 
  16.             } else {  
  17.                 LOG.error("Error deleting file " + file + " for trimming cache"); 
  18.             } 
  19.         } 
  20.     } 

4、 Md5FileNameGenerator类

此类实现了为输入文件路径,生成对应的MD5值的功能。MD5值是一种被"压缩"的保密格式,可以确保信息完整传输。

  1. public class Md5FileNameGenerator implements FileNameGenerator {  
  2.     private static final int MAX_EXTENSION_LENGTH = 4; 
  3.     @Override 
  4.     public String generate(String url) {  
  5.         //获取文件名的后缀 
  6.         String extension = getExtension(url);  
  7.         //获取MD5值 
  8.         String name = ProxyCacheUtils.computeMD5(url); 
  9.         Boolean isEmpty = false; 
  10.         //文件后缀名为空时,设置isEmpty 标志位为true 
  11.         if (extension == null || extension.length() == 0)  
  12.             isEmpty = true; 
  13.         return isEmpty ? name : name + "." + extension; 
  14.     } 

5、TotalCountLruDiskUsage类、TotalSizeLruDiskUsage类和UnlimitedDiskUsage类

LruDiskUsage类是标题中前两个类的父类,同时控制缓存文件的大小和数量,需要判断当前缓存文件的(总大小 & 总数量)未超过阈值时,才会缓存新的文件。 TotalCountLruDiskUsage类和TotalSizeLruDiskUsage类分别只对缓存文件总数量或者缓存文件总大小进行限制,满足一个条件便可以缓存新的文件。

TotalCountLruDiskUsage类和TotalSizeLruDiskUsage类各有两个方法:一个方法用于设定缓存文件的阈值;一个方法用于判断当前缓存数据是否超过了设定的阈值。

当不需要进行磁盘的缓存限制时使用UnlimitedDiskUsage类,其本身是一个空的类,不对缓存文件的数量和大小做任何限制。

  1. //控制缓存文件的总数量 
  2. public class TotalCountLruDiskUsage extends LruDiskUsage {  
  3.     private final int maxCount; 
  4.     //设置缓存文件的总数量的阈值 
  5.     public TotalCountLruDiskUsage(int maxCount) {  
  6.         if (maxCount <= 0) {  
  7.             throw new IllegalArgumentException("Max count must be positive number!"); 
  8.         } 
  9.         this.maxCount = maxCount; 
  10.     } 
  11.  
  12.     //当前缓存文件的总数量小于设定的阈值时,新文件accept 
  13.     @Override 
  14.     protected boolean accept(File file, long totalSize, int totalCount) {  
  15.         return totalCount <= maxCount; 
  16.     } 
  17.  
  18. //控制制缓存文件的总大小 
  19. public class TotalSizeLruDiskUsage extends LruDiskUsage {  
  20.     private final long maxSize; 
  21.     //设置制缓存文件的总大小的阈值 
  22.     public TotalSizeLruDiskUsage(long maxSize) {  
  23.         if (maxSize <= 0) {  
  24.             throw new IllegalArgumentException("Max size must be positive number!"); 
  25.         } 
  26.         this.maxSize = maxSize; 
  27.     } 
  28.  
  29.     //当前缓存文件的总大小小于设定的阈值时,新文件accept 
  30.     @Override 
  31.     protected boolean accept(File file, long totalSize, int totalCount) {  
  32.         return totalSize <= maxSize; 
  33.     } 

二、headers

文件中涉及到的功能不多,仅有一个接口文件和一个能实现URL和文件路径hashmap匹配功能的类文件,上述功能在HttpProxyCacheServer类中被调用。

图7 headers文件夹的组成结构

三、slice

鸿蒙程序的slice控件用于三方件迁移中的可视化调试,在这里我们对其不作进一步的分析。

图8 slice文件夹的组成结构

四、sourcestorage

sourcestorage用于在数据库中存储SourInfo。SourInfo可用于存储http请求源的一些信息,如URL,数据长度Length,请求资源的类型MIME等。sourcestorage中的类主要在上述的HttpProxyCacheServer类中被调用。

图9 sourcestorage文件夹的组成结构

DatabaseSourceInfoStorage类用于做数据库的初始化工作,数据库里面存的字段主要是URL、Length、MIME,SourceInfo类是对这3个字段的封装。类中包含了三个接口:get()、 put()、release(),可供外部调用,三个接口都是对SourceInfo的操作,主要用来查找和保存缓存的信息。

其余三个类是根据DatabaseSourceInfoStorage类进行的工厂模式的生成,如果对这部分不明白的同学可以在网上搜索“设计模式-工厂模式”进行学习。

  1. class DatabaseSourceInfoStorage extends DatabaseHelper implements SourceInfoStorage {  
  2.     //数据库中存储SourInfo:URL、Length、MIME 
  3.     private static final String TABLE = "SourceInfo"; 
  4.     private static final String COLUMN_ID = "_id"; 
  5.     private static final String COLUMN_URL = "url"; 
  6.     private static final String COLUMN_LENGTH = "length"; 
  7.     private static finavl String COLUMN_MIME = "mime"; 
  8.     private static final String[] ALL_COLUMNS = new String[]{ COLUMN_ID, COLUMN_URL, 
  9.                                                          COLUMN_LENGTH, COLUMN_MIME}; 
  10.     //创建数据库的SQL 
  11.     private static final String CREATE_SQL = 
  12.             "CREATE TABLE " + TABLE + " (" + 
  13.                     COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," + 
  14.                     COLUMN_URL + " TEXT NOT NULL," + 
  15.                     COLUMN_MIME + " TEXT," + 
  16.                     COLUMN_LENGTH + " INTEGER" + 
  17.                     ");"; 
  18.  
  19.     private final RdbStore myRdbStore; 
  20.     //连接的数据库名字 
  21.     private final StoreConfig config =  
  22.                              StoreConfig.newDefaultConfig("AndroidVideoCache.db"); 
  23.  
  24. //数据库get指令,通过URL获取SourceInfo  
  25. public SourceInfo get(String url) {  
  26.     checkNotNull(url); 
  27.     ResultSet cursor = null; 
  28.     try{  
  29.         RdbPredicates predicates = new RdbPredicates(TABLE); 
  30.         predicates.equalTo(COLUMN_URL, url); 
  31.         cursor = this.myRdbStore.query(predicates, null); 
  32.         return cursor == null || !cursor.goToFirstRow() ? null : convert(cursor); 
  33.     } finally {  
  34.         if (cursor != null) {  
  35.             cursor.close(); 
  36.         } 
  37.     } 
  38. //数据库put指令,将url和SourceInfo在数据库中登记绑定  
  39. public void put(String url, SourceInfo sourceInfo) {  
  40.     checkAllNotNull(url, sourceInfo); 
  41.     SourceInfo sourceInfoFromDb = get(url); 
  42.     boolean exist = sourceInfoFromDb != null; 
  43.     RdbPredicates predicates = new RdbPredicates(TABLE); 
  44.     if (exist) {  
  45.         predicates.contains(COLUMN_URL, url); 
  46.         this.myRdbStore.update(convert(sourceInfo), predicates); 
  47.     } else {  
  48.         this.myRdbStore.insert(TABLE, convert(sourceInfo)); 
  49.     } 
  50. //release指令:释放数据库控制流 
  51. @Override 
  52. public void release() {  
  53.     this.myRdbStore.close(); 

五、主功能文件

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

主要功能类如下图所示,他们的外部调用方法在Sample中已经详细说明,主要使用到的就是HttpProxyCacheServer类,下面对其内部实现进行详细的讲解。

图10主要功能类主文件

1、构造函数

在构造函数中主要进行了全局变量的初始化和对PROXY_HOST(VideoCache代理接口,也就是LocalURL所属的代理接口)进行访问,判断是否可以直接ping通。

  1. private HttpProxyCacheServer(Config config) {  
  2.     this.config = checkNotNull(config); 
  3.     try {  
  4.     //初始化各种全局变量 
  5.         InetAddress inetAddress = InetAddress.getByName(PROXY_HOST); 
  6.         this.serverSocket = new ServerSocket(0, 8, inetAddress); 
  7.         this.port = serverSocket.getLocalPort(); 
  8.         IgnoreHostProxySelector.install(PROXY_HOST, port); 
  9.         CountDownLatch startSignal = new CountDownLatch(1); 
  10.         this.waitConnectionThread = new Thread(new WaitRequestsRunnable(startSignal)); 
  11.         this.waitConnectionThread.start(); 
  12.         startSignal.await(); // freeze thread, wait for server starts 
  13.     //获取对PROXY_HOST& port的ping,判断是否可以ping通 
  14.         this.pinger = new Pinger(PROXY_HOST, port); 
  15.         LOG.info("Proxy cache server started. Is it alive? " + isAlive()); 
  16.     } catch (IOException | InterruptedException e) {  
  17.         socketProcessor.shutdown(); 
  18.         throw new IllegalStateException("Error starting local proxy server", e); 
  19.     } 

2、registerCacheListener函数

这个函数主要实现的功能是对URL进行注册监听。

  1. public void registerCacheListener(CacheListener cacheListener, String url) {  
  2.     checkAllNotNull(cacheListener, url); 
  3.     synchronized (clientsLock) {  
  4.         try {  
  5.       //对url获取Clients,并为其注册CacheListener 
  6.             getClients(url).registerCacheListener(cacheListener); 
  7.         } catch (ProxyCacheException e) {  
  8.             LOG.warn("Error registering cache listener", e); 
  9.         } 
  10.     } 

3、getProxyUrl函数

该函数实现了将(已经注册过的)URL转化为cached LocalURL的功能。

  1. public String getProxyUrl(String url) {  
  2.     return getProxyUrl(url, true); 
  3.  
  4. public String getProxyUrl(String url, boolean allowCachedFileUri) {  
  5.     if (allowCachedFileUri && isCached(url)) {  
  6.         File cacheFile = getCacheFile(url); 
  7.         touchFileSafely(cacheFile); 
  8.         return Uri.getUriFromFile(cacheFile).toString(); 
  9.     } 
  10.     return isAlive() ? appendToProxyUrl(url) : url; 

当传入一个网络视频的URL时,该方法会对该URL进行判断,如果可以在代理服务器上进行缓存,则提供正确的LocalURL返回值,否则返回原URL。

项目贡献人

吕泽 郑森文 朱伟 陈美汝 张馨心

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

 

责任编辑:jianghua 来源: 鸿蒙社区 鸿蒙HarmonyOS应用

(责任编辑:探索)

    推荐文章
    • A股275家公司无主 信息技术企业占比近三成

      A股275家公司无主 信息技术企业占比近三成亚太药业(002370)一则将变为“无主”状态的公告引起关注。据Wind数据统计,截至11月7日,除亚太药业外,两市共有275家上市公司处于无实控人状态。北京商报记者通过梳理发 ...[详细]
    • 焕然·新泉源——佛山顺德海骏达广场

      焕然·新泉源——佛山顺德海骏达广场顺德海骏达城,总建筑面积达50万平米的超大型商业综合体。规划为一座超高层建筑,主力12万平方综合购物中心,配套国际五星级酒店希尔顿及写字楼、高尚住宅。海骏达城主塔高达208米,与始建于清朝的文塔互相辉 ...[详细]
    • 小米平板5标准版曝光:骁龙860+120Hz高刷屏

      小米平板5标准版曝光:骁龙860+120Hz高刷屏最新消息称,小米新平板有望8月份发布,归属一个系列,提供多款设备,目前已知的至少两款。按照命名规律,应该称之为小米平板5系列,其中型号为M2105K81C的小米设备已通过了工信部认证,被指为搭载骁龙8 ...[详细]
    • 感受“历史”与“革新”——京都的艺术光景

      感受“历史”与“革新”——京都的艺术光景京都自古以来就与艺术有着密不可分的联系,在京都市内随处可见的寺院佛阁中,有着许多由画家捐赠给寺院的隔扇画、天花板画。近来,由设计师创办的艺术酒店、新进设计师举办的活动也越来越多,京都的艺术发展每年都呈 ...[详细]
    • 大生农业金融(01103.HK)发布公告:年度公司持有人应占亏损11.25亿元

      大生农业金融(01103.HK)发布公告:年度公司持有人应占亏损11.25亿元大生农业金融(01103.HK)发布公告,截至2021年12月31日止年度,持续经营业务实现总收益人民币9.37亿元,同比下降34.7%;公司持有人应占年度亏损11.25亿元,同比扩大44.33%;来 ...[详细]
    • 拿稳这波新马航千元机票大福利,黄金周浪得毫无压力

      拿稳这波新马航千元机票大福利,黄金周浪得毫无压力深居城市越久,越需要一场说走就走的旅行,来抚慰自己被生活琐事层层裹挟的身心。随着金秋9月的到来,久违的下半年假期终于到账,中秋节、十一黄金周、元旦、春节接踵而至,是时候将搁置许久的出游计划重新提上日程 ...[详细]
    • Redmi Note 11 Pro概念图曝光:好看!

      Redmi Note 11 Pro概念图曝光:好看!目前Redmi Note10 Pro还在热销中,但大家对于新手机的追求始终是狂热的,近日,外媒一位设计师带来了Redmi Note 11 Pro的概念机渲染图。从图上可以看到,Redmi Note 1 ...[详细]
    • 马汇Horseforex:玩转期货交易策略

      马汇Horseforex:玩转期货交易策略一个投资者,假设可以保持较长一段时间的成功生意,那么,一般说明他关于商场有了某种程度的了解和认知;形成了适合自己的一整套的生意理念、生意战略、生意规矩、生意方法以及交 易技巧;具有了恰当的执行力、控制 ...[详细]
    • 农行网捷贷利息一般是多少 农行网捷贷是不是随借随还?

      农行网捷贷利息一般是多少 农行网捷贷是不是随借随还?作为四大银行之一,农业银行旗下的贷款产品是非常多的。为了满足更多客户的贷款需求,农业银行也推出了一些线上可以申请的个人小额贷款产品,网捷贷。农行网捷贷利息高吗?农行网捷贷是不是随借随还?一起来跟希财君 ...[详细]
    • 宫本茂:以后别叫我“游戏界的斯皮尔伯格”

      宫本茂:以后别叫我“游戏界的斯皮尔伯格”任天堂传奇制作人、马里奥之父宫本茂想告诉大家,以后别再称他为“游戏界的斯皮尔伯格”了。当然,宫本茂和斯皮尔伯格都是各自领域里的大师级人物,早在1999年《时代》杂志的封面文章中就开始将宫本茂称作“游戏 ...[详细]
    热点阅读