DistributedVideoPlayer 分布式视频播放器(二)

想了解更多内容,分布放器请访问:

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

https://harmonyos.51cto.com

介绍

上一期我们实现了视频的播放功能,播放列表还有评论功能.这一期,我们来看一下手机端是如何实现一个对远端TV视频播放的遥控功能.

[本文正在参与优质创作者激励]

效果展示

搭建环境

安装DevEco Studio,详情请参考DevEco Studio下载。频播

设置DevEco Studio开发环境,分布放器DevEco Studio开发环境需要依赖于网络环境,式视需要连接上网络才能确保工具的频播正常使用,可以根据如下两种情况来配置开发环境:

如果可以直接访问Internet,分布放器只需进行下载HarmonyOS SDK操作。式视

如果网络不能直接访问Internet,频播需要通过代理服务器才可以访问,分布放器请参考配置开发环境。式视

下载源码后,频播使用DevEco 打开项目。分布放器

代码结构

手机端

Java后台

│  config.json │ ├─java │  └─com │      └─buty │          └─distributedvideoplayer │              │  MainAbility.java                │              │  MyApplication.java │              │ │              ├─ability │              │      DevicesSelectAbility.java       #可流转的式视设备列表 │              │      MainAbilitySlice.java           #视频播放列表页 │              │      SyncControlServiceAbility.java  #同步控制服务,TV-->Phone │              │      VideoPlayAbility.java           #视频播放Ability │              │      VideoPlayAbilitySlice.java      #视频播放详情和评论页 │              │ │              ├─components │              │      EpisodesSelectionDialog.java     │              │      RemoteController.java           #远端控制器 │              │      VideoPlayerPlaybackButton.java  #播放按钮组件 │              │      VideoPlayerSlider.java          #播放时间进度条 │              │ │              ├─constant │              │      Constants.java                  #常量 │              │      ResolutionEnum.java             #分辨率枚举 │              │      RouteRegister.java              #自定义路由 │              │ │              ├─data │              │      VideoInfo.java                  #视频基础信息 │              │      VideoInfoService.java           #视频信息服务,频播用于模拟数据 │              │      Videos.java                     #视频列表 │              │  │              ├─model │              │      CommentModel.java               #评论模型 │              │      DeviceModel.java                #设备模型 │              │      ResolutionModel.java            #解析度模型 │              │      VideoModel.java                 #视频模型 │              │ │              ├─provider │              │      CommentItemProvider.java        #评论数据提供程序 │              │      DeviceItemProvider.java         #设备列表提供程序 │              │      ResolutionItemProvider.java     #解析度数据提供程序 │              │      VideoItemProvider.java          #视频数据提供程序 │              │ │              └─utils │                      AppUtil.java                   #工具类 │                      DateUtils.java 

页面布局

│  │    │  ├─layout    │  │      ability_main.xml                                #播放列表布局    │  │      comments_item.xml                               #单条评论布局    │  │      dialog_playlist.xml                         │  │      dialog_resolution_list.xml    │  │      dialog_table_layout.xml    │  │      hm_sample_ability_video_box.xml                 #视频播放组件页    │  │      hm_sample_ability_video_comments.xml            #播放详情布局页    │  │      hm_sample_view_video_box_seek_bar_style1.xml    #播放进度条布局    │  │      hm_sample_view_video_box_seek_bar_style2.xml    │  │      remote_ability_control.xml                      #远程控制器布局    │  │      remote_ability_episodes.xml                 │  │      remote_ability_select_devices.xml               #可流转设备列表布局    │  │      remote_ability_sound_equipment.xml              │  │      remote_device_item.xml                          #设备子项显示布局    │  │      remote_episodes_item.xml    │  │      remote_video_quality_item.xml 

TV端

Java后台

├─main  │  │  config.json  │  │  │  ├─java  │  │  └─com  │  │      └─buty  │  │          └─distributedvideoplayer  │  │              │  MainAbility.java  │  │              │  MyApplication.java  │  │              │  VideoControlServiceAbility.java      #视频控制服务  Phone--->TV  │  │              │  │  │              ├─component  │  │              │      VideoSetting.java                  │  │              │  │  │              ├─constant                              #一些常量和枚举值  │  │              │      Constants.java                     │  │              │      ResolutionEnum.java  │  │              │      SettingOptionEnum.java  │  │              │      SpeedEnum.java  │  │              │  │  │              ├─data  │  │              │      VideoInfo.java                  #视频基本信息  │  │              │      VideoInfoService.java           #视频数据服务,读取json中的数据  │  │              │      Videos.java                     #视频对象  │  │              │  │  │              ├─model                                #一些数据模型  │  │              │      ResolutionModel.java  │  │              │      SettingComponentTag.java  │  │              │      SettingModel.java  │  │              │      VideoModel.java  │  │              │  │  │              ├─provider  │  │              │      SettingProvider.java  │  │              │      VideoEpisodesSelectProvider.java  │  │              │      VideoSettingProvider.java  │  │              │  │  │              ├─slice  │  │              │      MainAbilitySlice.java             │  │              │      VideoPlayAbilitySlice.java      #视频播放能力页  │  │              │   │  │              ├─utils  │  │              │      AppUtil.java  │  │              │  │  │              └─view  │  │                      VideoPlayerPlaybackButton.java  │  │                      VideoPlayerSlider.java 

页面布局

│      │  │    │      │  ├─layout    │      │  │      ability_main.xml    │      │  │      ability_video_box.xml                  #播放器布局页面    │      │  │      video_common_item.xml    │      │  │      video_episodes_item.xml                    │      │  │      video_setting.xml    │      │  │      video_setting_item.xml    │      │  │      view_video_box_seek_bar_style1.xml    #播放器进度条布局 

实现步骤

1.手机端

1.1.页面布局,控制器布局页 remote_ability_control.xml

使用了DependentLayout,DirectionalLayout,TableLayout 布局组件 和 其他常用的组件.

<?xml version="1.0" encoding="utf-8"?> <DependentLayout     xmlns:ohos="http://schemas.huawei.com/res/ohos"     ohos:height="match_parent"     ohos:width="match_parent"     ohos:clickable="true">     <DirectionalLayout         ohos:height="match_parent"         ohos:width="match_parent"         ohos:background_element="$graphic:background_ability_control_bg"         ohos:orientation="vertical">         <StackLayout             ohos:id="$+id:control_app_bar"             ohos:height="match_content"             ohos:width="match_parent">             <DirectionalLayout                 ohos:id="$+id:control_app_bar_left"                 ohos:height="56vp"                 ohos:width="match_content"                 ohos:layout_alignment="vertical_center"                 ohos:orientation="horizontal">                 <Image                     ohos:id="$+id:app_bar_back"                     ohos:height="$float:default_image_size"                     ohos:width="$float:default_image_size"                     ohos:foreground_element="$media:ic_back"                     ohos:layout_alignment="center"                     ohos:start_margin="$float:default_margin">                 </Image>                 <Text                     ohos:id="$+id:app_bar_device_name"                     ohos:height="match_parent"                     ohos:width="match_content"                     ohos:start_margin="12vp"                     ohos:text=""                     ohos:text_color="$color:default_white_color"                     ohos:text_size="$float:normal_text_size_20"                     ohos:truncation_mode="ellipsis_at_end"/>             </DirectionalLayout>         </StackLayout>         <DirectionalLayout             ohos:height="match_content"             ohos:width="match_parent"             ohos:layout_alignment="vertical_center"             ohos:orientation="horizontal">             <Image                 ohos:height="16vp"                 ohos:width="16vp"                 ohos:foreground_element="$media:ic_play"                 ohos:layout_alignment="center"                 ohos:start_margin="$float:default_margin">             </Image>             <Text                 ohos:id="$+id:device_video_desc"                 ohos:height="match_content"                 ohos:width="match_parent"                 ohos:auto_scrolling_count="unlimited"                 ohos:end_margin="$float:default_margin"                 ohos:start_margin="16vp"                 ohos:text=""                 ohos:text_color="$color:default_white_color"                 ohos:text_size="$float:little_text_size_12"                 ohos:truncation_mode="auto_scrolling"/>         </DirectionalLayout>         <DirectionalLayout             ohos:id="$+id:control_middle_panel"             ohos:height="225vp"             ohos:width="225vp"             ohos:background_element="$graphic:background_ability_control_middle"             ohos:layout_alignment="center"             ohos:orientation="vertical"             ohos:top_margin="64vp">             <DirectionalLayout                 ohos:id="$+id:control_middle_panel_top"                 ohos:height="75vp"                 ohos:width="match_parent">                 <Image                     ohos:id="$+id:control_voice_up"                     ohos:height="$float:default_image_size"                     ohos:width="$float:default_image_size"                     ohos:background_element="$graphic:background_button_click"                     ohos:foreground_element="$media:ic_voice"                     ohos:layout_alignment="center"                     ohos:top_margin="28vp"/>             </DirectionalLayout>             <DirectionalLayout                 ohos:id="$+id:control_middle_panel_center"                 ohos:height="75vp"                 ohos:width="match_parent"                 ohos:orientation="horizontal">                 <DirectionalLayout                     ohos:id="$+id:control_backword_parent"                     ohos:height="match_parent"                     ohos:width="75vp"                     ohos:alignment="vertical_center">                     <Image                         ohos:id="$+id:control_backword"                         ohos:height="$float:default_image_size"                         ohos:width="$float:default_image_size"                         ohos:background_element="$graphic:background_button_click"                         ohos:foreground_element="$media:ic_anthology"                         ohos:layout_alignment="center"/>                 </DirectionalLayout>                 <DirectionalLayout                     ohos:id="$+id:control_play_parent"                     ohos:height="match_parent"                     ohos:width="75vp"                     ohos:alignment="center">                     <Image                         ohos:id="$+id:control_play"                         ohos:height="45vp"                         ohos:width="45vp"                         ohos:background_element="$graphic:background_ability_control_ok"                         ohos:image_src="$media:ic_pause_black"                         ohos:layout_alignment="center"/>                 </DirectionalLayout>                 <DirectionalLayout                     ohos:id="$+id:control_forward_parent"                     ohos:height="match_parent"                     ohos:width="75vp"                     ohos:alignment="vertical_center">                     <Image                         ohos:id="$+id:control_forward"                         ohos:height="$float:default_image_size"                         ohos:width="$float:default_image_size"                         ohos:background_element="$graphic:background_button_click"                         ohos:foreground_element="$media:ic_anthology"                         ohos:layout_alignment="center"                         ohos:rotate="180"/>                 </DirectionalLayout>             </DirectionalLayout>             <DirectionalLayout                 ohos:id="$+id:control_middle_panel_bottom"                 ohos:height="75vp"                 ohos:width="match_parent">                 <Image                     ohos:id="$+id:control_voice_down"                     ohos:height="$float:default_image_size"                     ohos:width="$float:default_image_size"                     ohos:background_element="$graphic:background_button_click"                     ohos:foreground_element="$media:ic_voice"                     ohos:layout_alignment="center"                     ohos:top_margin="23vp"/>             </DirectionalLayout>         </DirectionalLayout>         <DirectionalLayout             ohos:height="0vp"             ohos:width="match_parent"             ohos:alignment="vertical_center"             ohos:orientation="horizontal"             ohos:weight="2">             <Text                 ohos:id="$+id:control_current_time"                 ohos:height="match_content"                 ohos:width="match_content"                 ohos:end_margin="4vp"                 ohos:start_margin="$float:default_margin"                 ohos:text=""                 ohos:text_color="$color:default_white_color"                 ohos:text_size="12vp"/>             <Slider                 ohos:id="$+id:control_progress"                 ohos:height="10vp"                 ohos:width="0vp"                 ohos:orientation="horizontal"                 ohos:progress_color="#FF6103"                 ohos:thumb_element="$graphic:background_slide_thumb"                 ohos:weight="5"/>             <Text                 ohos:id="$+id:control_end_time"                 ohos:height="match_content"                 ohos:width="match_content"                 ohos:end_margin="$float:default_margin"                 ohos:text=""                 ohos:text_color="$color:default_white_color"                 ohos:text_size="12vp"/>         </DirectionalLayout>         <DirectionalLayout             ohos:height="match_content"             ohos:width="match_parent"             ohos:bottom_margin="48vp"             ohos:start_margin="16vp"             ohos:top_margin="48vp">             <StackLayout                 ohos:height="26vp"                 ohos:width="match_parent">                 <DirectionalLayout                     ohos:height="match_parent"                     ohos:width="match_parent">                     <Text                         ohos:height="match_parent"                         ohos:width="match_parent"                         ohos:text="$string:control_episodes"                         ohos:text_alignment="vertical_center"                         ohos:text_color="#000000"                         ohos:text_size="18fp"/>                 </DirectionalLayout>                 <DirectionalLayout                     ohos:height="match_parent"                     ohos:width="match_parent"                     ohos:alignment="right"                     ohos:orientation="horizontal">                     <Text                         ohos:id="$+id:control_episodes_num"                         ohos:height="match_parent"                         ohos:width="match_content"                         ohos:background_element="$graphic:background_button_click"                         ohos:text=""                         ohos:text_color="$color:default_black_color"                         ohos:text_size="14fp"/>                     <Image                         ohos:id="$+id:control_all_episodes"                         ohos:height="$float:default_image_size"                         ohos:width="$float:default_image_size"                         ohos:background_element="$graphic:background_button_click"                         ohos:end_margin="8vp"                         ohos:foreground_element="$media:ic_right_arrow"                         ohos:layout_alignment="center"/>                 </DirectionalLayout>             </StackLayout>             <TableLayout                 ohos:id="$+id:cotrol_bottom_item"                 ohos:height="match_content"                 ohos:width="match_parent"                 ohos:below="$id:episodes_header"                 ohos:column_count="6"                 ohos:top_margin="12vp">             </TableLayout>         </DirectionalLayout>     </DirectionalLayout> </DependentLayout> 

1.2.页面布局,选择设备组件布局页 remote_ability_select_devices.xml

使用了DependentLayout,DirectionalLayout布局组件 和 ListContainer 等组件.

<?xml version="1.0" encoding="utf-8"?> <DirectionalLayout     xmlns:ohos="http://schemas.huawei.com/res/ohos"     ohos:height="match_parent"     ohos:width="match_parent"     ohos:alignment="vertical_center"     ohos:background_element="$color:default_panel_background"     ohos:orientation="vertical">     <DependentLayout         ohos:height="100vp"         ohos:width="match_parent"         ohos:background_element="$graphic:background_ability_devices"         ohos:end_margin="12vp"         ohos:end_padding="$float:default_margin"         ohos:layout_alignment="vertical_center"         ohos:start_margin="12vp"         ohos:start_padding="$float:default_margin">         <Text             ohos:id="$+id:devices_title"             ohos:height="match_content"             ohos:width="match_parent"             ohos:text="$string:local_machine"             ohos:text_color="$color:default_black_color"             ohos:text_size="14fp"             ohos:top_margin="12vp"/>         <Image             ohos:id="$+id:devices_head_icon"             ohos:height="$float:default_image_size"             ohos:width="$float:default_image_size"             ohos:below="$id:devices_title"             ohos:foreground_element="$media:icon"             ohos:top_margin="20vp"/>         <DirectionalLayout             ohos:height="match_content"             ohos:width="match_parent"             ohos:below="$id:devices_title"             ohos:end_of="$id:devices_head_icon"             ohos:orientation="vertical"             ohos:start_padding="12vp"             ohos:top_margin="12vp">             <Text                 ohos:id="$+id:devices_head_app_name"                 ohos:height="match_content"                 ohos:width="match_parent"                 ohos:max_height="28vp"                 ohos:text=""                 ohos:text_color="$color:default_black_color"                 ohos:text_size="14fp"/>             <Text                 ohos:id="$+id:devices_head_video_name"                 ohos:height="18vp"                 ohos:width="match_parent"                 ohos:text=""                 ohos:text_color="#99000000"                 ohos:text_size="12fp"                 ohos:truncation_mode="ellipsis_at_end"/>         </DirectionalLayout>     </DependentLayout>     <DirectionalLayout         ohos:height="300vp"         ohos:width="match_parent"         ohos:background_element="$graphic:background_ability_devices"         ohos:end_margin="12vp"         ohos:orientation="vertical"         ohos:padding="$float:default_margin"         ohos:start_margin="12vp"         ohos:top_margin="12vp">         <Text             ohos:height="21vp"             ohos:width="match_parent"             ohos:bottom_margin="10vp"             ohos:text="$string:my_devices"             ohos:text_color="$color:default_black_color"             ohos:text_size="16vp"/>         <ListContainer             ohos:id="$+id:devices_container"             ohos:height="match_parent"             ohos:width="match_parent"             ohos:layout_alignment="horizontal_center"             ohos:orientation="vertical"/>     </DirectionalLayout> </DirectionalLayout> 

1.3.Java代码,远端控制器视图组件 RemoteController.java

RemoteController继承自DependentLayout布局组件,实现了Component.ClickedListener和Slider.ValueChangedListener,用于处理 点击事件 和 滑块滑动事件。

/**  * 控制器面板组件  * Remote Control Page  */ public class RemoteController extends DependentLayout         //实现了 组件的点击监听和滑块的值变化监听 的接口         implements Component.ClickedListener, Slider.ValueChangedListener {  ... 

控制器面板视图组件的组成,包括两大部分,

第一部分是云服务器:组件的初始化,包括:控制组件的初始化, 播放进度组件的初始化, 剧集组件的初始化

/**  * 初始化远端控制视图的各个组件  */ private void initView() {      //设置隐藏     setVisibility(INVISIBLE);     if (controllerView == null) {          controllerView =                 LayoutScatter.getInstance(slice).parse(ResourceTable.Layout_remote_ability_control, this, false);     }     //初始化文本     initItemText();     initItemSize();     initItemImage();     //进度滑块     initProgressSlider();     //初始化按钮     initButton(ResourceTable.Id_app_bar_back);     initButton(ResourceTable.Id_control_episodes_num);     initButton(ResourceTable.Id_control_all_episodes);     initButton(ResourceTable.Id_control_play);     initButton(ResourceTable.Id_control_backword);     initButton(ResourceTable.Id_control_forward);     initButton(ResourceTable.Id_control_voice_down);     initButton(ResourceTable.Id_control_voice_up);     //初始化底部的显示的视频剧集     initBottomComponent();     //将组件追加到队列末尾     addComponent(controllerView);     //初始化剧集对话框     initEpisodesDialog();     isPlaying = true; } 

第二部分是:自定义了控制监听器(RemoteControllerListener )和接口,结合点击事和滑块滑动事件将自己的操作传递给手机视频播放器类(VideoPlayAbilitySlice)。

/**  * 控制器面板操作监听  * 播放/快退/快进/音量加减/停止连接/切换视频/切换解析度  * RemoteControllerListener  */ public interface RemoteControllerListener {       //发送控制码给该接口的实现     void sendControl(int code, String extra); } /**  *   * 设置控制器监听器  * setRemoteControllerCallback  *  * @param listener listener  */ public void setRemoteControllerCallback(RemoteControllerListener listener) {      remoteControllerListener = listener; } /**  * 点击事件进行统一处理,通过sendControl发送出去  */ @Override public void onClick(Component component) {      switch (component.getId()) {          //返回组件         case ResourceTable.Id_app_bar_back:             hide(true);             break;         case ResourceTable.Id_control_episodes_num:             //剧集组件,显示剧集对话框         case ResourceTable.Id_control_all_episodes:             episodesDialog.setVisibility(VISIBLE);             break;             //播放组件,发送播放的控制指令         case ResourceTable.Id_control_play:             remoteControllerListener.sendControl(ControlCode.PLAY.getCode(), "");             break;             //快退组件,发送快退指令         case ResourceTable.Id_control_backword:             remoteControllerListener.sendControl(ControlCode.BACKWARD.getCode(), "");             break;             //快进组件,发送快进指令         case ResourceTable.Id_control_forward:             remoteControllerListener.sendControl(ControlCode.FORWARD.getCode(), "");             break;             //增加音量,发送给增加音量指令         case ResourceTable.Id_control_voice_up:             remoteControllerListener.sendControl(ControlCode.VOLUME_ADD.getCode(), "");             break;             //降低音量,发送降低音量指令         case ResourceTable.Id_control_voice_down:             //关闭显示的对话框             if (getDialogVisibility()) {                  remoteControllerListener.sendControl(ControlCode.VOLUME_REDUCED.getCode(), "");             }             break;         default:             break;     } } /**  * 时间进度条值变化时,设置当前的播放时间  * @param slider  * @param value  * @param fromUser  */ @Override public void onProgressUpdated(Slider slider, int value, boolean fromUser) {      HiLog.debug(LABEL,"onProgressUpdated");     slice.getUITaskDispatcher()             .delayDispatch(                     () -> {                          //当前播放的时间进度                         Text currentTime =                                 (Text) controllerView.findComponentById(ResourceTable.Id_control_current_time);                         //设置显示的时间                         currentTime.setText(                                 DateUtils.msToString(totalTime * value / Constants.ONE_HUNDRED_PERCENT));                     },                     0); } @Override public void onTouchStart(Slider slider) {      isSliderTouching = true; } /**  * 进度条滑块拖拽结束触发,sendControl发送出去  * @param slider  */ @Override public void onTouchEnd(Slider slider) {      // The pop-up box cannot block the slider touch event.     // This event is not processed when a dialog box is displayed.     //滑动结束,发送seek指令到远端     if (getDialogVisibility()) {          //         remoteControllerListener.sendControl(ControlCode.SEEK.getCode(), String.valueOf(slider.getProgress()));     }     isSliderTouching = false; } 

1.4.Java代码,流转设备列表页面 DevicesSelectAbility.java

主要是提供设备选择列表以及选择设备后返回设备信息

/**  * 可供选择的远端设备能力  * Remote Device Selection Ability  */ public class DevicesSelectAbility extends Ability {      @Override     public void onStart(Intent intent) {          //请求数据流转权限         requestPermissionsFromUser(new String[]{ "ohos.permission.DISTRIBUTED_DATASYNC"}, 0);         super.onStart(intent);         super.setUIContent(ResourceTable.Layout_remote_ability_select_devices);         this.initPage(intent);     }     /**      * 初始化页面组件      *      * @param intent      */     private void initPage(Intent intent) {          //从json中获取视频数据         VideoInfoService videoService = new VideoInfoService(this);         //设置app名称         Text appName = (Text) findComponentById(ResourceTable.Id_devices_head_app_name);         appName.setText(ResourceTable.String_entry_MainAbility);         //视频名称组件         Text videoName = (Text) findComponentById(ResourceTable.Id_devices_head_video_name);         //当前播放视频的索引         int currentPlayingIndex = intent.getIntParam(Constants.PARAM_VIDEO_INDEX, 0) + 1;         //当前播放视频的剧集         String playingEpisodes =                 AppUtil.getStringResource(this, ResourceTable.String_control_playing_episodes)                         .replaceAll("\\?", String.valueOf(currentPlayingIndex));         //设置播放视频名称和剧集         videoName.setText(videoService.getAllVideoInfo().getVideoName() + " " + playingEpisodes);         //在线设备列表,以及设置点击的监听事件、传递数据         ListContainer listContainer = (ListContainer) findComponentById(ResourceTable.Id_devices_container);         List<DeviceModel> devices = AppUtil.getDevicesInfo();         //容器绑定数据提供程序         DeviceItemProvider provider = new DeviceItemProvider(this, devices);         listContainer.setItemProvider(provider);         //设置点击监听处理         listContainer.setItemClickedListener(                 (container, component, position, id) -> {                      //获取点击的item                     DeviceModel item = (DeviceModel) listContainer.getItemProvider().getItem(position);                     //返回数据意图                     Intent intentResult = new Intent();                     //设置要返回的参数                     intentResult.setParam(Constants.PARAM_DEVICE_TYPE, item.getDeviceType());                     intentResult.setParam(Constants.PARAM_DEVICE_ID, item.getDeviceId());                     intentResult.setParam(Constants.PARAM_DEVICE_NAME, item.getDeviceName());                     //设置返回结果                     setResult(0, intentResult);                     //关闭当前Ability                     this.terminateAbility();                 });     } } 

可用设备列表提供程序 DeviceItemProvider.java

/**  * 设备列表提供程序  * Device information list processing class  */ public class DeviceItemProvider extends BaseItemProvider {      private final Context context;     private final List<DeviceModel> list;     /**      * Initialization      */     public DeviceItemProvider(Context context, List<DeviceModel> list) {          this.context = context;         this.list = list;     }     @Override     public int getCount() {          return list == null ? 0 : list.size();     }     @Override     public Object getItem(int position) {          if (list != null && position >= 0 && position < list.size()) {              return list.get(position);         }         return new DeviceModel();     }     @Override     public long getItemId(int position) {          return position;     }     @Override     public Component getComponent(int position, Component convertComponent, ComponentContainer componentContainer) {          final Component cpt;         if (convertComponent == null) {              cpt = LayoutScatter.getInstance(context).parse(ResourceTable.Layout_remote_device_item, null, false);         } else {              cpt = convertComponent;         }         DeviceModel deviceItem = list.get(position);         //设备名称         Text deviceName = (Text) cpt.findComponentById(ResourceTable.Id_device_item_name);         deviceName.setText(deviceItem.getDeviceName());         //设备图标         Image deviceIcon = (Image) cpt.findComponentById(ResourceTable.Id_device_item_icon);         AppUtil.setDeviceIcon(deviceItem.getDeviceType(), deviceIcon);         if (position == list.size() - 1) {              Component divider = cpt.findComponentById(ResourceTable.Id_device_item_divider);             divider.setVisibility(Component.INVISIBLE);         }         return cpt;     } } 

1.5.Java代码,视频播放器页面 VideoPlayAbilitySlice.java

视频播放器页面 远端控制操作的代码主要包括两部分,服务器托管

第一部分是:点击“流转” 按钮时,打开可用设备列表,点击要流转的设备后,在onAbilityResult方法中,打开远端TV设备的播放器能力页(MainAbility) 并 连接上控制元服务(VideoControlServiceAbility)

/**  * 打开设备选择Ability后,选择流转的设备setResult后触发  * @param requestCode  * @param resultCode  * @param resultIntent  */ @Override protected void onAbilityResult(int requestCode, int resultCode, Intent resultIntent) {      HiLog.debug(LABEL, "onAbilityResult");     //     if (requestCode == Constants.PRESENT_SELECT_DEVICES_REQUEST_CODE && resultIntent != null) {          //         startRemoteAbilityPa(resultIntent);         return;     }     //     setDisplayOrientation(AbilityInfo.DisplayOrientation.values()[sourceDisplayOrientation + 1]);     if (isVideoPlaying) {          player.start();     } } /**  * 开启远端Ability  *  * @param resultIntent  */ private void startRemoteAbilityPa(Intent resultIntent) {      //远端TV设备ID     String devicesId = resultIntent.getStringParam(Constants.PARAM_DEVICE_ID);     Intent intent = new Intent();     Operation operation =             new Intent.OperationBuilder()                     .withDeviceId(devicesId)                     .withBundleName(getBundleName())                     .withAbilityName("com.buty.distributedvideoplayer.MainAbility")                     .withAction("action.video.play")                     .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)                     .build();     //本地存储设备ID     String localDeviceId =             KvManagerFactory.getInstance().createKvManager(new KvManagerConfig(this)).getLocalDeviceInfo().getId();     HiLog.debug(LABEL, "remoteDevicesId:" + devicesId + ",localDeviceId:" + localDeviceId);     //播放的视频路径     String path =             videoService                     .getVideoInfoByIndex(currentPlayingIndex)                     .getResolutions()                     .get(currentPlayingResolutionIndex)                     .getUrl();     //本地ph()one设备ID     intent.setParam(RemoteConstant.INTENT_PARAM_REMOTE_DEVICE_ID, localDeviceId);     //播放视频的URL     intent.setParam(RemoteConstant.INTENT_PARAM_REMOTE_VIDEO_PATH, path);     //播放不同分辨率视频的索引     intent.setParam(RemoteConstant.INTENT_PARAM_REMOTE_VIDEO_INDEX, currentPlayingIndex);     //播放进度位置     intent.setParam(RemoteConstant.INTENT_PARAM_REMOTE_START_POSITION, (int) player.getSeekWhenPrepared());     intent.setOperation(operation);     //启动远端的播放Ability     startAbility(intent);     //远端视频控制元服务     Intent remotePaIntent = new Intent();     Operation paOperation =             new Intent.OperationBuilder()                     .withDeviceId(devicesId)                     .withBundleName(getBundleName())                     .withAbilityName("com.buty.distributedvideoplayer.VideoControlServiceAbility")                     .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)                     .build();     remotePaIntent.setOperation(paOperation);     //连接远端视频控制服务,使用2台P40的超级终端模拟器连接不成功     //Context::connectRemoteAbility failed, errorCode is 1319     boolean connectFlag = connectAbility(remotePaIntent, connection);     if (connectFlag) {          HiLog.debug(LABEL, "start remote ability PA success");         //设置显示方向为竖屏         setDisplayOrientation(AbilityInfo.DisplayOrientation.PORTRAIT);         //初始化远端控制         initRemoteController();         //设置播放进度、状态、等         remoteController.setVideoInfo(                 resultIntent.getStringParam(Constants.PARAM_DEVICE_NAME),                 currentPlayingIndex,                 (int) player.getCurrentPosition(),                 (int) player.getDuration());         remoteController.show();     } else {          HiLog.error(LABEL, "start remote ability PA failed");         stopAbility(intent);     } } 

第二部分是:成功连接到远端视频控制元服务后,初始化远端控制器(RemoteController)并实现控制器面板的监听器接口(sendControl),通过mProxy发送控制指令到TV端(sendDataToRemote)

/**  * 初始化控制器 及 监听  */ private void initRemoteController() {      if (remoteController == null) {          remoteController = new RemoteController(this);         //手机端控制面板操作的监听回调         remoteController.setRemoteControllerCallback(                 (code, extra) -> {                      if (mProxy == null) {                          return;                     }                     //发送控制指令到TV端                     boolean result =                             mProxy.sendDataToRemote(RemoteConstant.REQUEST_CONTROL_REMOTE_DEVICE, code, extra);                     if (!result) {                          new ToastDialog(getContext())                                 .setText(                                         AppUtil.getStringResource(                                                 getContext(), ResourceTable.String_send_failed_tips))                                 .show();                         remoteController.hide(false);                     }                 });         StackLayout rootLayout = (StackLayout) findComponentById(ResourceTable.Id_root_layout);         rootLayout.addComponent(remoteController);     } } 

第三部分是:订阅手机端控制事件(Constants.PHONE_CONTROL_EVENT)用于处理同步控制服务(SyncControlServiceAbility)发过来的事件,目的是把TV端的状态同步给手机控制端

/**  * 订阅事件,用于 "TV端->手机端" 方向的播放状态的同步  */ private void subscribe() {      HiLog.debug(LABEL, "subscribe");     MatchingSkills matchingSkills = new MatchingSkills();     //手机端控制面板的 控制事件     matchingSkills.addEvent(Constants.PHONE_CONTROL_EVENT);     matchingSkills.addEvent(CommonEventSupport.COMMON_EVENT_SCREEN_ON);     CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(matchingSkills);     //事件订阅器 TODO     subscriber = new MyCommonEventSubscriber(subscribeInfo);     try {          CommonEventManager.subscribeCommonEvent(subscriber);     } catch (RemoteException e) {          HiLog.error(LABEL, "subscribeCommonEvent occur exception.");     } } /**  * 取消订阅  */ private void unSubscribe() {      HiLog.debug(LABEL, "unSubscribe");     try {          CommonEventManager.unsubscribeCommonEvent(subscriber);     } catch (RemoteException e) {          HiLog.error(LABEL, "unsubscribecommonevent occur exception.");     } } /**  * 事件订阅器,用于 "TV端->手机端" 方向的播放状态的同步  */ class MyCommonEventSubscriber extends CommonEventSubscriber {      MyCommonEventSubscriber(CommonEventSubscribeInfo info) {          super(info);     }     @Override     public void onReceiveEvent(CommonEventData commonEventData) {          Intent intent = commonEventData.getIntent();         //获取事件参数,控制指令码         int controlCode = intent.getIntParam(Constants.KEY_CONTROL_CODE, 0);         HiLog.debug(LABEL,"onReceiveEvent: controlCode"+controlCode);         //未进行远端控制         if (remoteController == null || !remoteController.isShown()) {              HiLog.debug(LABEL, "remote controller is hidden now");             return;         }         //如果是源码下载视频播放进度指令         if (controlCode == ControlCode.SYNC_VIDEO_PROCESS.getCode()) {              int totalTime = Integer.parseInt(intent.getStringParam(Constants.KEY_CONTROL_VIDEO_TIME));             int progress = Integer.parseInt(intent.getStringParam(Constants.KEY_CONTROL_VIDEO_PROGRESS));             //更新的控制面板的进度条             remoteController.syncVideoPlayProcess(totalTime, progress);         //更新控制面板的视频播放状态         } else if (controlCode == ControlCode.SYNC_VIDEO_STATUS.getCode()) {              boolean isPlaying =                     Boolean.parseBoolean(intent.getStringParam(Constants.KEY_CONTROL_VIDEO_PLAYBACK_STATUS));             if (remoteController.getPlayingStatus() != isPlaying) {                  remoteController.changePlayingStatus();             }         //更新控制面板的音量         } else {              int currentVolume = Integer.parseInt(intent.getStringParam(Constants.KEY_CONTROL_VIDEO_VOLUME));             remoteController.changeVolumeIcon(currentVolume);         }     } } 

1.6.Java代码,远端视频控制同步服务 SyncControlServiceAbility.java

这个服务是给TV端连接使用的,对端连接过来,将 播放状态、播放进度、音量值同步过来

/**  * 同步控制元服务  * Video Control Synchronization Service  */ public class SyncControlServiceAbility extends Ability {      private static final HiLogLabel LABEL = new HiLogLabel(0, 0, "=>SyncControlServiceAbility");     //远端设备代理     private final MyRemote remote = new MyRemote(RemoteConstant.REQUEST_SYNC_VIDEO_STATUS);     @Override     public void onStart(Intent intent) {          super.onStart(intent);         //         remote.setRemoteRequestCallback(                 this::sendEvent);     }     @Override     public void onBackground() {          super.onBackground();     }     @Override     public void onStop() {          super.onStop();     }     @Override     protected IRemoteObject onConnect(Intent intent) {          super.onConnect(intent);         return remote.asObject();     }     /**      * 发送播放器事件      * @param controlCode      * @param value      */     private void sendEvent(int controlCode, Map<?, ?> value) {          HiLog.debug(LABEL,"sendEvent,controlCode:"+controlCode+",value:"+value.toString());         try {              Intent intent = new Intent();             Operation operation = new Intent.OperationBuilder().withAction(Constants.PHONE_CONTROL_EVENT).build();             intent.setOperation(operation);             intent.setParam(Constants.KEY_CONTROL_CODE, controlCode);             //播放进度             if (controlCode == ControlCode.SYNC_VIDEO_PROCESS.getCode()) {                  intent.setParam(Constants.KEY_CONTROL_VIDEO_TIME,                         String.valueOf(value.get(RemoteConstant.REMOTE_KEY_VIDEO_TOTAL_TIME)));                 intent.setParam(Constants.KEY_CONTROL_VIDEO_PROGRESS,                         String.valueOf(value.get(RemoteConstant.REMOTE_KEY_VIDEO_CURRENT_PROGRESS)));             //播放状态             } else if (controlCode == ControlCode.SYNC_VIDEO_STATUS.getCode()) {                  intent.setParam(Constants.KEY_CONTROL_VIDEO_PLAYBACK_STATUS,                         String.valueOf(value.get(RemoteConstant.REMOTE_KEY_VIDEO_CURRENT_PLAYBACK_STATUS)));             //播放音量             } else {                  intent.setParam(Constants.KEY_CONTROL_VIDEO_VOLUME,                         String.valueOf(value.get(RemoteConstant.REMOTE_KEY_VIDEO_CURRENT_VOLUME)));             }             CommonEventData eventData = new CommonEventData(intent);             //发布事件             CommonEventManager.publishCommonEvent(eventData);         } catch (RemoteException e) {              HiLog.error(LABEL, "publishCommonEvent occur exception.");         }     } } 

2.TV端

2.1.页面布局,视频播放器布局组件 ability_video_box.xml

播放器组件VideoPlayerView

<?xml version="1.0" encoding="utf-8"?> <StackLayout     xmlns:ohos="http://schemas.huawei.com/res/ohos"     ohos:id="$+id:root_layout"     ohos:height="match_parent"     ohos:width="match_parent"     ohos:background_element="#FFFFFFFF"     ohos:orientation="vertical">      <com.buty.distributedvideoplayer.player.ui.widget.media.VideoPlayerView         ohos:id="$+id:video_view"         ohos:height="match_parent"         ohos:width="match_parent"/>  </StackLayot> 

2.2.页面布局,视频播放器的进度条布局组件 view_video_box_seek_bar_style1.xml

<?xml version="1.0" encoding="utf-8"?> <!--Time is above the progress bar--> <DependentLayout     xmlns:ohos="http://schemas.huawei.com/res/ohos"     ohos:height="54vp"     ohos:width="match_parent"     ohos:orientation="horizontal">      <Text         ohos:id="$+id:current_time"         ohos:height="match_content"         ohos:width="match_content"         ohos:above="$id:seek_bar"         ohos:align_parent_start="true"         ohos:start_margin="12vp"         ohos:text_color="white"         ohos:text_size="10fp"/>      <Slider         ohos:id="$+id:seek_bar"         ohos:height="match_content"         ohos:width="match_parent"         ohos:background_instruct_element="$color:seek_bar_background_instruct_color"         ohos:center_in_parent="true"         ohos:progress_element="$color:seek_bar_progress_color"         ohos:thumb_element="$graphic:hm_sample_slider_thumb"         ohos:vice_progress_element="$color:seek_bar_vice_progress_color"         />      <Text         ohos:id="$+id:end_time"         ohos:height="match_content"         ohos:width="match_content"         ohos:above="$id:seek_bar"         ohos:align_parent_end="true"         ohos:end_margin="12vp"         ohos:text_color="white"         ohos:text_size="10fp"/> </DependentLayout> 

2.3.Java代码, 视频控制元服务 VideoControlServiceAbility.java

分为两部分,

第一部分是:手机端连接过来后,asObject。

/**  * 远程设备的代理,来源commonlib  */ private final MyRemote remote = new MyRemote(RemoteConstant.REQUEST_CONTROL_REMOTE_DEVICE);  @Override protected IRemoteObject onConnect(Intent intent) {      HiLog.debug(LABEL, "onConnect");     super.onConnect(intent);     //返回代理对象     return remote.asObject(); } 

 第二部分是:发送事件通知到订阅方(VideoPlayAbilitySlice)

/**  * 发送事件通知 VideoPlayAbilitySlice  * @param controlCode 控制码  * @param value  */ private void sendEvent(int controlCode, Map<?, ?> value) {      HiLog.debug(LABEL, "sendEvent:"+controlCode+","+value.toString());     try {          //意图         Intent intent = new Intent();         //TV控制事件操作         Operation operation = new Intent.OperationBuilder()                 .withAction(Constants.TV_CONTROL_EVENT)                 .build();         intent.setOperation(operation);         //设置控制参数         intent.setParam(Constants.KEY_CONTROL_CODE, controlCode);         intent.setParam(Constants.KEY_CONTROL_VALUE, (String) value.get(RemoteConstant.REMOTE_KEY_CONTROL_VALUE));         //封装时间数据         CommonEventData eventData = new CommonEventData(intent);         //通用事件管理器,发布事件         CommonEventManager.publishCommonEvent(eventData);      } catch (RemoteException e) {          HiLog.error(LABEL, "publishCommonEvent occur exception.");     } } 

2.4.Java代码, 视频播放器能力页 VideoPlayAbilitySlice.java

第一部分是:连接手机端的同步控制元服务(SyncControlServiceAbility),建立连接后,初始化远端代理(MyRemoteProxy)。

//连接的phone设备 connectRemoteDevice(         //从意图中获取远端phone设备ID         intent.getStringParam(RemoteConstant.INTENT_PARAM_REMOTE_DEVICE_ID));  /**  * 连接远端phone设备的同步服务  * @param deviceId  */ private void connectRemoteDevice(String deviceId) {      HiLog.debug(LABEL,"connectRemoteDevice:"+deviceId);     Intent connectPaIntent = new Intent();     Operation operation =             new Intent.OperationBuilder()                     .withDeviceId(deviceId)                     .withBundleName(getBundleName())                     .withAbilityName(REMOTE_PHONE_ABILITY)                     .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)                     .build();     connectPaIntent.setOperation(operation);      connectAbility(connectPaIntent, connection); }  // Creating a Connection Callback Instance private final IAbilityConnection connection = new IAbilityConnection() {      // Callback for connecting to a service     @Override     public void onAbilityConnectDone(ElementName elementName, IRemoteObject iRemoteObject, int resultCode) {          myProxy = new MyRemoteProxy(iRemoteObject);     }      // Callback for disconnecting from the service     @Override     public void onAbilityDisconnectDone(ElementName elementName, int resultCode) {          disconnectAbility(this);     } }; 

 第二部分是:注册远端控制回调,实现视频播放器组件(VideoPlayerView)的RemoteControlCallback接口,使用远端代理对象(MyRemoteProxy)发送数据到手机端同步当前播放器信息

//注册远端控制回调 videoBox.registerRemoteControlCallback(remoteControlCallback);    /**  * 远端控制回调,来源commonlib,用于同步进度条进度/播放状态/音量  */ private VideoPlayerView.RemoteControlCallback remoteControlCallback =     new VideoPlayerView.RemoteControlCallback() {          @Override         //进度条变化         public void onProgressChanged(long totalTime, int progress) {              HiLog.debug(LABEL,"onProgressChanged,myProxy:"+myProxy);             if (myProxy != null) {                  Map<String, String> progressValue = new HashMap<>();                 //设置总时间和进度值                 progressValue.put(RemoteConstant.REMOTE_KEY_VIDEO_TOTAL_TIME, String.valueOf(totalTime));                 progressValue.put(RemoteConstant.REMOTE_KEY_VIDEO_CURRENT_PROGRESS, String.valueOf(progress));                  //同步进度之给手机端的控制面板                 myProxy.sendDataToRemote(                         RemoteConstant.REQUEST_SYNC_VIDEO_STATUS,                         ControlCode.SYNC_VIDEO_PROCESS.getCode(),                         progressValue);             }         }          @Override         //播放状态变化         public void onPlayingStatusChanged(boolean isPlaying) {              if (myProxy != null) {                  Map<String, String> videoStatusMap = new HashMap<>();                 videoStatusMap.put(                         RemoteConstant.REMOTE_KEY_VIDEO_CURRENT_PLAYBACK_STATUS, String.valueOf(isPlaying));                 HiLog.debug(LABEL, "isPlaying = " + String.valueOf(isPlaying));                 myProxy.sendDataToRemote(                         RemoteConstant.REQUEST_SYNC_VIDEO_STATUS,                         ControlCode.SYNC_VIDEO_STATUS.getCode(),                         videoStatusMap);             }         }          @Override         //音量变化         public void onVolumeChanged(int volume) {              if (myProxy != null) {                  Map<String, String> volumeMap = new HashMap<>();                 volumeMap.put(RemoteConstant.REMOTE_KEY_VIDEO_CURRENT_VOLUME, String.valueOf(volume));                 myProxy.sendDataToRemote(                         RemoteConstant.REQUEST_SYNC_VIDEO_STATUS,                         ControlCode.SYNC_VIDEO_VOLUME.getCode(),                         volumeMap);             }         } }; 

 第三部分是:订阅事件,处理视频控制服务(VideoControlServiceAbility)发送的播放器控制事件

/**  * 通用事件订阅  */ private void subscribe() {      HiLog.debug(LABEL,"subscribe");     MatchingSkills matchingSkills = new MatchingSkills();     matchingSkills.addEvent(Constants.TV_CONTROL_EVENT);     matchingSkills.addEvent(CommonEventSupport.COMMON_EVENT_SCREEN_ON);     CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(matchingSkills);     //订阅者     tvSubscriber = new MyCommonEventSubscriber(subscribeInfo);     try {          CommonEventManager.subscribeCommonEvent(tvSubscriber);     } catch (RemoteException e) {          HiLog.error(LABEL, "subscribeCommonEvent occur exception.");     } }  /**  * 取消订阅  */ private void unSubscribe() {      HiLog.debug(LABEL,"subscribe");     try {          CommonEventManager.unsubscribeCommonEvent(tvSubscriber);     } catch (RemoteException e) {          HiLog.error(LABEL, "unSubscribe Exception");     } }  /**  * 视频控制服务(VideoControlServiceAbility)事件订阅者  */ class MyCommonEventSubscriber extends CommonEventSubscriber {      MyCommonEventSubscriber(CommonEventSubscribeInfo info) {          super(info);     }      @Override     public void onReceiveEvent(CommonEventData commonEventData) {          HiLog.info(LABEL, "onReceiveEvent.....");          Intent intent = commonEventData.getIntent();         int controlCode = intent.getIntParam(Constants.KEY_CONTROL_CODE, 0);         String extras = intent.getStringParam(Constants.KEY_CONTROL_VALUE);          //播放or暂停         if (controlCode == ControlCode.PLAY.getCode()) {              if (videoBox.isPlaying()) {                  videoBox.pause();             } else if (!videoBox.isPlaying() && !needResumeStatus) {                  videoBox.start();             } else {                  HiLog.error(LABEL, "Ignoring the case with player status");             }             //拖动播放进度         } else if (controlCode == ControlCode.SEEK.getCode()) {              videoBox.seekTo(videoBox.getDuration() * Integer.parseInt(extras) / 100);             //快进         } else if (controlCode == ControlCode.FORWARD.getCode()) {              videoBox.seekTo(videoBox.getCurrentPosition() + Constants.REWIND_STEP);             //快退         } else if (controlCode == ControlCode.BACKWARD.getCode()) {              videoBox.seekTo(videoBox.getCurrentPosition() - Constants.REWIND_STEP);             //音量加         } else if (controlCode == ControlCode.VOLUME_ADD.getCode()) {              videoBox.setVolume(Constants.VOLUME_STEP);             //音量减         } else if (controlCode == ControlCode.VOLUME_REDUCED.getCode()) {              videoBox.setVolume(-Constants.VOLUME_STEP);             //切换播放速度         } else if (controlCode == ControlCode.SWITCH_SPEED.getCode()) {              videoBox.setPlaybackSpeed(Float.parseFloat(extras));             //切换视频源,例如高清         } else if (controlCode == ControlCode.SWITCH_RESOLUTION.getCode()) {              long currentPosition = videoBox.getCurrentPosition();             int resolutionIndex = Integer.parseInt(extras);             VideoInfo videoInfo = videoInfoService.getVideoInfoByIndex(currentPlayingIndex);             videoBox.pause();              //设置新的播放URL             videoBox.setVideoPath(videoInfo.getResolutions().get(resolutionIndex).getUrl());             //调整到原播放位置             videoBox.setPlayerOnPreparedListener(                     () -> {                          videoBox.seekTo(currentPosition);                         videoBox.start();                     });             //切换视频         } else if (controlCode == ControlCode.SWITCH_VIDEO.getCode()) {              videoBox.pause();             currentPlayingIndex = Integer.parseInt(extras);             VideoInfo videoInfo = videoInfoService.getVideoInfoByIndex(currentPlayingIndex);             videoBox.setVideoPathAndTitle(videoInfo.getResolutions().get(0).getUrl(), videoInfo.getVideoDesc());             videoBox.setPlayerOnPreparedListener(() -> videoBox.start());              //停止连接         } else if (controlCode == ControlCode.STOP_CONNECTION.getCode()) {              terminate();         } else {              HiLog.error(LABEL, "Ignoring the case with control code");         }     } } 

 至此,手机端控制端和TV端的过程就解读完了,部分细节如切换视频解析度、切换视频剧集、TV端设置 等不影响全局流程就不展开了。

两端涉及的权限如下:

{      "name": "ohos.permission.INTERNET",     "reason": "",     "usedScene": {        "ability": [         "VideoPlayAbilitySlice"       ],       "when": "inuse"     }   },   {      "name": "ohos.permission.DISTRIBUTED_DATASYNC",     "reason": "",     "usedScene": {        "ability": [         "VideoPlayAbilitySlice"       ],       "when": "inuse"     }   },   {      "name": "ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE",     "reason": "",     "usedScene": {        "ability": [         "VideoPlayAbilitySlice"       ],       "when": "inuse"     }   },   {      "name": "ohos.permission.GET_DISTRIBUTED_DEVICE_INFO",     "reason": "",     "usedScene": {        "ability": [         "VideoPlayAbilitySlice"       ],       "when": "inuse"     }   },   {      "name": "ohos.permission.GET_BUNDLE_INFO",     "reason": "",     "usedScene": {        "ability": [         "VideoPlayAbilitySlice"       ],       "when": "inuse"     }   },   {      "name": "ohos.permission.KEEP_BACKGROUND_RUNNING",     "reason": "",     "usedScene": {        "ability": [         "VideoPlayAbilitySlice"       ],       "when": "inuse"     }   } 

回顾总结

手机端控制TV端视频播放的流程

手机端:

点击手机端播放器(VideoPlayAbilitySlice)的【流转】按钮-------获取&选择可以流转的设备----启动 TV端播放器(MainAbility/VideoPlayAbilitySlice) & 连接TV端播放控制服务(VideoControlServiceAbility)-----在建立连接后,初始化控制面板并且对控制操作进行监听。

当操作控制面板(RemoteController)时 --发布事件通知播放器组件(VideoPlayAbilitySlice)-----(VideoPlayAbilitySlice)使用控制服务的远端代理(MyRemoteProxy,commonlib提供)发送控制指令到 TV端播放控制服务(VideoControlServiceAbility)。

TV端:

当TV端播放器(VideoPlayAbilitySlice)被启动时-----初始化视频播放器组件& 注册远端控制回调(registerRemoteControlCallback) & 获取手机端Intent传递的视频索引+视频URL+播放进度+手机端设备ID-----然后连接手机端的同步控制服务(SyncControlServiceAbility)— 在建立连接后,初始化代理(MyRemoteProxy)-----订阅手机端播放器控制事件。

当播放控制服务(VideoControlServiceAbility)收到控制指令后-----通过事件方式发布通知-----视频播放器(VideoPlayAbilitySlice)收到通知后对播放器进行设置-----注册远端控制回调(remoteControlCallback)将状态同步给远端的手机端。

文章相关附件可以点击下面的原文链接前往下载

https://harmonyos.51cto.com/resource/1356

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

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

https://harmonyos.51cto.com

-->
滇ICP备2023000592号-31