DistributedVideoPlayer 分布式视频播放器(一) 原创 精华

Buty9147
发布于 2021-10-18 04:06
浏览
10收藏

@toc

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

介绍

本示例是在官方Video Play Ability 模板基础上做了扩展开发,官方模板提供基本的视频播放功能,并允许您在手机和电视之间传输视频.
应用分为手机端(entry)和TV端(entrytv),以及一个依赖模块(commonlib).
在示例的基础之上,手机端增加了视频播放列表功能,以及播放详情页和评论功能;手机端播放的视频可以流转到TV端,并实现远端遥控的功能。
内容比较多,会分两期给大家讲解,本期文章主要讲解的内容是手机端部分:
1.实现一个视频播放器 2.实现一个播放列表 3.实现一个评论功能.
[本文正在参与优质创作者激励]

效果展示

DistributedVideoPlayer 分布式视频播放器(一)-鸿蒙开发者社区

搭建环境

安装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  
│              │      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

资源文件

└─resources
    ├─base
    │  ├─element
    │  │      color.json
    │  │      float.json
    │  │      strarray.json
    │  │      string.json
    │  │
    │  ├─graphic
    │  │      background_ability_control_bg.xml          
    │  │      background_ability_control_middle.xml
    │  │      background_ability_control_ok.xml
    │  │      background_ability_devices.xml
    │  │      background_ability_episodes.xml
    │  │      background_button_click.xml
    │  │      background_button_clicked.xml
    │  │      background_episodes_item.xml
    │  │      background_episodes_quality.xml
    │  │      background_episodes_trailer.xml
    │  │      background_slide_thumb.xml
    │  │      background_switch_checked.xml
    │  │      background_switch_empty.xml
    │  │      background_switch_thumb.xml
    │  │      background_switch_track.xml
    │  │      list_divider.xml
    │  │      shape_slider_thumb.xml
    │  │
    │  ├─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
    │  │
    │  ├─media
    │  │      comments.png
    │  │      great.png
    │  │      icon.png
    │  │      ic_anthology.png
    │  │
    │  └─profile
    ├─en
    │  └─element
    │          string.json
    │
    ├─rawfile
    │      videos.json                                      #模拟数据JSON文件
    │
    └─zh
        └─element
                string.json

实现步骤

1.实现一个视频播放器

引入对commonlib的依赖后,实现一个视频播放器就很容易了.
entry的build.gradle 增加对commonlib的依赖

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar', '*.har'])
    testImplementation 'junit:junit:4.13'
    ohosTestImplementation 'com.huawei.ohos.testkit:runner:1.0.0.200'
    //引用commonlib 依赖
    implementation project(path: ':commonlib')
}

1.1.页面布局 hm_sample_ability_video_comments.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="250vp"
        ohos:width="match_parent"/>
...
</StackLayout>

1.2.Java代码

获取视频组件对象后,只需要2步就可以了: 1.设置视频资源路径和描述; 2.设置播放器核心组件和自定义组件.几行关键代码 VideoPlayAbilitySlice.java

/**
 * 初始化播放器
 */
private void initPlayer() {
    HiLog.debug(LABEL, "initPlayer");
    //自定义视频播放视图组件
    player = (VideoPlayerView) findComponentById(ResourceTable.Id_video_view);

    if (player != null) {
        //获取视频信息服务
        videoService = new VideoInfoService(getContext());
        //获取当前播放视频的路径
        String path = videoService
                .getVideoInfoByIndex(currentPlayingIndex)
                .getResolutions()
                .get(currentPlayingResolutionIndex)
                .getUrl();
        //视频描述
        String videoDesc = videoService.getVideoInfoByIndex(currentPlayingIndex).getVideoDesc();
        HiLog.debug(LABEL, "videoDesc = " + videoDesc + " path = " + path);

        if (path != null) {
            //设置路径和描述
            player.setVideoPathAndTitle(path, videoDesc);
            //视频准备完毕就播放并设置播放进度条
            player.setPlayerOnPreparedListener(
                    () -> {
                        player.start();
                        //设置播放位置
                        player.seekTo(currentPlayingPosition);
                    });
            //双击播放或暂停
            player.setDoubleClickedListener(
                    component -> {
                        //是否在控制TV端
                        if (remoteController != null && remoteController.isShown()) {
                            return;
                        }
                        HiLog.debug(LABEL, "VideoPlayView double-click event");
                        if (player.isPlaying()) {
                            player.pause();
                        } else {
                            player.start();
                        }
                    });
            //监听播放错误
            player.setErrorListener(
                    (errorType, errorCode) -> {
                        ToastDialog toast = new ToastDialog(getContext());
                        switch (errorType) {
                            case HmPlayerAdapter.ERROR_LOADING_RESOURCE:
                                toast.setText(
                                        AppUtil.getStringResource(
                                                getContext(), ResourceTable.String_media_file_loading_error));
                                break;
                            case HmPlayerAdapter.ERROR_INVALID_OPERATION:
                                toast.setText(
                                        AppUtil.getStringResource(
                                                getContext(), ResourceTable.String_invalid_operation));
                                break;
                            default:
                                toast.setText(
                                        AppUtil.getStringResource(
                                                getContext(), ResourceTable.String_undefined_error_type));
                                break;
                        }
                        getUITaskDispatcher().asyncDispatch(toast::show);
                    });
        }
        //添加核心组件,播放时间进度滑块
        addCoreComponent();
        //添加自定义组件
        addCustomComponent();
        HiLog.debug(LABEL, "initPlayer finish");
    }
}

2.实现一个视频播放列表功能

2.1.页面布局 ability_main.xml

使用了DirectionalLayout布局组件,还有ScrollView和ListContainer组件

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:id="$+id:ability_main_root"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:orientation="vertical">

    <DirectionalLayout
        ohos:id="$+id:ability_main_titlebar"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:background_element="#B0B0B0"
        ohos:orientation="horizontal"
        ohos:padding="10vp">

        <DirectionalLayout
            ohos:height="match_parent"
            ohos:width="match_content"
            ohos:weight="1">

            <Text
                ohos:id="$+id:tag_favorite"
                ohos:height="match_content"
                ohos:width="match_parent"
                ohos:text="关注"
                ohos:text_size="$float:normal_text_size_15"
                />
        </DirectionalLayout>

        <DirectionalLayout
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:weight="1">

            <Text
                ohos:id="$+id:tag_support"
                ohos:height="match_content"
                ohos:width="match_content"
                ohos:text="推荐"
                ohos:text_size="$float:normal_text_size_15"
                />
        </DirectionalLayout>

        <DirectionalLayout
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:weight="1">

            <Text
                ohos:id="$+id:tag_movie"
                ohos:height="match_content"
                ohos:width="match_content"
                ohos:element_end="$id:favorite"
                ohos:text="电影"
                ohos:text_color="#1C6AE9"
                ohos:text_size="$float:normal_text_size_15"
                ohos:text_weight="600"/>

            <Component
                ohos:id="$+id:device_item_divider"
                ohos:height="2vp"
                ohos:width="30vp"
                ohos:background_element="$graphic:list_divider"/>
        </DirectionalLayout>

        <DirectionalLayout
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:weight="1">

            <Text
                ohos:id="$+id:tag_live"
                ohos:height="match_content"
                ohos:width="match_parent"
                ohos:text="直播"
                ohos:text_size="$float:normal_text_size_15"
                />
        </DirectionalLayout>

        <DirectionalLayout
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:weight="1">

            <Text
                ohos:id="$+id:tag_tv"
                ohos:height="match_content"
                ohos:width="match_parent"
                ohos:text="电视"
                ohos:text_size="$float:normal_text_size_15"
                />
        </DirectionalLayout>

    </DirectionalLayout>

    <ScrollView
        ohos:height="match_parent"
        ohos:width="match_parent"
        ohos:id="$+id:video_list_scroll" >

        <ListContainer
            ohos:id="$+id:videos_container"
            ohos:height="match_parent"
            ohos:width="match_parent">

        </ListContainer>
    </ScrollView>
</DirectionalLayout>

2.2.Java代码

申请用户敏感权限授权 MainAbility.java

/**
 * Entry to the main interface of the program
 */
public class MainAbility extends Ability {
    private static final int REQUEST_CODE = 1;

    //读写媒体权限
    private final String[] permissionLists
            = new String[]{"ohos.permission.READ_MEDIA", "ohos.permission.WRITE_MEDIA"};

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_main);
        super.setMainRoute(MainAbilitySlice.class.getName());

        //申请授权
        verifyPermissions();
    }
    private void verifyPermissions() {
        for (String permissionList : permissionLists) {
            int result = verifySelfPermission(permissionList);
            if (result != IBundleManager.PERMISSION_GRANTED) {
                requestPermissionsFromUser(permissionLists, REQUEST_CODE);
            }
        }
    }
...

通过VideoInfoService读取videos.json文件初始化视频容器列表MainAbilitySlice.java

public class MainAbilitySlice extends AbilitySlice {
    public static final HiLogLabel LABEL = new HiLogLabel(0, 0, "=>MainAbilitySlice");
    private VideoInfoService videoService;

    @Override
    protected void onStart(Intent intent) {
        super.onStart(intent);
        //加载视频播放器页面
        super.setUIContent(ResourceTable.Layout_ability_main);
        initVideoContainer();

    }

    /**
     * 模拟数据
     * 初始化视频容器列表
     */
    private void initVideoContainer() {
        HiLog.debug(LABEL, "initVideoContainer");
        List<VideoModel> videos = new ArrayList<>();
        //获取视频信息服务
        videoService = new VideoInfoService(getContext());

        for (int i = 0; i < 7; i++) {
            VideoModel video = new VideoModel();
            video.setComments(new Random().nextInt(1000));
            video.setFavorites(new Random().nextInt(1000));
            video.setGreats(new Random().nextInt(10000));
            VideoInfo videoInfo = videoService.getVideoInfoByIndex(i);
            videoInfo.setIndex(i);
            video.setVideoInfo(videoInfo);
            videos.add(video);
        }

        ListContainer listContainer = (ListContainer) findComponentById(ResourceTable.Id_videos_container);
        //容器绑定数据提供程序
        VideoItemProvider provider = new VideoItemProvider(this, videos, this);
        listContainer.setItemProvider(provider);
    }
}

视频容器列表数据提供程序 VideoItemProvider.java

public class VideoItemProvider extends BaseItemProvider {
    private static final HiLogLabel LABEL = new HiLogLabel(0, 0, "=>VideoItemProvider");
    private final Context context;
    private final List<VideoModel> list;
    private AbilitySlice abilitySlice;
    //当前播放视频分辨率索引
    private int currentPlayingResolutionIndex = 0;

    /**
     * Initialization
     */
    public VideoItemProvider(Context context, List<VideoModel> list, AbilitySlice abilitySlice) {
        HiLog.debug(LABEL, "VideoItemProvider");
        this.context = context;
        this.list = list;
        this.abilitySlice = abilitySlice;
    }

    public Context getContext() {
        return context;
    }

    @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 VideoModel();
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public Component getComponent(int position, Component convertComponent, ComponentContainer componentContainer) {
        HiLog.debug(LABEL, "getComponent:" + convertComponent + "," + componentContainer);
        final Component cpt;
        final VideoPlayerView player;

        VideoModel item = list.get(position);

        //初次获取组件
        if (convertComponent == null) {
            cpt = LayoutScatter.getInstance(context).parse(ResourceTable.Layout_hm_sample_ability_video_box, componentContainer, false);
            player = (VideoPlayerView) cpt.findComponentById(ResourceTable.Id_video_view);
            //初始化播放器组件
            initPlayer(player,item.getVideoInfo());
        } else {
            //ScrollView滑动时也会调用getComponent方法,
            cpt = convertComponent;
            player = (VideoPlayerView) cpt.findComponentById(ResourceTable.Id_video_view);
        }

        //设置其他组件的值
        Text text_favorites = (Text) cpt.findComponentById(ResourceTable.Id_text_favorites);
        Text text_comments = (Text) cpt.findComponentById(ResourceTable.Id_text_comments);
        Text text_greats = (Text) cpt.findComponentById(ResourceTable.Id_text_greats);
        //注意setText的参数类型是字符串
        text_favorites.setText(item.getFavorites() + "");
        text_comments.setText(item.getComments() + "");
        text_greats.setText(item.getGreats() + "");
        //点击评论图标,停止当前播放器,打开VideoPlayAbility的Slice页面
        Image commentImage = (Image) cpt.findComponentById(ResourceTable.Id_image_comment);
        commentImage.setClickedListener(component -> {
            //停止播放,列表页面的播放器
            player.stopPlayback();
            //打开带有评论的播放页面
            startVideoPlayDetail(item,player);
        });
        return cpt;
    }

    /**
     * 初始化播放器
     */
    private void initPlayer(VideoPlayerView player, VideoInfo videoInfo) {
        HiLog.debug(LABEL, "initPlayer");
        if (player != null) {
            //视频路径
            String path = videoInfo
                    .getResolutions()
                    .get(currentPlayingResolutionIndex)
                    .getUrl();
            HiLog.debug(LABEL, "path:" + path);

            //视频描述
            String videoDesc = videoInfo.getVideoDesc();
            HiLog.debug(LABEL, "videoDesc  = " + videoDesc + " path = " + path);

            if(path!=null) {
                //设置路径和名称
                player.setVideoPathAndTitle(path, videoDesc);
                //双击播放或暂停
                player.setDoubleClickedListener(
                        component -> {
                            HiLog.debug(LABEL, "VideoPlayView double-click event");
                            if (player.isPlaying()) {
                                player.pause();
                            } else {
                                player.start();
                            }
                        });
                //监听播放错误
                player.setErrorListener(
                        (errorType, errorCode) -> {
                            ToastDialog toast = new ToastDialog(getContext());
                            switch (errorType) {
                                case HmPlayerAdapter.ERROR_LOADING_RESOURCE:
                                    toast.setText(
                                            AppUtil.getStringResource(
                                                    getContext(), ResourceTable.String_media_file_loading_error));
                                    break;
                                case HmPlayerAdapter.ERROR_INVALID_OPERATION:
                                    toast.setText(
                                            AppUtil.getStringResource(
                                                    getContext(), ResourceTable.String_invalid_operation));
                                    break;
                                default:
                                    toast.setText(
                                            AppUtil.getStringResource(
                                                    getContext(), ResourceTable.String_undefined_error_type));
                                    break;
                            }
                            abilitySlice.getUITaskDispatcher().asyncDispatch(toast::show);
                        });
            }
            //添加核心组件,播放时间进度滑块
            addCoreComponent(player);
            //添加自定义组件
            HiLog.debug(LABEL, "initPlayer finish");
        }
    }

    /**
     * 添加播放按钮,剧集,进度条等核心组件
     * Adding core component like playback button and seek bar
     */
    private void addCoreComponent(VideoPlayerView player) {
        HiLog.debug(LABEL, "addCoreComponent");
        //添加播放按钮组件
        player.addPlaybackButton(new VideoPlayerPlaybackButton(getContext()), VideoBoxArea.BOTTOM);
        //添加播放进度条组件
        player.addSeekBar(
                new VideoPlayerSlider(getContext()),
                VideoBoxArea.BOTTOM,
                (int) AppUtil.getFloatResource(getContext(), ResourceTable.Float_normal_margin_24));
    }

    /**
     * 打开视频播放详情页
     */
    private void startVideoPlayDetail(VideoModel item,VideoPlayerView player) {
        HiLog.debug(LABEL, "startVideoPlayDetail ");
        //启动评论播放页面
        Intent intentService = new Intent();
        //播放视频评论数
        intentService.setParam("comments",String.valueOf(item.getComments()));

        VideoInfo videoInfo=item.getVideoInfo();

        if(videoInfo!=null) {
            //播放视频在播放列表中的索引
            intentService.setParam("currentPlayingIndex", videoInfo.getIndex());
            //播放视频的分辨率索引
            intentService.setParam("currentPlayingResolutionIndex", currentPlayingResolutionIndex);
            //当前视频播放的位置
            intentService.setParam(RemoteConstant.INTENT_PARAM_REMOTE_START_POSITION,(int)player.getCurrentPosition());
            HiLog.debug(LABEL,RemoteConstant.INTENT_PARAM_REMOTE_START_POSITION+":"+player.getCurrentPosition());
        }
        Operation operation =
                new Intent.OperationBuilder()
                        .withDeviceId("")
                        .withBundleName(abilitySlice.getBundleName())
                        .withAbilityName(VideoPlayAbility.class)
                        .build();
        intentService.setOperation(operation);
        abilitySlice.startAbility(intentService);
    }
}

3.实现一个评论功能

3.1.页面布局,评论列表布局页 hm_sample_ability_video_comments.xml

使用了StackLayout,DependentLayout布局组件和ListContainer,TextField,Text 组件

...
    <!-- 工具栏 -->
    <DependentLayout
        ohos:id="$+id:comments_bar"
        ohos:height="40vp"
        ohos:width="match_parent"
        ohos:background_element="#FFDDDADA"
        ohos:layout_alignment="top"
        ohos:top_margin="250vp">

        <DirectionalLayout
            ohos:id="$+id:favorite"
            ohos:height="match_parent"
            ohos:width="match_content"
            ohos:align_parent_left="true"
            ohos:left_margin="10vp"
            ohos:orientation="horizontal"
            ohos:padding="4vp">

            <Text
                ohos:id="$+id:text_favorites"
                ohos:height="match_parent"
                ohos:width="match_parent"
                ohos:left_padding="5vp"
                ohos:text="简介"
                ohos:text_size="16fp"
                ohos:text_weight="600">

            </Text>
        </DirectionalLayout>

        <DirectionalLayout
            ohos:id="$+id:comment"
            ohos:height="match_parent"
            ohos:width="match_content"
            ohos:end_of="$id:favorite"
            ohos:left_margin="20vp"
            ohos:orientation="horizontal"
            ohos:padding="4vp">

            <Text
                ohos:id="$+id:text_comment"
                ohos:height="match_parent"
                ohos:width="match_content"
                ohos:left_padding="5vp"
                ohos:text="评论"
                ohos:text_weight="600"
                ohos:text_size="16fp">
            </Text>

            <Text
                ohos:id="$+id:text_comments"
                ohos:height="match_parent"
                ohos:width="match_content"
                ohos:left_padding="5vp"
                ohos:text="1161"
                ohos:text_weight="600"
                ohos:text_size="16fp">
            </Text>
        </DirectionalLayout>

    </DependentLayout>

    <!-- 评论列表 -->
    <DependentLayout
        ohos:id="$+id:comments_view"
        ohos:height="match_parent"
        ohos:width="match_parent"
        ohos:background_element="#FFFDFFFF"
        ohos:top_margin="290vp">

        <ListContainer
            ohos:id="$+id:comments_container"
            ohos:height="match_parent"
            ohos:width="match_parent"
            ohos:layout_alignment="horizontal_center"
            ohos:orientation="vertical"/>

    </DependentLayout>

    <!-- 评论对话框 -->
    <DependentLayout
        ohos:id="$+id:comments_dialog"
        ohos:height="70vp"
        ohos:width="match_parent"
        ohos:background_element="#FFF4F4F8"
        ohos:layout_alignment="bottom"
        ohos:padding="5vp">

        <TextField
            ohos:id="$+id:comment_tf"
            ohos:height="match_parent"
            ohos:width="match_parent"
            ohos:hint="发一条友好的评论吧"
            ohos:right_padding="60vp"
            ohos:text_size="16vp"></TextField>

        <Text
            ohos:id="$+id:sent_comment"
            ohos:height="match_parent"
            ohos:width="60vp"
            ohos:align_parent_right="true"
            ohos:background_element="#FF7ECCCF"
            ohos:text="发送"
            ohos:input_enter_key_type="enter_key_type_send"
            ohos:text_alignment="center"
            ohos:text_size="16vp">
        </Text>
    </DependentLayout>

3.2.页面布局,单条评论组件布局 comments_item.xml

使用了DependentLayout,DirectionalLayout布局组件和Image,Component,Text 组件

<?xml version="1.0" encoding="utf-8"?>
<DependentLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_content"
    ohos:width="match_parent">

    <Image
        ohos:id="$+id:header_item_icon"
        ohos:height="50vp"
        ohos:width="50vp"
        ohos:end_margin="13vp"
        ohos:left_margin="5vp"
        ohos:image_src="$media:ic_header"
        ohos:scale_mode="stretch"
        ohos:top_margin="13vp"/>

    <DirectionalLayout
        ohos:id="$+id:comment_item_content"
        ohos:height="match_content"
        ohos:width="250vp"
        ohos:bottom_padding="5vp"
        ohos:orientation="vertical"
        ohos:right_of="$id:header_item_icon">

        <Text
            ohos:id="$+id:comment_uname"
            ohos:height="match_content"
            ohos:width="160vp"
            ohos:layout_alignment="vertical_center"
            ohos:padding="5vp"
            ohos:text="我是一只鱼"
            ohos:text_color="#FFA09E9E"
            ohos:text_size="16vp"/>

        <Text
            ohos:id="$+id:comment_content"
            ohos:height="match_content"
            ohos:width="match_parent"
            ohos:layout_alignment="vertical_center"
            ohos:multiple_lines="true"
            ohos:padding="5vp"
            ohos:text="这是一条评论"
            ohos:text_color="$color:default_black_color"
            ohos:text_size="16vp"/>

        <Text
            ohos:id="$+id:comment_date"
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:layout_alignment="vertical_center"
            ohos:padding="5vp"
            ohos:text="2天前"
            ohos:text_color="#FFA09E9E"
            ohos:text_size="16vp"/>

    </DirectionalLayout>

    <DirectionalLayout
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:id="$+id:goods_view"
        ohos:align_parent_right="true"
        ohos:padding="5vp"
        ohos:right_margin="5vp"
        ohos:right_of="$id:comment_item_content">

        <Image
            ohos:id="$+id:good_icon"
            ohos:height="25vp"
            ohos:width="match_parent"
            ohos:image_src="$media:ic_great"
            ohos:scale_mode="stretch"
            ohos:top_margin="13vp"
            />

        <Text
            ohos:id="$+id:comment_goods"
            ohos:height="match_content"
            ohos:width="match_parent"
            ohos:text="42"
            ohos:text_alignment="center"
            ohos:text_color="#FFA09E9E"
            ohos:text_size="12vp"></Text>

    </DirectionalLayout>

    <DirectionalLayout
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:align_bottom="$id:comment_item_content">
        <Component
            ohos:id="$+id:device_item_divider"
            ohos:height="1vp"
            ohos:width="match_parent"
            ohos:background_element="$graphic:list_divider"/>
    </DirectionalLayout>
</DependentLayout>

3.3.Java代码

创建评论列表提供程序类 CommentItemProvider.java

public class CommentItemProvider extends BaseItemProvider {
    private static final HiLogLabel LABEL = new HiLogLabel(0, 0, "=>CommentsItemProvider");
    private final Context context;
    private final List<CommentModel> list;
    private AbilitySlice abilitySlice;

    /**
     * Initialization
     */
    public CommentItemProvider(Context context, List<CommentModel> list, AbilitySlice slice) {
        this.context = context;
        this.list = list;
        this.abilitySlice = slice;
    }

    @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 CommentModel();
    }

    public void addComment(CommentModel comment) {
        if (comment == null) return;

        list.add(0, comment);
        notifyDataSetItemInserted(0);
    }

    @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_comments_item, null, false);
        } else {
            cpt = convertComponent;
        }


        CommentModel item = list.get(position);
        //评论人头像
        Image headerIcon = (Image) cpt.findComponentById(ResourceTable.Id_header_item_icon);
        headerIcon.setPixelMap(ResourceTable.Media_ic_header);

        //评论人名称
        Text uName = (Text) cpt.findComponentById(ResourceTable.Id_comment_uname);
        uName.setText(item.getuName());

        //评论内容
        Text uContent = (Text) cpt.findComponentById(ResourceTable.Id_comment_content);
        uContent.setText(item.getCommentContent());
        //点击事件
        uContent.setClickedListener(component -> {
            DependentLayout commentDialog = (DependentLayout) abilitySlice.findComponentById(ResourceTable.Id_comments_dialog);
            if (commentDialog.getVisibility() == Component.VISIBLE) {
                commentDialog.setVisibility(Component.VISIBLE);
            }
            TextField comment = (TextField) abilitySlice.findComponentById(ResourceTable.Id_comment_tf);
            comment.setText("");
            comment.setHint("回复 " + item.getuName() + ":");
            comment.requestFocus();
        });


        //评论日期
        Text uDate = (Text) cpt.findComponentById(ResourceTable.Id_comment_date);
        uDate.setText(item.getCommentDate());

        //已赞数
        Text goods = (Text) cpt.findComponentById(ResourceTable.Id_comment_goods);
        HiLog.debug(LABEL, "goods:" + item.getCommentGoods());
        goods.setText(item.getCommentGoods() + "");

        //点赞
        Image greatImage = (Image) cpt.findComponentById(ResourceTable.Id_good_icon);
        //点赞加一
        greatImage.setClickedListener(component1 -> {
            greatImage.setPixelMap(ResourceTable.Media_ic_great_red);
            goods.setTextColor(Color.RED);
            goods.setText((item.getCommentGoods() + 1) + "");
        });

        if (position == list.size() - 1) {
            Component divider = cpt.findComponentById(ResourceTable.Id_device_item_divider);
            divider.setVisibility(Component.INVISIBLE);
        }

        return cpt;
    }
}

初始化评论列表和评论功能 VideoPlayAbilitySlice.java

/**
 * 初始化评论列表
 */
private void initComments(AbilitySlice slice) {
    //评论列表,以及设置点击的监听事件、传递数据
    ListContainer listContainer = (ListContainer) findComponentById(ResourceTable.Id_comments_container);
    List<CommentModel> list = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        CommentModel obj = new CommentModel();
        obj.setuId(i + "");
        obj.setuName(getRandomUname());
        obj.setCommentContent(getRandomText());
        obj.setCommentDate("刚刚");
        obj.setCommentGoods(new Random().nextInt(100));
        list.add(obj);
    }

    //容器绑定数据提供程序
    provider = new CommentItemProvider(this, list, slice);
    listContainer.setItemProvider(provider);

    Component sendComment = findComponentById(ResourceTable.Id_sent_comment);
    TextField comment = (TextField) findComponentById(ResourceTable.Id_comment_tf);

    //评论功能
    sendComment.setClickedListener(component -> {
        if (comment.getText().isEmpty()) {
            new ToastDialog(getContext()).setText("评论不能为空").show();
            return;
        }
        CommentModel model = new CommentModel();
        model.setCommentContent(comment.getText());
        model.setuId(UUID.randomUUID().toString());
        model.setuName("robot");
        model.setCommentDate("刚刚");

        //添加到数据提供程序中
        provider.addComment(model);

        comment.setText("");
        //评论数+1
        commentsText.setText(String.valueOf(Integer.valueOf(comments) + 1));

        new ToastDialog(getContext()).setText("评论成功").show();

    });
}

问题总结

1.播放器列表滑动时,会重复添加播放组件的问题

2.评论功能,输入框跟随输入法调整位置的问题(待完善)

3.播放列表滑动时,根据ScrollView的位置控制视频播放(待完善)

4.播放列表中播放视频进度同步到播放详情页的问题(待完善)

完整代码

附件直接下载

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

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
DistributedVideoPlayer20211018.zip 27.45M 159次下载
已于2021-10-18 13:54:36修改
10
收藏 10
回复
举报
3条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

期待大佬下期精彩内容

回复
2021-10-18 18:05:33
Buty9147
Buty9147

在 VideoPlayAbilitySlice.java的onStart方法中,增加如下代码,可以使TextField跟随输入法的键盘浮动,在输入的过程中看到输入的内容,感谢这个为网友的分享:https://harmonyos.51cto.com/posts/8674

//作用是让评论的输入框随着软键盘而浮动
this.getWindow().setInputPanelDisplayType(WindowManager.LayoutConfig.INPUT_ADJUST_PAN);

 

回复
2021-10-22 11:36:45
甜甜爱开发
甜甜爱开发

楼主每一篇文章都是精品呀

回复
2021-11-15 09:52:52
回复