介绍
上一期我们实现了视频的播放功能,播放列表还有评论功能.这一期,我们来看一下手机端是如何实现一个对远端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