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

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

[[430308]]

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

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

https://harmonyos.51cto.com

介绍

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

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

效果展示

DistributedVideoPlayer 分布式视频播放器(二)-鸿蒙HarmonyOS技术社区

搭建环境

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

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

如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作

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

下载源码后,使用DevEco 打开项目。

代码结构

手机端

Java后台

  1. │  config.json 
  2. │ 
  3. ├─java 
  4. │  └─com 
  5. │      └─buty 
  6. │          └─distributedvideoplayer 
  7. │              │  MainAbility.java                
  8. │              │  MyApplication.java 
  9. │              │ 
  10. │              ├─ability 
  11. │              │      DevicesSelectAbility.java       #可流转的设备列表 
  12. │              │      MainAbilitySlice.java           #视频播放列表页 
  13. │              │      SyncControlServiceAbility.java  #同步控制服务,TV-->Phone 
  14. │              │      VideoPlayAbility.java           #视频播放Ability 
  15. │              │      VideoPlayAbilitySlice.java      #视频播放详情和评论页 
  16. │              │ 
  17. │              ├─components 
  18. │              │      EpisodesSelectionDialog.java     
  19. │              │      RemoteController.java           #远端控制器 
  20. │              │      VideoPlayerPlaybackButton.java  #播放按钮组件 
  21. │              │      VideoPlayerSlider.java          #播放时间进度条 
  22. │              │ 
  23. │              ├─constant 
  24. │              │      Constants.java                  #常量 
  25. │              │      ResolutionEnum.java             #分辨率枚举 
  26. │              │      RouteRegister.java              #自定义路由 
  27. │              │ 
  28. │              ├─data 
  29. │              │      VideoInfo.java                  #视频基础信息 
  30. │              │      VideoInfoService.java           #视频信息服务,用于模拟数据 
  31. │              │      Videos.java                     #视频列表 
  32. │              │  
  33. │              ├─model 
  34. │              │      CommentModel.java               #评论模型 
  35. │              │      DeviceModel.java                #设备模型 
  36. │              │      ResolutionModel.java            #解析度模型 
  37. │              │      VideoModel.java                 #视频模型 
  38. │              │ 
  39. │              ├─provider 
  40. │              │      CommentItemProvider.java        #评论数据提供程序 
  41. │              │      DeviceItemProvider.java         #设备列表提供程序 
  42. │              │      ResolutionItemProvider.java     #解析度数据提供程序 
  43. │              │      VideoItemProvider.java          #视频数据提供程序 
  44. │              │ 
  45. │              └─utils 
  46. │                      AppUtil.java                   #工具类 
  47. │                      DateUtils.java 

页面布局

  1. │  │ 
  2.    │  ├─layout 
  3.    │  │      ability_main.xml                                #播放列表布局 
  4.    │  │      comments_item.xml                               #单条评论布局 
  5.    │  │      dialog_playlist.xml                      
  6.    │  │      dialog_resolution_list.xml 
  7.    │  │      dialog_table_layout.xml 
  8.    │  │      hm_sample_ability_video_box.xml                 #视频播放组件页 
  9.    │  │      hm_sample_ability_video_comments.xml            #播放详情布局页 
  10.    │  │      hm_sample_view_video_box_seek_bar_style1.xml    #播放进度条布局 
  11.    │  │      hm_sample_view_video_box_seek_bar_style2.xml 
  12.    │  │      remote_ability_control.xml                      #远程控制器布局 
  13.    │  │      remote_ability_episodes.xml              
  14.    │  │      remote_ability_select_devices.xml               #可流转设备列表布局 
  15.    │  │      remote_ability_sound_equipment.xml           
  16.    │  │      remote_device_item.xml                          #设备子项显示布局 
  17.    │  │      remote_episodes_item.xml 
  18.    │  │      remote_video_quality_item.xml 

TV端

Java后台

  1. ├─main 
  2.  │  │  config.json 
  3.  │  │ 
  4.  │  ├─java 
  5.  │  │  └─com 
  6.  │  │      └─buty 
  7.  │  │          └─distributedvideoplayer 
  8.  │  │              │  MainAbility.java 
  9.  │  │              │  MyApplication.java 
  10.  │  │              │  VideoControlServiceAbility.java      #视频控制服务  Phone--->TV 
  11.  │  │              │ 
  12.  │  │              ├─component 
  13.  │  │              │      VideoSetting.java                 
  14.  │  │              │ 
  15.  │  │              ├─constant                              #一些常量和枚举值 
  16.  │  │              │      Constants.java                    
  17.  │  │              │      ResolutionEnum.java 
  18.  │  │              │      SettingOptionEnum.java 
  19.  │  │              │      SpeedEnum.java 
  20.  │  │              │ 
  21.  │  │              ├─data 
  22.  │  │              │      VideoInfo.java                  #视频基本信息 
  23.  │  │              │      VideoInfoService.java           #视频数据服务,读取json中的数据 
  24.  │  │              │      Videos.java                     #视频对象 
  25.  │  │              │ 
  26.  │  │              ├─model                                #一些数据模型 
  27.  │  │              │      ResolutionModel.java 
  28.  │  │              │      SettingComponentTag.java 
  29.  │  │              │      SettingModel.java 
  30.  │  │              │      VideoModel.java 
  31.  │  │              │ 
  32.  │  │              ├─provider 
  33.  │  │              │      SettingProvider.java 
  34.  │  │              │      VideoEpisodesSelectProvider.java 
  35.  │  │              │      VideoSettingProvider.java 
  36.  │  │              │ 
  37.  │  │              ├─slice 
  38.  │  │              │      MainAbilitySlice.java            
  39.  │  │              │      VideoPlayAbilitySlice.java      #视频播放能力页 
  40.  │  │              │  
  41.  │  │              ├─utils 
  42.  │  │              │      AppUtil.java 
  43.  │  │              │ 
  44.  │  │              └─view 
  45.  │  │                      VideoPlayerPlaybackButton.java 
  46.  │  │                      VideoPlayerSlider.java 

页面布局

  1. │      │  │ 
  2.    │      │  ├─layout 
  3.    │      │  │      ability_main.xml 
  4.    │      │  │      ability_video_box.xml                  #播放器布局页面 
  5.    │      │  │      video_common_item.xml 
  6.    │      │  │      video_episodes_item.xml                 
  7.    │      │  │      video_setting.xml 
  8.    │      │  │      video_setting_item.xml 
  9.    │      │  │      view_video_box_seek_bar_style1.xml    #播放器进度条布局 

实现步骤

1.手机端

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

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

DistributedVideoPlayer 分布式视频播放器(二)-鸿蒙HarmonyOS技术社区
  1. <?xml version="1.0" encoding="utf-8"?> 
  2. <DependentLayout 
  3.     xmlns:ohos="http://schemas.huawei.com/res/ohos" 
  4.     ohos:height="match_parent" 
  5.     ohos:width="match_parent" 
  6.     ohos:clickable="true"
  7.     <DirectionalLayout 
  8.         ohos:height="match_parent" 
  9.         ohos:width="match_parent" 
  10.         ohos:background_element="$graphic:background_ability_control_bg" 
  11.         ohos:orientation="vertical"
  12.         <StackLayout 
  13.             ohos:id="$+id:control_app_bar" 
  14.             ohos:height="match_content" 
  15.             ohos:width="match_parent"
  16.  
  17.             <DirectionalLayout 
  18.                 ohos:id="$+id:control_app_bar_left" 
  19.                 ohos:height="56vp" 
  20.                 ohos:width="match_content" 
  21.                 ohos:layout_alignment="vertical_center" 
  22.                 ohos:orientation="horizontal"
  23.                 <Image 
  24.                     ohos:id="$+id:app_bar_back" 
  25.                     ohos:height="$float:default_image_size" 
  26.                     ohos:width="$float:default_image_size" 
  27.                     ohos:foreground_element="$media:ic_back" 
  28.                     ohos:layout_alignment="center" 
  29.                     ohos:start_margin="$float:default_margin"
  30.                 </Image> 
  31.  
  32.                 <Text 
  33.                     ohos:id="$+id:app_bar_device_name" 
  34.                     ohos:height="match_parent" 
  35.                     ohos:width="match_content" 
  36.                     ohos:start_margin="12vp" 
  37.                     ohos:text="" 
  38.                     ohos:text_color="$color:default_white_color" 
  39.                     ohos:text_size="$float:normal_text_size_20" 
  40.                     ohos:truncation_mode="ellipsis_at_end"/> 
  41.             </DirectionalLayout> 
  42.         </StackLayout> 
  43.         <DirectionalLayout 
  44.             ohos:height="match_content" 
  45.             ohos:width="match_parent" 
  46.             ohos:layout_alignment="vertical_center" 
  47.             ohos:orientation="horizontal"
  48.             <Image 
  49.                 ohos:height="16vp" 
  50.                 ohos:width="16vp" 
  51.                 ohos:foreground_element="$media:ic_play" 
  52.                 ohos:layout_alignment="center" 
  53.                 ohos:start_margin="$float:default_margin"
  54.             </Image> 
  55.  
  56.             <Text 
  57.                 ohos:id="$+id:device_video_desc" 
  58.                 ohos:height="match_content" 
  59.                 ohos:width="match_parent" 
  60.                 ohos:auto_scrolling_count="unlimited" 
  61.                 ohos:end_margin="$float:default_margin" 
  62.                 ohos:start_margin="16vp" 
  63.                 ohos:text="" 
  64.                 ohos:text_color="$color:default_white_color" 
  65.                 ohos:text_size="$float:little_text_size_12" 
  66.                 ohos:truncation_mode="auto_scrolling"/> 
  67.         </DirectionalLayout> 
  68.         <DirectionalLayout 
  69.             ohos:id="$+id:control_middle_panel" 
  70.             ohos:height="225vp" 
  71.             ohos:width="225vp" 
  72.             ohos:background_element="$graphic:background_ability_control_middle" 
  73.             ohos:layout_alignment="center" 
  74.             ohos:orientation="vertical" 
  75.             ohos:top_margin="64vp"
  76.             <DirectionalLayout 
  77.                 ohos:id="$+id:control_middle_panel_top" 
  78.                 ohos:height="75vp" 
  79.                 ohos:width="match_parent"
  80.                 <Image 
  81.                     ohos:id="$+id:control_voice_up" 
  82.                     ohos:height="$float:default_image_size" 
  83.                     ohos:width="$float:default_image_size" 
  84.                     ohos:background_element="$graphic:background_button_click" 
  85.                     ohos:foreground_element="$media:ic_voice" 
  86.                     ohos:layout_alignment="center" 
  87.                     ohos:top_margin="28vp"/> 
  88.             </DirectionalLayout> 
  89.             <DirectionalLayout 
  90.                 ohos:id="$+id:control_middle_panel_center" 
  91.                 ohos:height="75vp" 
  92.                 ohos:width="match_parent" 
  93.                 ohos:orientation="horizontal"
  94.                 <DirectionalLayout 
  95.                     ohos:id="$+id:control_backword_parent" 
  96.                     ohos:height="match_parent" 
  97.                     ohos:width="75vp" 
  98.                     ohos:alignment="vertical_center"
  99.                     <Image 
  100.                         ohos:id="$+id:control_backword" 
  101.                         ohos:height="$float:default_image_size" 
  102.                         ohos:width="$float:default_image_size" 
  103.                         ohos:background_element="$graphic:background_button_click" 
  104.                         ohos:foreground_element="$media:ic_anthology" 
  105.                         ohos:layout_alignment="center"/> 
  106.  
  107.                 </DirectionalLayout> 
  108.                 <DirectionalLayout 
  109.                     ohos:id="$+id:control_play_parent" 
  110.                     ohos:height="match_parent" 
  111.                     ohos:width="75vp" 
  112.                     ohos:alignment="center"
  113.                     <Image 
  114.                         ohos:id="$+id:control_play" 
  115.                         ohos:height="45vp" 
  116.                         ohos:width="45vp" 
  117.                         ohos:background_element="$graphic:background_ability_control_ok" 
  118.                         ohos:image_src="$media:ic_pause_black" 
  119.                         ohos:layout_alignment="center"/> 
  120.                 </DirectionalLayout> 
  121.                 <DirectionalLayout 
  122.                     ohos:id="$+id:control_forward_parent" 
  123.                     ohos:height="match_parent" 
  124.                     ohos:width="75vp" 
  125.                     ohos:alignment="vertical_center"
  126.                     <Image 
  127.                         ohos:id="$+id:control_forward" 
  128.                         ohos:height="$float:default_image_size" 
  129.                         ohos:width="$float:default_image_size" 
  130.                         ohos:background_element="$graphic:background_button_click" 
  131.                         ohos:foreground_element="$media:ic_anthology" 
  132.                         ohos:layout_alignment="center" 
  133.                         ohos:rotate="180"/> 
  134.                 </DirectionalLayout> 
  135.             </DirectionalLayout> 
  136.             <DirectionalLayout 
  137.                 ohos:id="$+id:control_middle_panel_bottom" 
  138.                 ohos:height="75vp" 
  139.                 ohos:width="match_parent"
  140.                 <Image 
  141.                     ohos:id="$+id:control_voice_down" 
  142.                     ohos:height="$float:default_image_size" 
  143.                     ohos:width="$float:default_image_size" 
  144.                     ohos:background_element="$graphic:background_button_click" 
  145.                     ohos:foreground_element="$media:ic_voice" 
  146.                     ohos:layout_alignment="center" 
  147.                     ohos:top_margin="23vp"/> 
  148.             </DirectionalLayout> 
  149.         </DirectionalLayout> 
  150.         <DirectionalLayout 
  151.             ohos:height="0vp" 
  152.             ohos:width="match_parent" 
  153.             ohos:alignment="vertical_center" 
  154.             ohos:orientation="horizontal" 
  155.             ohos:weight="2"
  156.             <Text 
  157.                 ohos:id="$+id:control_current_time" 
  158.                 ohos:height="match_content" 
  159.                 ohos:width="match_content" 
  160.                 ohos:end_margin="4vp" 
  161.                 ohos:start_margin="$float:default_margin" 
  162.                 ohos:text="" 
  163.                 ohos:text_color="$color:default_white_color" 
  164.                 ohos:text_size="12vp"/> 
  165.  
  166.             <Slider 
  167.                 ohos:id="$+id:control_progress" 
  168.                 ohos:height="10vp" 
  169.                 ohos:width="0vp" 
  170.                 ohos:orientation="horizontal" 
  171.                 ohos:progress_color="#FF6103" 
  172.                 ohos:thumb_element="$graphic:background_slide_thumb" 
  173.                 ohos:weight="5"/> 
  174.  
  175.             <Text 
  176.                 ohos:id="$+id:control_end_time" 
  177.                 ohos:height="match_content" 
  178.                 ohos:width="match_content" 
  179.                 ohos:end_margin="$float:default_margin" 
  180.                 ohos:text="" 
  181.                 ohos:text_color="$color:default_white_color" 
  182.                 ohos:text_size="12vp"/> 
  183.  
  184.         </DirectionalLayout> 
  185.         <DirectionalLayout 
  186.             ohos:height="match_content" 
  187.             ohos:width="match_parent" 
  188.             ohos:bottom_margin="48vp" 
  189.             ohos:start_margin="16vp" 
  190.             ohos:top_margin="48vp"
  191.             <StackLayout 
  192.                 ohos:height="26vp" 
  193.                 ohos:width="match_parent"
  194.                 <DirectionalLayout 
  195.                     ohos:height="match_parent" 
  196.                     ohos:width="match_parent"
  197.                     <Text 
  198.                         ohos:height="match_parent" 
  199.                         ohos:width="match_parent" 
  200.                         ohos:text="$string:control_episodes" 
  201.                         ohos:text_alignment="vertical_center" 
  202.                         ohos:text_color="#000000" 
  203.                         ohos:text_size="18fp"/> 
  204.                 </DirectionalLayout> 
  205.                 <DirectionalLayout 
  206.                     ohos:height="match_parent" 
  207.                     ohos:width="match_parent" 
  208.                     ohos:alignment="right" 
  209.                     ohos:orientation="horizontal"
  210.                     <Text 
  211.                         ohos:id="$+id:control_episodes_num" 
  212.                         ohos:height="match_parent" 
  213.                         ohos:width="match_content" 
  214.                         ohos:background_element="$graphic:background_button_click" 
  215.                         ohos:text="" 
  216.                         ohos:text_color="$color:default_black_color" 
  217.                         ohos:text_size="14fp"/> 
  218.                     <Image 
  219.                         ohos:id="$+id:control_all_episodes" 
  220.                         ohos:height="$float:default_image_size" 
  221.                         ohos:width="$float:default_image_size" 
  222.                         ohos:background_element="$graphic:background_button_click" 
  223.                         ohos:end_margin="8vp" 
  224.                         ohos:foreground_element="$media:ic_right_arrow" 
  225.                         ohos:layout_alignment="center"/> 
  226.                 </DirectionalLayout> 
  227.             </StackLayout> 
  228.             <TableLayout 
  229.                 ohos:id="$+id:cotrol_bottom_item" 
  230.                 ohos:height="match_content" 
  231.                 ohos:width="match_parent" 
  232.                 ohos:below="$id:episodes_header" 
  233.                 ohos:column_count="6" 
  234.                 ohos:top_margin="12vp"
  235.             </TableLayout> 
  236.         </DirectionalLayout> 
  237.     </DirectionalLayout> 
  238. </DependentLayout> 

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

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

DistributedVideoPlayer 分布式视频播放器(二)-鸿蒙HarmonyOS技术社区
  1. <?xml version="1.0" encoding="utf-8"?> 
  2. <DirectionalLayout 
  3.     xmlns:ohos="http://schemas.huawei.com/res/ohos" 
  4.     ohos:height="match_parent" 
  5.     ohos:width="match_parent" 
  6.     ohos:alignment="vertical_center" 
  7.     ohos:background_element="$color:default_panel_background" 
  8.     ohos:orientation="vertical"
  9.  
  10.     <DependentLayout 
  11.         ohos:height="100vp" 
  12.         ohos:width="match_parent" 
  13.         ohos:background_element="$graphic:background_ability_devices" 
  14.         ohos:end_margin="12vp" 
  15.         ohos:end_padding="$float:default_margin" 
  16.         ohos:layout_alignment="vertical_center" 
  17.         ohos:start_margin="12vp" 
  18.         ohos:start_padding="$float:default_margin"
  19.         <Text 
  20.             ohos:id="$+id:devices_title" 
  21.             ohos:height="match_content" 
  22.             ohos:width="match_parent" 
  23.             ohos:text="$string:local_machine" 
  24.             ohos:text_color="$color:default_black_color" 
  25.             ohos:text_size="14fp" 
  26.             ohos:top_margin="12vp"/> 
  27.         <Image 
  28.             ohos:id="$+id:devices_head_icon" 
  29.             ohos:height="$float:default_image_size" 
  30.             ohos:width="$float:default_image_size" 
  31.             ohos:below="$id:devices_title" 
  32.             ohos:foreground_element="$media:icon" 
  33.             ohos:top_margin="20vp"/> 
  34.         <DirectionalLayout 
  35.             ohos:height="match_content" 
  36.             ohos:width="match_parent" 
  37.             ohos:below="$id:devices_title" 
  38.             ohos:end_of="$id:devices_head_icon" 
  39.             ohos:orientation="vertical" 
  40.             ohos:start_padding="12vp" 
  41.             ohos:top_margin="12vp"
  42.             <Text 
  43.                 ohos:id="$+id:devices_head_app_name" 
  44.                 ohos:height="match_content" 
  45.                 ohos:width="match_parent" 
  46.                 ohos:max_height="28vp" 
  47.                 ohos:text="" 
  48.                 ohos:text_color="$color:default_black_color" 
  49.                 ohos:text_size="14fp"/> 
  50.             <Text 
  51.                 ohos:id="$+id:devices_head_video_name" 
  52.                 ohos:height="18vp" 
  53.                 ohos:width="match_parent" 
  54.                 ohos:text="" 
  55.                 ohos:text_color="#99000000" 
  56.                 ohos:text_size="12fp" 
  57.                 ohos:truncation_mode="ellipsis_at_end"/> 
  58.         </DirectionalLayout> 
  59.     </DependentLayout> 
  60.     <DirectionalLayout 
  61.         ohos:height="300vp" 
  62.         ohos:width="match_parent" 
  63.         ohos:background_element="$graphic:background_ability_devices" 
  64.         ohos:end_margin="12vp" 
  65.         ohos:orientation="vertical" 
  66.         ohos:padding="$float:default_margin" 
  67.         ohos:start_margin="12vp" 
  68.         ohos:top_margin="12vp"
  69.         <Text 
  70.             ohos:height="21vp" 
  71.             ohos:width="match_parent" 
  72.             ohos:bottom_margin="10vp" 
  73.             ohos:text="$string:my_devices" 
  74.             ohos:text_color="$color:default_black_color" 
  75.             ohos:text_size="16vp"/> 
  76.         <ListContainer 
  77.             ohos:id="$+id:devices_container" 
  78.             ohos:height="match_parent" 
  79.             ohos:width="match_parent" 
  80.             ohos:layout_alignment="horizontal_center" 
  81.             ohos:orientation="vertical"/> 
  82.     </DirectionalLayout> 
  83. </DirectionalLayout> 

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

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

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

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

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

  1. /** 
  2.  * 初始化远端控制视图的各个组件 
  3.  */ 
  4. private void initView() { 
  5.     //设置隐藏 
  6.     setVisibility(INVISIBLE); 
  7.     if (controllerView == null) { 
  8.         controllerView = 
  9.                 LayoutScatter.getInstance(slice).parse(ResourceTable.Layout_remote_ability_control, this, false); 
  10.     } 
  11.  
  12.     //初始化文本 
  13.     initItemText(); 
  14.     initItemSize(); 
  15.     initItemImage(); 
  16.  
  17.     //进度滑块 
  18.     initProgressSlider(); 
  19.     //初始化按钮 
  20.     initButton(ResourceTable.Id_app_bar_back); 
  21.     initButton(ResourceTable.Id_control_episodes_num); 
  22.     initButton(ResourceTable.Id_control_all_episodes); 
  23.     initButton(ResourceTable.Id_control_play); 
  24.     initButton(ResourceTable.Id_control_backword); 
  25.     initButton(ResourceTable.Id_control_forward); 
  26.     initButton(ResourceTable.Id_control_voice_down); 
  27.     initButton(ResourceTable.Id_control_voice_up); 
  28.  
  29.     //初始化底部的显示的视频剧集 
  30.     initBottomComponent(); 
  31.  
  32.     //将组件追加到队列末尾 
  33.     addComponent(controllerView); 
  34.  
  35.     //初始化剧集对话框 
  36.     initEpisodesDialog(); 
  37.  
  38.     isPlaying = true

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

  1. /** 
  2.  * 控制器面板操作监听 
  3.  * 播放/快退/快进/音量加减/停止连接/切换视频/切换解析度 
  4.  * RemoteControllerListener 
  5.  */ 
  6. public interface RemoteControllerListener { 
  7.      //发送控制码给该接口的实现 
  8.     void sendControl(int code, String extra); 
  9. /** 
  10.  *  
  11.  * 设置控制器监听器 
  12.  * setRemoteControllerCallback 
  13.  * 
  14.  * @param listener listener 
  15.  */ 
  16. public void setRemoteControllerCallback(RemoteControllerListener listener) { 
  17.     remoteControllerListener = listener; 
  18.  
  19.  
  20. /** 
  21.  * 点击事件进行统一处理,通过sendControl发送出去 
  22.  */ 
  23. @Override 
  24. public void onClick(Component component) { 
  25.     switch (component.getId()) { 
  26.         //返回组件 
  27.         case ResourceTable.Id_app_bar_back: 
  28.             hide(true); 
  29.             break; 
  30.  
  31.         case ResourceTable.Id_control_episodes_num: 
  32.             //剧集组件,显示剧集对话框 
  33.         case ResourceTable.Id_control_all_episodes: 
  34.             episodesDialog.setVisibility(VISIBLE); 
  35.             break; 
  36.             //播放组件,发送播放的控制指令 
  37.         case ResourceTable.Id_control_play: 
  38.             remoteControllerListener.sendControl(ControlCode.PLAY.getCode(), ""); 
  39.             break; 
  40.             //快退组件,发送快退指令 
  41.         case ResourceTable.Id_control_backword: 
  42.             remoteControllerListener.sendControl(ControlCode.BACKWARD.getCode(), ""); 
  43.             break; 
  44.             //快进组件,发送快进指令 
  45.         case ResourceTable.Id_control_forward: 
  46.             remoteControllerListener.sendControl(ControlCode.FORWARD.getCode(), ""); 
  47.             break; 
  48.             //增加音量,发送给增加音量指令 
  49.         case ResourceTable.Id_control_voice_up: 
  50.             remoteControllerListener.sendControl(ControlCode.VOLUME_ADD.getCode(), ""); 
  51.             break; 
  52.             //降低音量,发送降低音量指令 
  53.         case ResourceTable.Id_control_voice_down: 
  54.             //关闭显示的对话框 
  55.             if (getDialogVisibility()) { 
  56.                 remoteControllerListener.sendControl(ControlCode.VOLUME_REDUCED.getCode(), ""); 
  57.             } 
  58.             break; 
  59.         default
  60.             break; 
  61.     } 
  62.  
  63. /** 
  64.  * 时间进度条值变化时,设置当前的播放时间 
  65.  * @param slider 
  66.  * @param value 
  67.  * @param fromUser 
  68.  */ 
  69. @Override 
  70. public void onProgressUpdated(Slider slider, int value, boolean fromUser) { 
  71.     HiLog.debug(LABEL,"onProgressUpdated"); 
  72.     slice.getUITaskDispatcher() 
  73.             .delayDispatch( 
  74.                     () -> { 
  75.                         //当前播放的时间进度 
  76.                         Text currentTime = 
  77.                                 (Text) controllerView.findComponentById(ResourceTable.Id_control_current_time); 
  78.                         //设置显示的时间 
  79.                         currentTime.setText( 
  80.                                 DateUtils.msToString(totalTime * value / Constants.ONE_HUNDRED_PERCENT)); 
  81.                     }, 
  82.                     0); 
  83.  
  84. @Override 
  85. public void onTouchStart(Slider slider) { 
  86.     isSliderTouching = true
  87.  
  88.  
  89. /** 
  90.  * 进度条滑块拖拽结束触发,sendControl发送出去 
  91.  * @param slider 
  92.  */ 
  93. @Override 
  94. public void onTouchEnd(Slider slider) { 
  95.     // The pop-up box cannot block the slider touch event. 
  96.     // This event is not processed when a dialog box is displayed. 
  97.     //滑动结束,发送seek指令到远端 
  98.     if (getDialogVisibility()) { 
  99.         // 
  100.         remoteControllerListener.sendControl(ControlCode.SEEK.getCode(), String.valueOf(slider.getProgress())); 
  101.     } 
  102.     isSliderTouching = false

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

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

  1. /** 
  2.  * 可供选择的远端设备能力 
  3.  * Remote Device Selection Ability 
  4.  */ 
  5. public class DevicesSelectAbility extends Ability { 
  6.     @Override 
  7.     public void onStart(Intent intent) { 
  8.  
  9.         //请求数据流转权限 
  10.         requestPermissionsFromUser(new String[]{"ohos.permission.DISTRIBUTED_DATASYNC"}, 0); 
  11.  
  12.         super.onStart(intent); 
  13.         super.setUIContent(ResourceTable.Layout_remote_ability_select_devices); 
  14.  
  15.         this.initPage(intent); 
  16.     } 
  17.  
  18.     /** 
  19.      * 初始化页面组件 
  20.      * 
  21.      * @param intent 
  22.      */ 
  23.     private void initPage(Intent intent) { 
  24.         //从json中获取视频数据 
  25.         VideoInfoService videoService = new VideoInfoService(this); 
  26.  
  27.         //设置app名称 
  28.         Text appName = (Text) findComponentById(ResourceTable.Id_devices_head_app_name); 
  29.         appName.setText(ResourceTable.String_entry_MainAbility); 
  30.  
  31.         //视频名称组件 
  32.         Text videoName = (Text) findComponentById(ResourceTable.Id_devices_head_video_name); 
  33.         //当前播放视频的索引 
  34.         int currentPlayingIndex = intent.getIntParam(Constants.PARAM_VIDEO_INDEX, 0) + 1; 
  35.         //当前播放视频的剧集 
  36.         String playingEpisodes = 
  37.                 AppUtil.getStringResource(this, ResourceTable.String_control_playing_episodes) 
  38.                         .replaceAll("\\?", String.valueOf(currentPlayingIndex)); 
  39.         //设置播放视频名称和剧集 
  40.         videoName.setText(videoService.getAllVideoInfo().getVideoName() + " " + playingEpisodes); 
  41.  
  42.         //在线设备列表,以及设置点击的监听事件、传递数据 
  43.         ListContainer listContainer = (ListContainer) findComponentById(ResourceTable.Id_devices_container); 
  44.         List<DeviceModel> devices = AppUtil.getDevicesInfo(); 
  45.  
  46.         //容器绑定数据提供程序 
  47.         DeviceItemProvider provider = new DeviceItemProvider(this, devices); 
  48.         listContainer.setItemProvider(provider); 
  49.  
  50.         //设置点击监听处理 
  51.         listContainer.setItemClickedListener( 
  52.                 (container, component, position, id) -> { 
  53.                     //获取点击的item 
  54.                     DeviceModel item = (DeviceModel) listContainer.getItemProvider().getItem(position); 
  55.  
  56.                     //返回数据意图 
  57.                     Intent intentResult = new Intent(); 
  58.                     //设置要返回的参数 
  59.                     intentResult.setParam(Constants.PARAM_DEVICE_TYPE, item.getDeviceType()); 
  60.                     intentResult.setParam(Constants.PARAM_DEVICE_ID, item.getDeviceId()); 
  61.                     intentResult.setParam(Constants.PARAM_DEVICE_NAME, item.getDeviceName()); 
  62.                     //设置返回结果 
  63.                     setResult(0, intentResult); 
  64.  
  65.                     //关闭当前Ability 
  66.                     this.terminateAbility(); 
  67.                 }); 
  68.     } 

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

  1. /** 
  2.  * 设备列表提供程序 
  3.  * Device information list processing class 
  4.  */ 
  5. public class DeviceItemProvider extends BaseItemProvider { 
  6.     private final Context context; 
  7.     private final List<DeviceModel> list; 
  8.  
  9.     /** 
  10.      * Initialization 
  11.      */ 
  12.     public DeviceItemProvider(Context context, List<DeviceModel> list) { 
  13.         this.context = context; 
  14.         this.list = list; 
  15.     } 
  16.  
  17.     @Override 
  18.     public int getCount() { 
  19.         return list == null ? 0 : list.size(); 
  20.     } 
  21.  
  22.     @Override 
  23.     public Object getItem(int position) { 
  24.         if (list != null && position >= 0 && position < list.size()) { 
  25.             return list.get(position); 
  26.         } 
  27.         return new DeviceModel(); 
  28.     } 
  29.  
  30.     @Override 
  31.     public long getItemId(int position) { 
  32.         return position; 
  33.     } 
  34.  
  35.     @Override 
  36.     public Component getComponent(int position, Component convertComponent, ComponentContainer componentContainer) { 
  37.         final Component cpt; 
  38.         if (convertComponent == null) { 
  39.             cpt = LayoutScatter.getInstance(context).parse(ResourceTable.Layout_remote_device_item, nullfalse); 
  40.         } else { 
  41.             cpt = convertComponent; 
  42.         } 
  43.         DeviceModel deviceItem = list.get(position); 
  44.         //设备名称 
  45.         Text deviceName = (Text) cpt.findComponentById(ResourceTable.Id_device_item_name); 
  46.         deviceName.setText(deviceItem.getDeviceName()); 
  47.  
  48.         //设备图标 
  49.         Image deviceIcon = (Image) cpt.findComponentById(ResourceTable.Id_device_item_icon); 
  50.         AppUtil.setDeviceIcon(deviceItem.getDeviceType(), deviceIcon); 
  51.  
  52.         if (position == list.size() - 1) { 
  53.             Component divider = cpt.findComponentById(ResourceTable.Id_device_item_divider); 
  54.             divider.setVisibility(Component.INVISIBLE); 
  55.         } 
  56.  
  57.         return cpt; 
  58.     } 

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

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

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

  1. /** 
  2.  * 打开设备选择Ability后,选择流转的设备setResult后触发 
  3.  * @param requestCode 
  4.  * @param resultCode 
  5.  * @param resultIntent 
  6.  */ 
  7. @Override 
  8. protected void onAbilityResult(int requestCode, int resultCode, Intent resultIntent) { 
  9.     HiLog.debug(LABEL, "onAbilityResult"); 
  10.     // 
  11.     if (requestCode == Constants.PRESENT_SELECT_DEVICES_REQUEST_CODE && resultIntent != null) { 
  12.         // 
  13.         startRemoteAbilityPa(resultIntent); 
  14.         return
  15.     } 
  16.     // 
  17.     setDisplayOrientation(AbilityInfo.DisplayOrientation.values()[sourceDisplayOrientation + 1]); 
  18.     if (isVideoPlaying) { 
  19.         player.start(); 
  20.     } 
  21. /** 
  22.  * 开启远端Ability 
  23.  * 
  24.  * @param resultIntent 
  25.  */ 
  26. private void startRemoteAbilityPa(Intent resultIntent) { 
  27.  
  28.     //远端TV设备ID 
  29.     String devicesId = resultIntent.getStringParam(Constants.PARAM_DEVICE_ID); 
  30.     Intent intent = new Intent(); 
  31.     Operation operation = 
  32.             new Intent.OperationBuilder() 
  33.                     .withDeviceId(devicesId) 
  34.                     .withBundleName(getBundleName()) 
  35.                     .withAbilityName("com.buty.distributedvideoplayer.MainAbility"
  36.                     .withAction("action.video.play"
  37.                     .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE) 
  38.                     .build(); 
  39.     //本地存储设备ID 
  40.     String localDeviceId = 
  41.             KvManagerFactory.getInstance().createKvManager(new KvManagerConfig(this)).getLocalDeviceInfo().getId(); 
  42.  
  43.     HiLog.debug(LABEL, "remoteDevicesId:" + devicesId + ",localDeviceId:" + localDeviceId); 
  44.  
  45.     //播放的视频路径 
  46.     String path = 
  47.             videoService 
  48.                     .getVideoInfoByIndex(currentPlayingIndex) 
  49.                     .getResolutions() 
  50.                     .get(currentPlayingResolutionIndex) 
  51.                     .getUrl(); 
  52.     //本地ph()one设备ID 
  53.     intent.setParam(RemoteConstant.INTENT_PARAM_REMOTE_DEVICE_ID, localDeviceId); 
  54.     //播放视频的URL 
  55.     intent.setParam(RemoteConstant.INTENT_PARAM_REMOTE_VIDEO_PATH, path); 
  56.     //播放不同分辨率视频的索引 
  57.     intent.setParam(RemoteConstant.INTENT_PARAM_REMOTE_VIDEO_INDEX, currentPlayingIndex); 
  58.     //播放进度位置 
  59.     intent.setParam(RemoteConstant.INTENT_PARAM_REMOTE_START_POSITION, (int) player.getSeekWhenPrepared()); 
  60.     intent.setOperation(operation); 
  61.     //启动远端的播放Ability 
  62.     startAbility(intent); 
  63.  
  64.     //远端视频控制元服务 
  65.     Intent remotePaIntent = new Intent(); 
  66.     Operation paOperation = 
  67.             new Intent.OperationBuilder() 
  68.                     .withDeviceId(devicesId) 
  69.                     .withBundleName(getBundleName()) 
  70.                     .withAbilityName("com.buty.distributedvideoplayer.VideoControlServiceAbility"
  71.                     .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE) 
  72.                     .build(); 
  73.     remotePaIntent.setOperation(paOperation); 
  74.     //连接远端视频控制服务,使用2台P40的超级终端模拟器连接不成功 
  75.     //Context::connectRemoteAbility failed, errorCode is 1319 
  76.     boolean connectFlag = connectAbility(remotePaIntent, connection); 
  77.  
  78.     if (connectFlag) { 
  79.         HiLog.debug(LABEL, "start remote ability PA success"); 
  80.         //设置显示方向为竖屏 
  81.         setDisplayOrientation(AbilityInfo.DisplayOrientation.PORTRAIT); 
  82.  
  83.         //初始化远端控制 
  84.         initRemoteController(); 
  85.  
  86.         //设置播放进度、状态、等 
  87.         remoteController.setVideoInfo( 
  88.                 resultIntent.getStringParam(Constants.PARAM_DEVICE_NAME), 
  89.                 currentPlayingIndex, 
  90.                 (int) player.getCurrentPosition(), 
  91.                 (int) player.getDuration()); 
  92.         remoteController.show(); 
  93.     } else { 
  94.         HiLog.error(LABEL, "start remote ability PA failed"); 
  95.         stopAbility(intent); 
  96.     } 

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

  1. /** 
  2.  * 初始化控制器 及 监听 
  3.  */ 
  4. private void initRemoteController() { 
  5.     if (remoteController == null) { 
  6.         remoteController = new RemoteController(this); 
  7.  
  8.         //手机端控制面板操作的监听回调 
  9.         remoteController.setRemoteControllerCallback( 
  10.                 (code, extra) -> { 
  11.                     if (mProxy == null) { 
  12.                         return
  13.                     } 
  14.                     //发送控制指令到TV端 
  15.                     boolean result = 
  16.                             mProxy.sendDataToRemote(RemoteConstant.REQUEST_CONTROL_REMOTE_DEVICE, code, extra); 
  17.  
  18.                     if (!result) { 
  19.                         new ToastDialog(getContext()) 
  20.                                 .setText( 
  21.                                         AppUtil.getStringResource( 
  22.                                                 getContext(), ResourceTable.String_send_failed_tips)) 
  23.                                 .show(); 
  24.                         remoteController.hide(false); 
  25.                     } 
  26.                 }); 
  27.  
  28.         StackLayout rootLayout = (StackLayout) findComponentById(ResourceTable.Id_root_layout); 
  29.         rootLayout.addComponent(remoteController); 
  30.     } 

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

  1. /** 
  2.  * 订阅事件,用于 "TV端->手机端" 方向的播放状态的同步 
  3.  */ 
  4. private void subscribe() { 
  5.     HiLog.debug(LABEL, "subscribe"); 
  6.     MatchingSkills matchingSkills = new MatchingSkills(); 
  7.     //手机端控制面板的 控制事件 
  8.     matchingSkills.addEvent(Constants.PHONE_CONTROL_EVENT); 
  9.     matchingSkills.addEvent(CommonEventSupport.COMMON_EVENT_SCREEN_ON); 
  10.     CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(matchingSkills); 
  11.  
  12.     //事件订阅器 TODO 
  13.     subscriber = new MyCommonEventSubscriber(subscribeInfo); 
  14.     try { 
  15.         CommonEventManager.subscribeCommonEvent(subscriber); 
  16.     } catch (RemoteException e) { 
  17.         HiLog.error(LABEL, "subscribeCommonEvent occur exception."); 
  18.     } 
  19.  
  20. /** 
  21.  * 取消订阅 
  22.  */ 
  23. private void unSubscribe() { 
  24.     HiLog.debug(LABEL, "unSubscribe"); 
  25.     try { 
  26.         CommonEventManager.unsubscribeCommonEvent(subscriber); 
  27.     } catch (RemoteException e) { 
  28.         HiLog.error(LABEL, "unsubscribecommonevent occur exception."); 
  29.     } 
  30. /** 
  31.  * 事件订阅器,用于 "TV端->手机端" 方向的播放状态的同步 
  32.  */ 
  33. class MyCommonEventSubscriber extends CommonEventSubscriber { 
  34.     MyCommonEventSubscriber(CommonEventSubscribeInfo info) { 
  35.         super(info); 
  36.     } 
  37.  
  38.     @Override 
  39.     public void onReceiveEvent(CommonEventData commonEventData) { 
  40.         Intent intent = commonEventData.getIntent(); 
  41.         //获取事件参数,控制指令码 
  42.         int controlCode = intent.getIntParam(Constants.KEY_CONTROL_CODE, 0); 
  43.  
  44.         HiLog.debug(LABEL,"onReceiveEvent: controlCode"+controlCode); 
  45.  
  46.         //未进行远端控制 
  47.         if (remoteController == null || !remoteController.isShown()) { 
  48.             HiLog.debug(LABEL, "remote controller is hidden now"); 
  49.             return
  50.         } 
  51.         //如果是视频播放进度指令 
  52.         if (controlCode == ControlCode.SYNC_VIDEO_PROCESS.getCode()) { 
  53.             int totalTime = Integer.parseInt(intent.getStringParam(Constants.KEY_CONTROL_VIDEO_TIME)); 
  54.             int progress = Integer.parseInt(intent.getStringParam(Constants.KEY_CONTROL_VIDEO_PROGRESS)); 
  55.             //更新的控制面板的进度条 
  56.             remoteController.syncVideoPlayProcess(totalTime, progress); 
  57.  
  58.         //更新控制面板的视频播放状态 
  59.         } else if (controlCode == ControlCode.SYNC_VIDEO_STATUS.getCode()) { 
  60.  
  61.             boolean isPlaying = 
  62.                     Boolean.parseBoolean(intent.getStringParam(Constants.KEY_CONTROL_VIDEO_PLAYBACK_STATUS)); 
  63.             if (remoteController.getPlayingStatus() != isPlaying) { 
  64.                 remoteController.changePlayingStatus(); 
  65.             } 
  66.  
  67.         //更新控制面板的音量 
  68.         } else { 
  69.             int currentVolume = Integer.parseInt(intent.getStringParam(Constants.KEY_CONTROL_VIDEO_VOLUME)); 
  70.             remoteController.changeVolumeIcon(currentVolume); 
  71.  
  72.         } 
  73.     } 

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

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

  1. /** 
  2.  * 同步控制元服务 
  3.  * Video Control Synchronization Service 
  4.  */ 
  5. public class SyncControlServiceAbility extends Ability { 
  6.     private static final HiLogLabel LABEL = new HiLogLabel(0, 0, "=>SyncControlServiceAbility"); 
  7.  
  8.     //远端设备代理 
  9.     private final MyRemote remote = new MyRemote(RemoteConstant.REQUEST_SYNC_VIDEO_STATUS); 
  10.  
  11.     @Override 
  12.     public void onStart(Intent intent) { 
  13.         super.onStart(intent); 
  14.         // 
  15.         remote.setRemoteRequestCallback( 
  16.                 this::sendEvent); 
  17.     } 
  18.  
  19.     @Override 
  20.     public void onBackground() { 
  21.         super.onBackground(); 
  22.     } 
  23.  
  24.     @Override 
  25.     public void onStop() { 
  26.         super.onStop(); 
  27.     } 
  28.  
  29.     @Override 
  30.     protected IRemoteObject onConnect(Intent intent) { 
  31.         super.onConnect(intent); 
  32.         return remote.asObject(); 
  33.     } 
  34.  
  35.     /** 
  36.      * 发送播放器事件 
  37.      * @param controlCode 
  38.      * @param value 
  39.      */ 
  40.     private void sendEvent(int controlCode, Map<?, ?> value) { 
  41.         HiLog.debug(LABEL,"sendEvent,controlCode:"+controlCode+",value:"+value.toString()); 
  42.         try { 
  43.             Intent intent = new Intent(); 
  44.             Operation operation = new Intent.OperationBuilder().withAction(Constants.PHONE_CONTROL_EVENT).build(); 
  45.             intent.setOperation(operation); 
  46.             intent.setParam(Constants.KEY_CONTROL_CODE, controlCode); 
  47.             //播放进度 
  48.             if (controlCode == ControlCode.SYNC_VIDEO_PROCESS.getCode()) { 
  49.                 intent.setParam(Constants.KEY_CONTROL_VIDEO_TIME, 
  50.                         String.valueOf(value.get(RemoteConstant.REMOTE_KEY_VIDEO_TOTAL_TIME))); 
  51.                 intent.setParam(Constants.KEY_CONTROL_VIDEO_PROGRESS, 
  52.                         String.valueOf(value.get(RemoteConstant.REMOTE_KEY_VIDEO_CURRENT_PROGRESS))); 
  53.             //播放状态 
  54.             } else if (controlCode == ControlCode.SYNC_VIDEO_STATUS.getCode()) { 
  55.                 intent.setParam(Constants.KEY_CONTROL_VIDEO_PLAYBACK_STATUS, 
  56.                         String.valueOf(value.get(RemoteConstant.REMOTE_KEY_VIDEO_CURRENT_PLAYBACK_STATUS))); 
  57.  
  58.             //播放音量 
  59.             } else { 
  60.                 intent.setParam(Constants.KEY_CONTROL_VIDEO_VOLUME, 
  61.                         String.valueOf(value.get(RemoteConstant.REMOTE_KEY_VIDEO_CURRENT_VOLUME))); 
  62.             } 
  63.             CommonEventData eventData = new CommonEventData(intent); 
  64.  
  65.             //发布事件 
  66.             CommonEventManager.publishCommonEvent(eventData); 
  67.         } catch (RemoteException e) { 
  68.             HiLog.error(LABEL, "publishCommonEvent occur exception."); 
  69.         } 
  70.     } 

2.TV端

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

播放器组件VideoPlayerView

  1. <?xml version="1.0" encoding="utf-8"?> 
  2. <StackLayout 
  3.     xmlns:ohos="http://schemas.huawei.com/res/ohos" 
  4.     ohos:id="$+id:root_layout" 
  5.     ohos:height="match_parent" 
  6.     ohos:width="match_parent" 
  7.     ohos:background_element="#FFFFFFFF" 
  8.     ohos:orientation="vertical"
  9.  
  10.     <com.buty.distributedvideoplayer.player.ui.widget.media.VideoPlayerView 
  11.         ohos:id="$+id:video_view" 
  12.         ohos:height="match_parent" 
  13.         ohos:width="match_parent"/> 
  14.  
  15. </StackLayot> 

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

  1. <?xml version="1.0" encoding="utf-8"?> 
  2. <!--Time is above the progress bar--> 
  3. <DependentLayout 
  4.     xmlns:ohos="http://schemas.huawei.com/res/ohos" 
  5.     ohos:height="54vp" 
  6.     ohos:width="match_parent" 
  7.     ohos:orientation="horizontal"
  8.  
  9.     <Text 
  10.         ohos:id="$+id:current_time" 
  11.         ohos:height="match_content" 
  12.         ohos:width="match_content" 
  13.         ohos:above="$id:seek_bar" 
  14.         ohos:align_parent_start="true" 
  15.         ohos:start_margin="12vp" 
  16.         ohos:text_color="white" 
  17.         ohos:text_size="10fp"/> 
  18.  
  19.     <Slider 
  20.         ohos:id="$+id:seek_bar" 
  21.         ohos:height="match_content" 
  22.         ohos:width="match_parent" 
  23.         ohos:background_instruct_element="$color:seek_bar_background_instruct_color" 
  24.         ohos:center_in_parent="true" 
  25.         ohos:progress_element="$color:seek_bar_progress_color" 
  26.         ohos:thumb_element="$graphic:hm_sample_slider_thumb" 
  27.         ohos:vice_progress_element="$color:seek_bar_vice_progress_color" 
  28.         /> 
  29.  
  30.     <Text 
  31.         ohos:id="$+id:end_time" 
  32.         ohos:height="match_content" 
  33.         ohos:width="match_content" 
  34.         ohos:above="$id:seek_bar" 
  35.         ohos:align_parent_end="true" 
  36.         ohos:end_margin="12vp" 
  37.         ohos:text_color="white" 
  38.         ohos:text_size="10fp"/> 
  39. </DependentLayout> 

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

分为两部分,

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

  1. /** 
  2.  * 远程设备的代理,来源commonlib 
  3.  */ 
  4. private final MyRemote remote = new MyRemote(RemoteConstant.REQUEST_CONTROL_REMOTE_DEVICE); 
  5.  
  6. @Override 
  7. protected IRemoteObject onConnect(Intent intent) { 
  8.     HiLog.debug(LABEL, "onConnect"); 
  9.     super.onConnect(intent); 
  10.     //返回代理对象 
  11.     return remote.asObject(); 

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

  1. /** 
  2.  * 发送事件通知 VideoPlayAbilitySlice 
  3.  * @param controlCode 控制码 
  4.  * @param value 
  5.  */ 
  6. private void sendEvent(int controlCode, Map<?, ?> value) { 
  7.     HiLog.debug(LABEL, "sendEvent:"+controlCode+","+value.toString()); 
  8.     try { 
  9.         //意图 
  10.         Intent intent = new Intent(); 
  11.         //TV控制事件操作 
  12.         Operation operation = new Intent.OperationBuilder() 
  13.                 .withAction(Constants.TV_CONTROL_EVENT) 
  14.                 .build(); 
  15.         intent.setOperation(operation); 
  16.         //设置控制参数 
  17.         intent.setParam(Constants.KEY_CONTROL_CODE, controlCode); 
  18.         intent.setParam(Constants.KEY_CONTROL_VALUE, (String) value.get(RemoteConstant.REMOTE_KEY_CONTROL_VALUE)); 
  19.         //封装时间数据 
  20.         CommonEventData eventData = new CommonEventData(intent); 
  21.         //通用事件管理器,发布事件 
  22.         CommonEventManager.publishCommonEvent(eventData); 
  23.  
  24.     } catch (RemoteException e) { 
  25.         HiLog.error(LABEL, "publishCommonEvent occur exception."); 
  26.     } 

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

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

  1. //连接的phone设备 
  2. connectRemoteDevice( 
  3.         //从意图中获取远端phone设备ID 
  4.         intent.getStringParam(RemoteConstant.INTENT_PARAM_REMOTE_DEVICE_ID)); 
  5.  
  6. /** 
  7.  * 连接远端phone设备的同步服务 
  8.  * @param deviceId 
  9.  */ 
  10. private void connectRemoteDevice(String deviceId) { 
  11.     HiLog.debug(LABEL,"connectRemoteDevice:"+deviceId); 
  12.     Intent connectPaIntent = new Intent(); 
  13.     Operation operation = 
  14.             new Intent.OperationBuilder() 
  15.                     .withDeviceId(deviceId) 
  16.                     .withBundleName(getBundleName()) 
  17.                     .withAbilityName(REMOTE_PHONE_ABILITY) 
  18.                     .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE) 
  19.                     .build(); 
  20.     connectPaIntent.setOperation(operation); 
  21.  
  22.     connectAbility(connectPaIntent, connection); 
  23.  
  24. // Creating a Connection Callback Instance 
  25. private final IAbilityConnection connection = 
  26. new IAbilityConnection() { 
  27.     // Callback for connecting to a service 
  28.     @Override 
  29.     public void onAbilityConnectDone(ElementName elementName, IRemoteObject iRemoteObject, int resultCode) { 
  30.         myProxy = new MyRemoteProxy(iRemoteObject); 
  31.     } 
  32.  
  33.     // Callback for disconnecting from the service 
  34.     @Override 
  35.     public void onAbilityDisconnectDone(ElementName elementName, int resultCode) { 
  36.         disconnectAbility(this); 
  37.     } 
  38. }; 

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

  1. //注册远端控制回调 
  2. videoBox.registerRemoteControlCallback(remoteControlCallback); 
  3.  
  4.  
  5.  
  6. /** 
  7.  * 远端控制回调,来源commonlib,用于同步进度条进度/播放状态/音量 
  8.  */ 
  9. private VideoPlayerView.RemoteControlCallback remoteControlCallback = 
  10.     new VideoPlayerView.RemoteControlCallback() { 
  11.         @Override 
  12.         //进度条变化 
  13.         public void onProgressChanged(long totalTime, int progress) { 
  14.             HiLog.debug(LABEL,"onProgressChanged,myProxy:"+myProxy); 
  15.             if (myProxy != null) { 
  16.                 Map<String, String> progressValue = new HashMap<>(); 
  17.                 //设置总时间和进度值 
  18.                 progressValue.put(RemoteConstant.REMOTE_KEY_VIDEO_TOTAL_TIME, String.valueOf(totalTime)); 
  19.                 progressValue.put(RemoteConstant.REMOTE_KEY_VIDEO_CURRENT_PROGRESS, String.valueOf(progress)); 
  20.  
  21.                 //同步进度之给手机端的控制面板 
  22.                 myProxy.sendDataToRemote( 
  23.                         RemoteConstant.REQUEST_SYNC_VIDEO_STATUS, 
  24.                         ControlCode.SYNC_VIDEO_PROCESS.getCode(), 
  25.                         progressValue); 
  26.             } 
  27.         } 
  28.  
  29.         @Override 
  30.         //播放状态变化 
  31.         public void onPlayingStatusChanged(boolean isPlaying) { 
  32.             if (myProxy != null) { 
  33.                 Map<String, String> videoStatusMap = new HashMap<>(); 
  34.                 videoStatusMap.put( 
  35.                         RemoteConstant.REMOTE_KEY_VIDEO_CURRENT_PLAYBACK_STATUS, String.valueOf(isPlaying)); 
  36.                 HiLog.debug(LABEL, "isPlaying = " + String.valueOf(isPlaying)); 
  37.                 myProxy.sendDataToRemote( 
  38.                         RemoteConstant.REQUEST_SYNC_VIDEO_STATUS, 
  39.                         ControlCode.SYNC_VIDEO_STATUS.getCode(), 
  40.                         videoStatusMap); 
  41.             } 
  42.         } 
  43.  
  44.         @Override 
  45.         //音量变化 
  46.         public void onVolumeChanged(int volume) { 
  47.             if (myProxy != null) { 
  48.                 Map<String, String> volumeMap = new HashMap<>(); 
  49.                 volumeMap.put(RemoteConstant.REMOTE_KEY_VIDEO_CURRENT_VOLUME, String.valueOf(volume)); 
  50.                 myProxy.sendDataToRemote( 
  51.                         RemoteConstant.REQUEST_SYNC_VIDEO_STATUS, 
  52.                         ControlCode.SYNC_VIDEO_VOLUME.getCode(), 
  53.                         volumeMap); 
  54.             } 
  55.         } 
  56. }; 

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

  1. /** 
  2.  * 通用事件订阅 
  3.  */ 
  4. private void subscribe() { 
  5.     HiLog.debug(LABEL,"subscribe"); 
  6.     MatchingSkills matchingSkills = new MatchingSkills(); 
  7.     matchingSkills.addEvent(Constants.TV_CONTROL_EVENT); 
  8.     matchingSkills.addEvent(CommonEventSupport.COMMON_EVENT_SCREEN_ON); 
  9.     CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(matchingSkills); 
  10.     //订阅者 
  11.     tvSubscriber = new MyCommonEventSubscriber(subscribeInfo); 
  12.     try { 
  13.         CommonEventManager.subscribeCommonEvent(tvSubscriber); 
  14.     } catch (RemoteException e) { 
  15.         HiLog.error(LABEL, "subscribeCommonEvent occur exception."); 
  16.     } 
  17.  
  18. /** 
  19.  * 取消订阅 
  20.  */ 
  21. private void unSubscribe() { 
  22.     HiLog.debug(LABEL,"subscribe"); 
  23.     try { 
  24.         CommonEventManager.unsubscribeCommonEvent(tvSubscriber); 
  25.     } catch (RemoteException e) { 
  26.         HiLog.error(LABEL, "unSubscribe Exception"); 
  27.     } 
  28.  
  29. /** 
  30.  * 视频控制服务(VideoControlServiceAbility)事件订阅者 
  31.  */ 
  32. class MyCommonEventSubscriber extends CommonEventSubscriber { 
  33.     MyCommonEventSubscriber(CommonEventSubscribeInfo info) { 
  34.         super(info); 
  35.     } 
  36.  
  37.     @Override 
  38.     public void onReceiveEvent(CommonEventData commonEventData) { 
  39.         HiLog.info(LABEL, "onReceiveEvent....."); 
  40.  
  41.         Intent intent = commonEventData.getIntent(); 
  42.         int controlCode = intent.getIntParam(Constants.KEY_CONTROL_CODE, 0); 
  43.         String extras = intent.getStringParam(Constants.KEY_CONTROL_VALUE); 
  44.  
  45.         //播放or暂停 
  46.         if (controlCode == ControlCode.PLAY.getCode()) { 
  47.             if (videoBox.isPlaying()) { 
  48.                 videoBox.pause(); 
  49.             } else if (!videoBox.isPlaying() && !needResumeStatus) { 
  50.                 videoBox.start(); 
  51.             } else { 
  52.                 HiLog.error(LABEL, "Ignoring the case with player status"); 
  53.             } 
  54.             //拖动播放进度 
  55.         } else if (controlCode == ControlCode.SEEK.getCode()) { 
  56.             videoBox.seekTo(videoBox.getDuration() * Integer.parseInt(extras) / 100); 
  57.             //快进 
  58.         } else if (controlCode == ControlCode.FORWARD.getCode()) { 
  59.             videoBox.seekTo(videoBox.getCurrentPosition() + Constants.REWIND_STEP); 
  60.             //快退 
  61.         } else if (controlCode == ControlCode.BACKWARD.getCode()) { 
  62.             videoBox.seekTo(videoBox.getCurrentPosition() - Constants.REWIND_STEP); 
  63.             //音量加 
  64.         } else if (controlCode == ControlCode.VOLUME_ADD.getCode()) { 
  65.             videoBox.setVolume(Constants.VOLUME_STEP); 
  66.             //音量减 
  67.         } else if (controlCode == ControlCode.VOLUME_REDUCED.getCode()) { 
  68.             videoBox.setVolume(-Constants.VOLUME_STEP); 
  69.             //切换播放速度 
  70.         } else if (controlCode == ControlCode.SWITCH_SPEED.getCode()) { 
  71.             videoBox.setPlaybackSpeed(Float.parseFloat(extras)); 
  72.             //切换视频源,例如高清 
  73.         } else if (controlCode == ControlCode.SWITCH_RESOLUTION.getCode()) { 
  74.             long currentPosition = videoBox.getCurrentPosition(); 
  75.             int resolutionIndex = Integer.parseInt(extras); 
  76.             VideoInfo videoInfo = videoInfoService.getVideoInfoByIndex(currentPlayingIndex); 
  77.             videoBox.pause(); 
  78.  
  79.             //设置新的播放URL 
  80.             videoBox.setVideoPath(videoInfo.getResolutions().get(resolutionIndex).getUrl()); 
  81.             //调整到原播放位置 
  82.             videoBox.setPlayerOnPreparedListener( 
  83.                     () -> { 
  84.                         videoBox.seekTo(currentPosition); 
  85.                         videoBox.start(); 
  86.                     }); 
  87.             //切换视频 
  88.         } else if (controlCode == ControlCode.SWITCH_VIDEO.getCode()) { 
  89.             videoBox.pause(); 
  90.             currentPlayingIndex = Integer.parseInt(extras); 
  91.             VideoInfo videoInfo = videoInfoService.getVideoInfoByIndex(currentPlayingIndex); 
  92.             videoBox.setVideoPathAndTitle(videoInfo.getResolutions().get(0).getUrl(), videoInfo.getVideoDesc()); 
  93.             videoBox.setPlayerOnPreparedListener(() -> videoBox.start()); 
  94.  
  95.             //停止连接 
  96.         } else if (controlCode == ControlCode.STOP_CONNECTION.getCode()) { 
  97.             terminate(); 
  98.         } else { 
  99.             HiLog.error(LABEL, "Ignoring the case with control code"); 
  100.         } 
  101.     } 

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

两端涉及的权限如下:

  1.     "name""ohos.permission.INTERNET"
  2.     "reason"""
  3.     "usedScene": { 
  4.       "ability": [ 
  5.         "VideoPlayAbilitySlice" 
  6.       ], 
  7.       "when""inuse" 
  8.     } 
  9.   }, 
  10.   { 
  11.     "name""ohos.permission.DISTRIBUTED_DATASYNC"
  12.     "reason"""
  13.     "usedScene": { 
  14.       "ability": [ 
  15.         "VideoPlayAbilitySlice" 
  16.       ], 
  17.       "when""inuse" 
  18.     } 
  19.   }, 
  20.   { 
  21.     "name""ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE"
  22.     "reason"""
  23.     "usedScene": { 
  24.       "ability": [ 
  25.         "VideoPlayAbilitySlice" 
  26.       ], 
  27.       "when""inuse" 
  28.     } 
  29.   }, 
  30.   { 
  31.     "name""ohos.permission.GET_DISTRIBUTED_DEVICE_INFO"
  32.     "reason"""
  33.     "usedScene": { 
  34.       "ability": [ 
  35.         "VideoPlayAbilitySlice" 
  36.       ], 
  37.       "when""inuse" 
  38.     } 
  39.   }, 
  40.   { 
  41.     "name""ohos.permission.GET_BUNDLE_INFO"
  42.     "reason"""
  43.     "usedScene": { 
  44.       "ability": [ 
  45.         "VideoPlayAbilitySlice" 
  46.       ], 
  47.       "when""inuse" 
  48.     } 
  49.   }, 
  50.   { 
  51.     "name""ohos.permission.KEEP_BACKGROUND_RUNNING"
  52.     "reason"""
  53.     "usedScene": { 
  54.       "ability": [ 
  55.         "VideoPlayAbilitySlice" 
  56.       ], 
  57.       "when""inuse" 
  58.     } 
  59.   } 

回顾总结

手机端控制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

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

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

https://harmonyos.51cto.com

 

责任编辑:jianghua 来源: 鸿蒙社区
相关推荐

2021-10-19 14:27:07

鸿蒙HarmonyOS应用

2021-08-24 15:13:06

鸿蒙HarmonyOS应用

2022-11-08 15:48:35

应用开发音乐播放器

2022-08-16 17:37:06

视频播放器鸿蒙

2023-03-28 09:44:02

开发应用鸿蒙

2011-07-20 16:21:20

iPhone 视频 播放器

2015-05-21 15:25:42

VLC播放器

2022-06-21 14:41:38

播放器适配西瓜视频

2022-11-12 08:26:04

VLC视频播放器裁剪视频

2023-03-29 09:37:49

视频播放器应用鸿蒙

2023-03-28 09:38:34

开发应用鸿蒙

2023-03-06 16:20:08

视频播放器VLC

2011-06-13 09:33:04

2018-05-25 14:37:58

2015-09-01 16:48:44

ios暴风视频播放器

2023-03-29 09:32:15

视频播放器应用鸿蒙

2023-08-26 19:07:40

VLC旋转视频

2019-10-10 09:16:34

Zookeeper架构分布式

2017-09-01 05:35:58

分布式计算存储

2023-05-29 14:07:00

Zuul网关系统
点赞
收藏

51CTO技术栈公众号