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

 鸿蒙开源第三方组件-VideoCache视频缓存组件 鸿蒙 HarmonyOS 应用 第1张

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

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

https://HarmonyOS.51cto.com

前言

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

背景

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

组件效果图展示

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

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

 鸿蒙开源第三方组件-VideoCache视频缓存组件 鸿蒙 HarmonyOS 应用 第2张

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

2、验证缓存

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

 鸿蒙开源第三方组件-VideoCache视频缓存组件 鸿蒙 HarmonyOS 应用 第3张

图 2 关闭网络连接

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

 鸿蒙开源第三方组件-VideoCache视频缓存组件 鸿蒙 HarmonyOS 应用 第4张

图 3 缓存播放视频

Sample解析

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

 鸿蒙开源第三方组件-VideoCache视频缓存组件 鸿蒙 HarmonyOS 应用 第5张

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

1、实例化HttpProxyCacheServer类的对象

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

  1. privateHttpProxyCacheServermCacheServerProxy=null;
  2. publicvoidonStart(Intentintent){
  3. ...
  4. if(mCacheServerProxy==null){
  5. Contextcontext=this;
  6. //实例化HttpProxyCacheServer对象
  7. mCacheServerProxy=newHttpProxyCacheServer(context);
  8. }
  9. ...
  10. }

2、定义缓存监听器CacheListener

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

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

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

  1. privateCacheListenermCacheListener=newCacheListener(){
  2. @Override
  3. publicvoidonCacheAvailable(FilecacheFile,Stringurl,intpercentsAvailable){
  4. //打印实时缓存进度
  5. HiLog.info(newHiLogLabel(3,0,"cache"),"Saving……,percent:"+String.valueOf(percentsAvailable));
  6. //当进度达到100时,可进行一些特殊操作,此处仅以log打印为例
  7. if(percentsAvailable==100&&!cacheFile.getPath().endsWith(".download")){
  8. HiLog.info(newHiLogLabel(3,0,"cache"),"Downloadalready!");
  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所示。

 鸿蒙开源第三方组件-VideoCache视频缓存组件 鸿蒙 HarmonyOS 应用 第6张

图5 library的组成结构

一、file

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

 鸿蒙开源第三方组件-VideoCache视频缓存组件 鸿蒙 HarmonyOS 应用 第7张

图6 file文件夹的组成结构

1、FileCache类

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

  1. //定义缓存文件的后缀格式
  2. privatestaticfinalStringTEMP_POSTFIX=".download";
  3. publicFileCache(Filefile,DiskUsagediskUsage)throwsProxyCacheException{
  4. ...
  5. Filedirectory=file.getParentFile();
  6. Files.makeDir(directory);
  7. booleancompleted=file.exists();
  8. //文件的保存格式:根目录文件+文件名+之前定义的文件后缀格式
  9. this.file=completed?file:newFile(file.getParentFile(),file.getName()+TEMP_POSTFIX);
  10. //文件权限设置。缓存完成,文件只能读取;未缓存完成,文件可读可写。
  11. this.dataFile=newRandomAccessFile(this.file,completed?"r":"rw");
  12. }catch(IOExceptione){
  13. thrownewProxyCacheException("Errorusingfile"+file+"asdisccache",e);
  14. }

2、Files类

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

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

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

3、LruDiskUsage类

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

  1. privatevoidtrim(List<File>files){
  2. longtotalSize=countTotalSize(files);//缓存文件的总大小
  3. inttotalCount=files.size();//缓存文件的总数量
  4. for(Filefile:files){
  5. //未超过缓存文件的(总大小&总数量)的阈值时,接收缓存
  6. booleanaccepted=accept(file,totalSize,totalCount);
  7. if(!accepted){
  8. longfileSize=file.length();//单一文件的大小
  9. booleandeleted=file.delete();//文件是否为预备删除的文件
  10. //如果是准备删除的文件
  11. if(deleted){
  12. totalCount--;//缓存文件的总数量-1
  13. totalSize-=fileSize;//缓存文件的总大小-预备删除的单一文件的大小
  14. LOG.info("Cachefile"+file+
  15. "isdeletedbecauseitexceedscachelimit");
  16. }else{
  17. LOG.error("Errordeletingfile"+file+"fortrimmingcache");
  18. }
  19. }
  20. }
  21. }

4、 Md5FileNameGenerator类

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

  1. publicclassMd5FileNameGeneratorimplementsFileNameGenerator{
  2. privatestaticfinalintMAX_EXTENSION_LENGTH=4;
  3. @Override
  4. publicStringgenerate(Stringurl){
  5. //获取文件名的后缀
  6. Stringextension=getExtension(url);
  7. //获取MD5值
  8. Stringname=ProxyCacheUtils.computeMD5(url);
  9. BooleanisEmpty=false;
  10. //文件后缀名为空时,设置isEmpty标志位为true
  11. if(extension==null||extension.length()==0)
  12. isEmpty=true;
  13. returnisEmpty?name:name+"."+extension;
  14. }

5、TotalCountLruDiskUsage类、TotalSizeLruDiskUsage类和UnlimitedDiskUsage类

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

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

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

  1. //控制缓存文件的总数量
  2. publicclassTotalCountLruDiskUsageextendsLruDiskUsage{
  3. privatefinalintmaxCount;
  4. //设置缓存文件的总数量的阈值
  5. publicTotalCountLruDiskUsage(intmaxCount){
  6. if(maxCount<=0){
  7. thrownewIllegalArgumentException("Maxcountmustbepositivenumber!");
  8. }
  9. this.maxCount=maxCount;
  10. }
  11. //当前缓存文件的总数量小于设定的阈值时,新文件accept
  12. @Override
  13. protectedbooleanaccept(Filefile,longtotalSize,inttotalCount){
  14. returntotalCount<=maxCount;
  15. }
  16. }
  17. //控制制缓存文件的总大小
  18. publicclassTotalSizeLruDiskUsageextendsLruDiskUsage{
  19. privatefinallongmaxSize;
  20. //设置制缓存文件的总大小的阈值
  21. publicTotalSizeLruDiskUsage(longmaxSize){
  22. if(maxSize<=0){
  23. thrownewIllegalArgumentException("Maxsizemustbepositivenumber!");
  24. }
  25. this.maxSize=maxSize;
  26. }
  27. //当前缓存文件的总大小小于设定的阈值时,新文件accept
  28. @Override
  29. protectedbooleanaccept(Filefile,longtotalSize,inttotalCount){
  30. returntotalSize<=maxSize;
  31. }
  32. }

二、headers

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

 鸿蒙开源第三方组件-VideoCache视频缓存组件 鸿蒙 HarmonyOS 应用 第8张

图7 headers文件夹的组成结构

三、slice

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

 鸿蒙开源第三方组件-VideoCache视频缓存组件 鸿蒙 HarmonyOS 应用 第9张

图8 slice文件夹的组成结构

四、sourcestorage

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

 鸿蒙开源第三方组件-VideoCache视频缓存组件 鸿蒙 HarmonyOS 应用 第10张

图9 sourcestorage文件夹的组成结构

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

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

  1. classDatabaseSourceInfoStorageextendsDatabaseHelperimplementsSourceInfoStorage{
  2. //数据库中存储SourInfo:URL、Length、MIME
  3. privatestaticfinalStringTABLE="SourceInfo";
  4. privatestaticfinalStringCOLUMN_ID="_id";
  5. privatestaticfinalStringCOLUMN_URL="url";
  6. privatestaticfinalStringCOLUMN_LENGTH="length";
  7. privatestaticfinavlStringCOLUMN_MIME="mime";
  8. privatestaticfinalString[]ALL_COLUMNS=newString[]{COLUMN_ID,COLUMN_URL,
  9. COLUMN_LENGTH,COLUMN_MIME};
  10. //创建数据库的SQL
  11. privatestaticfinalStringCREATE_SQL=
  12. "CREATETABLE"+TABLE+"("+
  13. COLUMN_ID+"INTEGERPRIMARYKEYAUTOINCREMENTNOTNULL,"+
  14. COLUMN_URL+"TEXTNOTNULL,"+
  15. COLUMN_MIME+"TEXT,"+
  16. COLUMN_LENGTH+"INTEGER"+
  17. ");";
  18. privatefinalRdbStoremyRdbStore;
  19. //连接的数据库名字
  20. privatefinalStoreConfigconfig=
  21. StoreConfig.newDefaultConfig("AndroidVideoCache.db");
  22. }
  23. //数据库get指令,通过URL获取SourceInfo
  24. publicSourceInfoget(Stringurl){
  25. checkNotNull(url);
  26. ResultSetcursor=null;
  27. try{
  28. RdbPredicatespredicates=newRdbPredicates(TABLE);
  29. predicates.equalTo(COLUMN_URL,url);
  30. cursor=this.myRdbStore.query(predicates,null);
  31. returncursor==null||!cursor.goToFirstRow()?null:convert(cursor);
  32. }finally{
  33. if(cursor!=null){
  34. cursor.close();
  35. }
  36. }
  37. }
  38. //数据库put指令,将url和SourceInfo在数据库中登记绑定
  39. publicvoidput(Stringurl,SourceInfosourceInfo){
  40. checkAllNotNull(url,sourceInfo);
  41. SourceInfosourceInfoFromDb=get(url);
  42. booleanexist=sourceInfoFromDb!=null;
  43. RdbPredicatespredicates=newRdbPredicates(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. }
  51. //release指令:释放数据库控制流
  52. @Override
  53. publicvoidrelease(){
  54. this.myRdbStore.close();
  55. }

五、主功能文件

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

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

 鸿蒙开源第三方组件-VideoCache视频缓存组件 鸿蒙 HarmonyOS 应用 第11张

图10主要功能类主文件

1、构造函数

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

  1. privateHttpProxyCacheServer(Configconfig){
  2. this.config=checkNotNull(config);
  3. try{
  4. //初始化各种全局变量
  5. InetAddressinetAddress=InetAddress.getByName(PROXY_HOST);
  6. this.serverSocket=newServerSocket(0,8,inetAddress);
  7. this.port=serverSocket.getLocalPort();
  8. IgnoreHostProxySelector.install(PROXY_HOST,port);
  9. CountDownLatchstartSignal=newCountDownLatch(1);
  10. this.waitConnectionThread=newThread(newWaitRequestsRunnable(startSignal));
  11. this.waitConnectionThread.start();
  12. startSignal.await();//freezethread,waitforserverstarts
  13. //获取对PROXY_HOST&port的ping,判断是否可以ping通
  14. this.pinger=newPinger(PROXY_HOST,port);
  15. LOG.info("Proxycacheserverstarted.Isitalive?"+isAlive());
  16. }catch(IOException|InterruptedExceptione){
  17. socketProcessor.shutdown();
  18. thrownewIllegalStateException("Errorstartinglocalproxyserver",e);
  19. }
  20. }

2、registerCacheListener函数

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

  1. privateCacheListenermCacheListener=newCacheListener(){
  2. @Override
  3. publicvoidonCacheAvailable(FilecacheFile,Stringurl,intpercentsAvailable){
  4. //打印实时缓存进度
  5. HiLog.info(newHiLogLabel(3,0,"cache"),"Saving……,percent:"+String.valueOf(percentsAvailable));
  6. //当进度达到100时,可进行一些特殊操作,此处仅以log打印为例
  7. if(percentsAvailable==100&&!cacheFile.getPath().endsWith(".download")){
  8. HiLog.info(newHiLogLabel(3,0,"cache"),"Downloadalready!");
  9. }
  10. }
  11. };
0

3、getProxyUrl函数

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

  1. privateCacheListenermCacheListener=newCacheListener(){
  2. @Override
  3. publicvoidonCacheAvailable(FilecacheFile,Stringurl,intpercentsAvailable){
  4. //打印实时缓存进度
  5. HiLog.info(newHiLogLabel(3,0,"cache"),"Saving……,percent:"+String.valueOf(percentsAvailable));
  6. //当进度达到100时,可进行一些特殊操作,此处仅以log打印为例
  7. if(percentsAvailable==100&&!cacheFile.getPath().endsWith(".download")){
  8. HiLog.info(newHiLogLabel(3,0,"cache"),"Downloadalready!");
  9. }
  10. }
  11. };
1

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

项目贡献人

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

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

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

https://harmonyos.51cto.com

 鸿蒙开源第三方组件-VideoCache视频缓存组件 鸿蒙 HarmonyOS 应用 第12张

转载请说明出处
知优网 » 鸿蒙开源第三方组件-VideoCache视频缓存组件

发表评论

您需要后才能发表评论