HarmonyOS上视频跨设备协同技术超全详解

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

创新互联公司是一家专注于成都网站制作、网站建设与策划设计,赣榆网站建设哪家好?创新互联公司做网站,专注于网站建设10多年,网设计领域的专业建站公司;建站业务涵盖:赣榆等地区。赣榆做网站价格咨询:18982081108

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

https://harmonyos.

1. 介绍

您将会学到什么

● 如何使用PageSlider、PageSliderIndicator和ListContainer编写定时滚动及可滑动的页面。

● 如何使用分布式能力实现跨设备视频播放。

● 如何使用HarmonyOS IDL跨进程通信实现远程控制视频播放。

技能要求

● HarmonyOS Player接口熟练使用

● 基本组件熟练使用

说明

本篇Codelab所附代码适合在真机运行。运行时需要至少两台手机处于同一个分布式网络中,可以通过操作如下配置实现:

● 所有手机接入同一网络

● 所有手机登录相同华为账号

● 所有手机上开启“设置->更多连接->多设备协同 ”

2. 代码结构

在鸿蒙上实现本地和Internet视频资源播放已对视频播放和播放界面代码结构做了讲解,本次Codelab只对视频列表页、视频迁移设备列表、迁移后控制界面及迁移服务核心代码做讲解,对于完整代码,我们会在参考提供下载方式。代码结构图如下:

● provider:该目录包含CommonProvider、ViewProvider和AdvertisementProvider。CommonProvider是一个ListContainer 多样式提供者管理类。ViewProvider结合CommonProvider使用,可以把布局文件中需要赋值的控件单独提取出来进行赋值。AdvertisementProvider实现广告视频资源定时滚动的效果。

● ImplVideoMigration.idl:接口中定义了视频迁入、迁出、根据控制码对视频进行远程控制方法。

● data:该目录包括滚动视频广告对象封装、即将上映视频对象封装以及视频图片格式定义。

● VideoMigrateService:供远端连接的Service Ability。

● manager:该目录下的文件为ImplVideoMigration.idl在编译时自行生成,初始生成位置为entry\build\generated\source\idl\com\huawei\codelab。

● MediaUtil:对广告和视频列表对象初始化赋值。

● config.json:配置文件,新增权限配置如下图:

1. ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE:用于允许监听分布式组网内的设备状态变化。

2. ohos.permission.GET_DISTRIBUTED_DEVICE_INFO:用于允许获取分布式组网内的设备列表和设备信息。

3. ohos.permission.GET_BUNDLE_INFO:用于查询其他应用的信息。

4. ohos.permission.DISTRIBUTED_DATASYNC:用于允许不同设备间的数据交换。

5. ohos.permission.INTERNET:用于允许设备访问网络。

3. 创建应用程序布局文件

在路径"resources/base/layout"文件夹下创建video.xml为应用主页面,展示要播放的视频列表。

 
 
 
 
  1.                    ohos:width="match_parent" 
  2.                    ohos:height="match_parent" 
  3.                    ohos:orientation="vertical"> 
  4.     
  5.         ohos:height="match_content" 
  6.         ohos:width="match_parent" 
  7.         ohos:orientation="vertical" 
  8.         > 
  9.          
  10.         
  11.             ohos:id="$+id:video_advertisement_container_view" 
  12.             ohos:width="match_parent" 
  13.             ohos:left_margin="20vp" 
  14.             ohos:height="175vp" 
  15.             ohos:top_margin="20vp" 
  16.             ohos:right_margin="12vp" 
  17.             > 
  18.             
  19.                 ohos:id="$+id:video_advertisement_viewpager" 
  20.                 ohos:width="match_parent" 
  21.                 ohos:height="match_parent" 
  22.                 ohos:orientation="horizontal"/> 
  23.   
  24.             
  25.                 ohos:id="$+id:video_advertisement_indicator" 
  26.                 ohos:right_margin="8vp" 
  27.                 ohos:bottom_margin="7vp" 
  28.                 ohos:width="match_content" 
  29.                 ohos:height="match_content" 
  30.                 ohos:align_parent_bottom="true" 
  31.                 ohos:align_parent_right="true" /> 
  32.          
  33.          
  34.         
  35.             ohos:width="match_parent" 
  36.             ohos:height="22vp" 
  37.             ohos:top_margin="12vp" 
  38.             ohos:left_margin="24vp" 
  39.             ohos:right_margin="12vp" 
  40.             ohos:orientation="horizontal"> 
  41.             
  42.                 ohos:id="$+id:video_play_title" 
  43.                 ohos:text="Coming soon" 
  44.                 ohos:text_size="16fp" 
  45.                 ohos:text_color="#ff000000" 
  46.                 ohos:text_alignment="4" 
  47.                 ohos:layout_alignment="vertical_center" 
  48.                 ohos:width="match_content" 
  49.                 ohos:height="match_content" /> 
  50.             
  51.                 ohos:left_margin="6vp" 
  52.                 ohos:width="13vp" 
  53.                 ohos:height="13vp" 
  54.                 ohos:layout_alignment="vertical_center" 
  55.                 ohos:image_src="$media:ic_next"/> 
  56.   
  57.          
  58.          
  59.         
  60.             ohos:width="match_parent" 
  61.             ohos:height="500vp" 
  62.             ohos:orientation="vertical"> 
  63.             
  64.                 ohos:id="$+id:video_list_play_view" 
  65.                 ohos:width="match_parent" 
  66.                 ohos:height="match_content" 
  67.                 ohos:orientation="horizontal" 
  68.                 ohos:left_margin="18vp" 
  69.                 ohos:top_margin="12vp" 
  70.                 > 
  71.              
  72.          
  73.      
  74.   

video.xml采用垂直方向的线性布局方式。整个页面分为三部分的内容。从上至下依次是PageSlider滚动广告布局,即将上映视频图标布局,可左右滑动的listContainer布局。

PageSlider是一个描述滚动页面的组件,PageSliderIndicator是一个将滚动页面组件和其它组件比如图标、按钮等组合管理的管理器。本应用程序展示的滚动广告页面采取的是三组广告图片和图片title组成的PageSlider,广告图片和图片title组合样式由AdvertisementProvider定义。AdvertisementMo初始化代码如下:

 
 
 
 
  1. public AdvertisementMo(int sourceId, String description) { 
  2.     this.sourceId = sourceId; 
  3.     this.description = description; 
  4. videoAdvertisementMos.add(new AdvertisementMo(ResourceTable.Media_video_advertisement0, "玩心释放 尽情创想")); 
  5. videoAdvertisementMos.add(new AdvertisementMo(ResourceTable.Media_video_advertisement1, "玩心释放 尽情创想")); 
  6. videoAdvertisementMos.add(new AdvertisementMo(ResourceTable.Media_video_advertisement2, "一起创造 焕新假期"));

AdvertisementProvider对滚动视频广告组件以list形式进行封装。

 
 
 
 
  1. public class AdvertisementProvider extends PageSliderProvider { 
  2.     private List componentList; 
  3.     public AdvertisementProvider(List componentList) { 
  4.         this.componentList = componentList; 
  5.     } 
  6. }

通过PageSlider对象的setProvider(CommProvider)方法即可达到对图片列表地滚动显示效果。

 
 
 
 
  1. advertisementProvider = new AdvertisementProvider(getAdvertisementComponents()); 
  2. Component advViewPager = findComponentById(ResourceTable.Id_video_advertisement_viewpager); 
  3. if (advViewPager instanceof PageSlider) { 
  4.     advPageSlider = (PageSlider) advViewPager; 
  5.     advPageSlider.setProvider(advertisementProvider); 
  6. }

getAdertisementCompoents方法将滚动视频广告添加到list。

 
 
 
 
  1. private List getAdvertisementComponents() { 
  2.     List advertisementMos = MediaUtil.getVideoAdvertisementInfo(); 
  3.     List componentList = new ArrayList<>(advertisementMos.size()); 
  4.     Font.Builder fb = new Font.Builder(VideoTabStyle.BOLD_FONT_NAME); 
  5.     fb.setWeight(Font.BOLD); 
  6.     Font newFont = fb.build(); 
  7.     for (AdvertisementMo advertisementMo : advertisementMos) { 
  8.         Component advRootView = LayoutScatter.getInstance(getContext()).parse( 
  9.                 ResourceTable.Layout_video_advertisement_item, null, false); 
  10.         Image imgTemp = null; 
  11.         if (advRootView.findComponentById(ResourceTable.Id_video_advertisement_poster) instanceof Image) { 
  12.             imgTemp = (Image) advRootView.findComponentById(ResourceTable.Id_video_advertisement_poster); 
  13.         } 
  14.         imgTemp.setPixelMap(advertisementMo.getSourceId()); 
  15.         Text titleTmp = null; 
  16.         if (advRootView.findComponentById(ResourceTable.Id_video_advertisement_title) instanceof Text) { 
  17.             titleTmp = (Text) advRootView.findComponentById(ResourceTable.Id_video_advertisement_title); 
  18.         } 
  19.         titleTmp.setText(advertisementMo.getDescription()); 
  20.         titleTmp.setFont(newFont); 
  21.         componentList.add(advRootView); 
  22.     } 
  23.   
  24.     return componentList; 
  25. }

想要实现滚动到某一特定图片时呈现标志,在图片上方加上一组空心圆,当滚动到第一张图片时,第一个圆变为实心,此联动实现效果可通过PageSliderIndicator实现。

 
 
 
 
  1. PageSliderIndicator advIndicator = null; 
  2. if (findComponentById(ResourceTable.Id_video_advertisement_indicator) instanceof PageSliderIndicator) { 
  3.     advIndicator = (PageSliderIndicator) findComponentById( 
  4.             ResourceTable.Id_video_advertisement_indicator); 
  5. advIndicator.setItemOffset(VideoTabStyle.INDICATOR_OFFSET);

实心圆效果:

 
 
 
 
  1. ShapeElement normalDrawable = new ShapeElement(); 
  2. normalDrawable.setRgbColor(RgbColor.fromRgbaInt(Color.WHITE.getValue())); 
  3. normalDrawable.setAlpha(VideoTabStyle.INDICATOR_NORMA_ALPHA); 
  4. normalDrawable.setShape(ShapeElement.OVAL); 
  5. normalDrawable.setBounds(0, 0, VideoTabStyle.INDICATOR_BONDS, VideoTabStyle.INDICATOR_BONDS);

空心圆效果:

 
 
 
 
  1. ShapeElement selectedDrawable = new ShapeElement(); 
  2. selectedDrawable.setRgbColor(RgbColor.fromRgbaInt(Color.WHITE.getValue())); 
  3. selectedDrawable.setShape(ShapeElement.OVAL); 
  4. selectedDrawable.setBounds(0, 0, VideoTabStyle.INDICATOR_BONDS, VideoTabStyle.INDICATOR_BONDS);

实心圆、空心圆效果如下图:

PageSliderIndicator通过设置可选类型将会实现图片被选中时,将会显示实心圆。

 
 
 
 
  1. advIndicator.setItemElement(normalDrawable, selectedDrawable); 
  2. advIndicator.setViewPager((PageSlider) advViewPager);

本节任务完成的效果如下图:

视频播放业务本次Codelab不再描述,下面直接进入视频流转环节。

4. 视频跨设备协同

HarmonyOS提供了分布式跨设备能力,本小节可以实现将视频迁移到分布式环境中的其它设备上,被迁移设备可以实现对迁移设备的视频操作控制。

首先对视频播放界面中迁移按钮增加监听事件,在点击时,从窗口底部滑出分布式设备列表界面可供选择迁移。

 
 
 
 
  1. tv = (Image) simplePlayerController.findComponentById(ResourceTable.Id_tv); 
  2. tv.setClickedListener(new Component.ClickedListener() { 
  3.     @Override 
  4.     public void onClick(Component component) { 
  5.         initDevices(); 
  6.         showDeviceList(); 
  7.     } 
  8. });

通过分布式设备管理器DeviceManager获取到当前分布式网络中可发现的所有设备并全部添加到设备列表。如果设备列表初始不为空,先将列表清空,再添加,以达到刷新设备列表效果。

 
 
 
 
  1. private void initDevices() { 
  2.     if (devices.size() > 0) { 
  3.         devices.clear(); 
  4.     } 
  5.     // 通过FLAG_GET_ONLINE_DEVICE标记获得在线设备列表 
  6.     List deviceInfos = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE); 
  7.     devices.addAll(deviceInfos); 
  8. }

显示设备列表使用单样式的内容提供器CommonProvider,设置设备名字样式。

 
 
 
 
  1. private void showDeviceList() { 
  2.     CommonProvider commonProvider = new CommonProvider(devices,getContext(), ResourceTable.Layout_device_list_item) { 
  3.         @Override 
  4.         protected void convert(ViewProvider viewProvider, DeviceInfo item, int position) { 
  5.             viewProvider.setText(ResourceTable.Id_device_text, item.getDeviceName()); 
  6.         } 
  7.     }; 
  8.     // 对deviceListContainer注入commonProvider,完成设备列表资源样式设置 
  9.     deviceListContainer.setItemProvider(commonProvider); 
  10.     // 通知列表数据发生变化更新设备列表 
  11.     commonProvider.notifyDataChanged(); 
  12.     transWindow.show(); 
  13. }

创建设备列表显示组件SlidePopupWindow。设备列表是一个从底部滑出的一个窗口,属于自定义组件。核心功能是设备列表的显示与隐藏。

 
 
 
 
  1. public void show() { 
  2.     if (!isShow) { 
  3.         isShow = true; 
  4.         animatorProperty 
  5.                 .moveFromX(startX) 
  6.                 .moveToX(endX) 
  7.                 .moveFromY(startY) 
  8.                 .moveToY(endY) 
  9.                 .setCurveType(Animator.CurveType.LINEAR) 
  10.                 .setDuration(ANIM_DURATION) 
  11.                 .start(); 
  12.     } 
  13.   
  14. public void hide() { 
  15.     if (isShow) { 
  16.         isShow = false; 
  17.         animatorProperty 
  18.                 .moveFromX(endX) 
  19.                 .moveToX(startX) 
  20.                 .moveFromY(endY) 
  21.                 .moveToY(startY) 
  22.                 .setCurveType(Animator.CurveType.LINEAR) 
  23.                 .setDuration(ANIM_DURATION) 
  24.                 .start(); 
  25.     } 
  26. }

设备列表效果如下图:

点击列表中某一个设备,将在已选设备端拉起该视频应用。

 
 
 
 
  1. deviceListContainer.setItemClickedListener(new ListContainer.ItemClickedListener() { 
  2.     @Override 
  3.     public void onItemClicked(ListContainer listContainer, Component component, int num, long l) { 
  4.         // 列表窗口隐藏 
  5.         transWindow.hide(); 
  6.         startAbilityFa(devices.get(num).getDeviceId()); 
  7.     } 
  8. });

通过startAbilityFa()跨设备拉起视频FA,再调用connectAbility()异步对远端服务连接,成功连接后,在回调onAbilityConnectDone中服务端恢复视频数据。

 
 
 
 
  1. private void startAbilityFa(String devicesId) { 
  2.     Intent intent = new Intent(); 
  3.     Operation operation = 
  4.             new Intent.OperationBuilder() 
  5.                     .withDeviceId(devicesId) 
  6.                     .withBundleName(getBundleName()) 
  7.                     .withAbilityName(VideoMigrateService.class.getName()) 
  8.                     .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE) 
  9.                     .build();// 开发者需要在Intent中设置支持分布式的标记FLAG_ABILITYSLICE_MULTI_DEVICE,否则无法获得分布式能力 
  10.     intent.setOperation(operation); 
  11.     boolean connectFlag = connectAbility(intent, 
  12.                     new IAbilityConnection() { 
  13.                         @Override 
  14.                         public void onAbilityConnectDone( 
  15.                                 ElementName elementName, IRemoteObject remoteObject, int i) { 
  16.                             // asInterface的作用是根据调用的服务是否属于同进程而返回不同的实例对象 
  17.                             implVideoMigration = VideoMigrationStub.asInterface(remoteObject); 
  18.                             try { 
  19.                                 implVideoMigration.flyIn(startMillisecond); 
  20.                             } catch (RemoteException e) { 
  21.                                 LogUtil.error(TAG, "connect successful,but have remote exception"); 
  22.                             } 
  23.                         } 
  24.   
  25.                         @Override 
  26.                         public void onAbilityDisconnectDone(ElementName elementName, int i) { 
  27.                             disconnectAbility(this); 
  28.                         } 
  29.                     }); 
  30.     if (connectFlag) { 
  31.         Toast.toast(this, "migrate successful!", TOAST_DURATION); 
  32.         remoteController.show(); 
  33.         startMillisecond = implPlayer.getAudioCurrentPosition();// 获取视频当前播放进度 
  34.         implPlayer.release();// 释放资源 
  35.     } else { 
  36.         Toast.toast(this, "migrate failed!Please try again later.", TOAST_DURATION); 
  37.     } 
  38. }

通过指定abilityName为VideoMigrateService,执行VideoMigrateService中onConnect(intent)方法,返回binder对象,回调onAbilityConnectDone拿到具体的binder对象。VideoMigrationStub.asInterface(remoteObject)根据调用是否属于同进程而返回不同的实例对象, 由于返回的binder不是本进程的,所以返回的是VideoMigrationProxy对象。

接下来我们分别把本端设备称为设备A,跨设备协同端称为设备B。 implVideoMigration.flyIn(startMillisecond)由设备A即VideoMigrationProxy执行,通过sendRequest发送到设备B。

 
 
 
 
  1. remote.sendRequest(COMMAND_FLY_IN, data, reply, option);

设备B通过接收到的code类型为COMMAND_FLY_IN在服务端执行视频数据恢复。

 
 
 
 
  1. @Override 
  2. public void flyIn(int startTimemiles) throws RemoteException { 
  3.     Intent intent = new Intent(); 
  4.     Operation operation = 
  5.             new Intent.OperationBuilder() 
  6.                     .withBundleName(getBundleName()) 
  7.                     .withAbilityName(MainAbility.class.getName()) 
  8.                     .withAction("action.video.play") 
  9.                     .build(); 
  10.     intent.setOperation(operation); 
  11.     intent.setParam(Constants.INTENT_STARTTIME_PARAM, startTimemiles); 
  12.     startAbility(intent); 
  13. }

设备B呈现播放界面并跳转到Intent中携带的播放位置。在设备A的视频应用跨设备协同到设备B时,设备A会释放掉视频资源并展示RemoteController。

 
 
 
 
  1. if (connectFlag) { 
  2.     Toast.toast(this, "migrate successful!", TOAST_DURATION); 
  3.     remoteController.show();// 控制界面出现 
  4.     startMillisecond = implPlayer.getAudioCurrentPosition(); 
  5.     implPlayer.release(); 
  6. }

设备A的RemoteController在创建时初始化界面布局。通过操作界面控件来控制设备B视频播放。例如点击前进按钮,RemoteController发送FORWARD 控制码。SimplePlayerAbilitySlice通过添加RemoteController.RemoteControllerListener来执行回调方法sendControl,再通过implVideoMigration代理对象与对端进行通信。

 
 
 
 
  1. remoteController.setRemoteControllerCallback(new RemoteController.RemoteControllerListener() { 
  2. @Override 
  3.     public void sendControl(int code, int extra) { 
  4.     try { 
  5.         if (implVideoMigration != null) { 
  6.             // 调用设备A服务代理对象的playControl方法通过binder对象调用设备B服务端的playControl方法 
  7.             implVideoMigration.playControl(code, extra); 
  8.         } 
  9.     } catch (RemoteException e) { 
  10.         LogUtil.error(TAG, "RemoteException occurs "); 
  11.     } 
  12.   } 
  13. });

设备A效果如下图:

设备B效果如下图:

当设备A在RemoteController界面执行返回操作时,会隐藏RemoteController,同时设备A继续播放。

 
 
 
 
  1. public void hide() { 
  2.     if (isShown) { 
  3.         isShown = false; 
  4.         setVisibility(INVISIBLE); 
  5.         if (remoteControllerListener != null) { 
  6.             remoteControllerListener.controllerDismiss(); 
  7.         } 
  8.     } 
  9. remoteController.setRemoteControllerCallback(new RemoteController.RemoteControllerListener() { 
  10. @Override 
  11. public void controllerDismiss() { 
  12.     int progress = 0; 
  13.     try { 
  14.         if (implVideoMigration!= null) { 
  15.             // 迁回视频时获取进度条进度 
  16.             progress = implVideoMigration.flyOut(); 
  17.         } 
  18.     } catch (RemoteException e) { 
  19.         LogUtil.e(TAG, "RemoteException occurs"); 
  20.     } 
  21.     // 设备A视频按照迁回的视频进度继续播放 
  22.     implPlayer.reload(url, progress); 
  23. });

说明

以上代码仅demo演示参考使用,产品化的代码需要使用国际化。

5. 恭喜你

● 通过使用PageSlider、PageSliderIndicator结合ListContainer编写定时滚动及可滑动的页面。

● HarmonyOS通过DeviceManger获取分布式网络中设备列表,选中设备ID之后,再通过IDL跨进程通信方式将FA或PA携带数据跨设备拉起。

● 整体运行效果图如下:

设备A视频跨设备协同后效果图如下:

至此,您已经完成HarmonyOS上视频跨设备协同的体验!

6. 参考

gitee源码

github源码

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

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

https://harmonyos.

网页名称:HarmonyOS上视频跨设备协同技术超全详解
分享网址:http://www.shufengxianlan.com/qtweb/news13/245513.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联