HarmonyOS鸿蒙Next分布式多媒体互动应用

HarmonyOS鸿蒙Next分布式多媒体互动应用 #HarmonyOS征文# 分布式多媒体互动应用

简介

分布式多媒体互动应用是基于#HarmonyOS挑战赛的挑战要求结合HarmonyOS媒体组件和分布式能力等,构建的一款分布式多媒体互动的应用。通过#HarmonyOS挑战赛第二期#分布式多媒体互动应用的分布式从零学习,到#HarmonyOS挑战赛第三期#分布式多媒体互动应用-绘画版逐步掌握,到#HarmonyOS技术训练营第三期#分布式多媒体互动应用-搜索图片版的添加奇思妙想,HarmonyOS的各种能力让我这位App开发十年的老菜鸟得到了无限的想象空间。本应用功能包括滑动切换视频功能、评论功能(评论数据使用了分布式数据库实现了分布式和持久化)、分布式流转功能、远程控制播放和发弹幕互动功能、分布式流转后同步观看并发弹幕互动功能、绘画多屏互动功能、文件搜索、图片分享功能。

本应用可满足个人娱乐、家庭互助、朋友娱乐社交等场景,例如以下使用场景:

  1. 自己观看本地视频,并发送评论或者弹幕;
  2. 把手机里的视频流转到智慧屏上给小孩或者老人观看,并远程控制播放,同时可以发送弹幕互动;
  3. 在视频流转后,也可同步观看同一视频,并发送评论、弹幕互动、绘画、文件搜索、图片分享。

功能介绍

1. 滑动切换视频功能

动画演示:

核心代码展示:

// 增加视频滑动切换功能
private void changeVideo() {
    player.setDraggedListener(
        Component.DRAG_HORIZONTAL_VERTICAL,
        new Component.DraggedListener() {
            @Override
            public void onDragDown(Component component, DragInfo dragInfo) {
                HiLog.info(VIDEO_BOX_LABEL, "VideoView onDragDown");
            }
            @Override
            public void onDragStart(Component component, DragInfo dragInfo) {
            }
            @Override
            public void onDragUpdate(Component component, DragInfo dragInfo) {
                currentPlayingIndex++;
                int size = videoService.getAllVideoInfo().getDetail().size();
                if(currentPlayingIndex >= size)
                {
                    currentPlayingIndex = 0;
                }
                playbackNext(videoService.getVideoInfoByIndex(currentPlayingIndex));
            }
            @Override
            public void onDragEnd(Component component, DragInfo dragInfo) {
            }
            @Override
            public void onDragCancel(Component component, DragInfo dragInfo) {
            }
        }
    );
}

2. 评论分布式持久化功能

动画演示:

核心代码展示:

/**
 * Initializing Database Management
 */
private void initDbManager() {
    kvManager = createManager();
    singleKvStore = createDb(kvManager);
    subscribeDb(singleKvStore);
}

/**
 * Create a distributed database manager instance
 *
 * @return database manager
 */
private KvManager createManager() {
    KvManager manager = null;
    try {
        KvManagerConfig config = new KvManagerConfig(this);
        manager = KvManagerFactory.getInstance().createKvManager(config);
    } catch (KvStoreException exception) {
    }
    return manager;
}

/**
 * Creating a Single-Version Distributed Database
 *
 * @param manager Database management
 * @return SingleKvStore
 */
private SingleKvStore createDb(KvManager manager) {
    SingleKvStore kvStore = null;
    try {
        Options options = new Options();
        options.setCreateIfMissing(true).setEncrypt(false).setKvStoreType(KvStoreType.SINGLE_VERSION);
        kvStore = manager.getKvStore(options, STORE_ID);
    } catch (KvStoreException exception) {
    }
    return kvStore;
}

/**
 * Subscribing to All (Currently, Remote) Data Change Notifications of a Single-Version Distributed Database
 *
 * @param kvStore Data operation
 */
private void subscribeDb(SingleKvStore kvStore) {
    KvStoreObserver kvStoreObserverClient = new KvStoreObserverClient();
    kvStore.subscribe(SubscribeType.SUBSCRIBE_TYPE_REMOTE, kvStoreObserverClient);
}

/**
 * Receive database messages
 *
 * @since 2021-01-06
 */
private class KvStoreObserverClient implements KvStoreObserver {
    @Override
    public void onChange(ChangeNotification notification) {
        getUITaskDispatcher().asyncDispatch(() -> {
            queryData();
            ToastUtils.showTips(getContext(), "同步成功", 0);
        });
    }
}

/**
 * 查询评论列表数据
 */
private void queryData() {
    List<Entry> entryList = singleKvStore.getEntries("");
    selectNews.clear();
    try {
        for (Entry entry : entryList) {
            selectNews.add(new NewsInfo(entry.getKey(),entry.getValue().getString()));
        }
    } catch (KvStoreException exception) {
    }
    newsListProvider.notifyDataChanged();
}

3. 分布式流转远程控制功能

动画演示:

核心代码展示:

@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;
        case ResourceTable.Id_comment_button:
            if(commentTextField != null)
            {
                String text = commentTextField.getText();
                if(text != null && !text.equals("")){
                    remoteControllerListener.sendControl(ControlCode.COMMENT.getCode(), text);
                    ToastUtils.showTips(getContext(),"弹幕发送成功:\r\n"+text,0);
                    commentTextField.setText("");
                }
                else{
                    ToastUtils.showTips(getContext(),"内容为空,请输入弹幕!",0);
                }
            }
            break;
        default:
            break;
    }
}

4. 分布式弹幕功能

动画演示:

核心代码展示:

// 被流转端接收远程控制信息
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);
        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();
            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 if (controlCode == ControlCode.COMMENT.getCode()) {
            if(textAutoScrolling != null) {
                textAutoScrolling.setText(extras);
                textAutoScrolling.startAutoScrolling();
            }
        } else {
            HiLog.error(LABEL, "Ignoring the case with control code");
        }
    }
}

5. 分布式流转后同步观看并发弹幕互动功能

动画演示:

核心代码展示:

// 发送按钮,进行评论/弹幕
Text textAutoScrolling = (Text) findComponentById(ResourceTable.Id_text_auto_scrolling);
if(textAutoScrolling != null)
{
    textAutoScrolling.setTruncationMode(Text.TruncationMode.AUTO_SCROLLING);
    textAutoScrolling.setAutoScrollingCount(Text.AUTO_SCROLLING_FOREVER);
    textAutoScrolling.startAutoScrolling();
}
button.setClickedListener(listener -> {
    String text = commentTextField.getText();
    if(text !=null && !text.equals("")){
        if(isDanmuFlag)
        {
            if (remoteController != null)
            {
                remoteController.sendDanmu(text);
            }
            if(textAutoScrolling != null) {
                textAutoScrolling.setText(text);
                textAutoScrolling.startAutoScrolling();
            }
        }
        NewsInfo newsInfo = new NewsInfo();
        newsInfo.setTitle(text);
        SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd a hh:mm");
        String date = sdf1.format(new Date(System.currentTimeMillis()));
        newsInfo.setTime(date);
        newsInfo.setType("自己:");
        selectNews.add(newsInfo);
        singleKvStore.putString(text, date);
        queryData();
        newsListContainer.scrollToCenter(selectNews.size());
        commentTextField.setText("");
    }
});
getUITaskDispatcher().delayDispatch(this::queryData, DELAY_MS);

6. 分布式流转后同步观看并绘画多屏互动功能

动画演示:

核心代码展示:

private void initDraw() {
    if(findComponentById(ResourceTable.Id_text_title) instanceof Text) {
        Text textTitle = (Text) findComponentById(ResourceTable.Id_text_title);
        textTitle.setText(isLocal ? "本地端绘画" : "远程端绘画");
    }
    if(findComponentById(ResourceTable.Id_bac_area) instanceof DependentLayout) {
        area = (DependentLayout) findComponentById(ResourceTable.Id_bac_area);
        showButton = (Button) findComponentById(ResourceTable.Id_draw_show_button);
        clearButton = (Button) findComponentById(ResourceTable.Id_draw_clear_button);
        if(area != null)
        {
            drawl = new DrawPoint(this, isLocal);
            drawl.setWidth(MATCH_PARENT);
            drawl.setWidth(MATCH_PARENT);
            area.addComponent(drawl);
            area.setVisibility(INVISIBLE);
            drawl.setOnDrawBack(points -> {
                drawPoint(points);
            });
            showButton.setClickedListener(listener -> {
                if(area.getVisibility() == VISIBLE)
                {
                    area.setVisibility(INVISIBLE);
                    showButton.setText("显示");
                }
                else
                {
                    area.setVisibility(VISIBLE);
                    showButton.setText("隐藏");
                }
            });
            clearButton.setClickedListener(listener -> {
                if(area.getVisibility() == VISIBLE)
                {
                    drawl.Clear();
                }
            });
        }
    }
}

private void drawPoint(List<MyPoint> points) {
    if(connectFlag && points != null && points.size() > 1) {
        pointXs = new float[points.size()];
        pointYs = new float[points.size()];
        isLastPoints = new boolean[points.size()];
        for(int i = 0; i < points.size(); i++) {
            pointXs[i] = points.get(i).getPositionX();
            pointYs[i] = points.get(i).getPositionY();
            isLastPoints[i] = points.get(i).isLastPoint();
        }
        // After the drawing is completed, send the data to the remote
        if(myProxy != null) {
            try {
                myProxy.sendDataToRemote(MyRemoteProxy.REQUEST_SEND_DATA,pointXs,pointYs,isLastPoints);
            } catch (RemoteException e) {
                LogUtil.info(TAG, "processEvent RemoteException");
            }
        }
    }
}

7. 分布式文件搜索,图片分享

动画演示:

核心代码展示:

/**
 * Query Local Data
 */
private void queryData() {
    List<Entry> entryList = singleKvStore.getEntries("");
    selectNews.clear();
    try {
        for (Entry entry : entryList) {
            ZSONObject zsonObject = ZSONObject.stringToZSON(entry.getValue().getString());
            String type = zsonObject.getString("type");
            String text = zsonObject.getString("text");
            String time = zsonObject.getString("time");
            String imageUrl = zsonObject.getString("imageUrl");
            selectNews.add(new NewsInfo(type,text,time,imageUrl));
        }
    } catch (KvStoreException exception) {
    }
    newsListProvider.notifyDataChanged();
}

工程源码

代码工程和相关视频演示、gif演示、程序截图已放到了Gitee指定的代码仓里-代码仓,欢迎大家指导指导,有兴趣一起开源开发!

总结

本次开发时间比较有限,但是在华为开发者联盟论坛里有大量HarmonyOS应用开发的工程例子和DevEco Studio 的模板,非常方便地实现了丰富的功能。例如本应用就是在Video PlayerAbility模板下生成工程,使用了分布式数据库单版本分布式数据库类型KvStoreType.SINGLE_VERSION,同时实现了数据的分布式和持久化。本应用的使用场景越来越偏向于家庭式分享互动,家长和小孩间可以一边观看教育或者动画视频,一边绘画互动,一边远程搜索文件图片分享图片功能,将会更加方便有趣。因为时间关系,还有如下改进方向:

  1. 目前播放视频为内置视频,后面可增加自定义播放本地视频;
  2. 目前弹幕效果比较单一,可考虑接入一些第三方弹幕库进行效果丰富;
  3. 目前是以评论、图片和弹幕进行互动,都属于图文类的,可接入语音模块,已了解到官方有相关工程例子,后面可方便实现。

更多关于HarmonyOS鸿蒙Next分布式多媒体互动应用的实战教程也可以访问 https://www.itying.com/category-93-b0.html

1 回复

更多关于HarmonyOS鸿蒙Next分布式多媒体互动应用的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


HarmonyOS鸿蒙Next的分布式多媒体互动应用,通过其分布式技术,实现了跨设备的无缝协同。用户可以在不同设备间自由切换,享受一致的多媒体体验。例如,用户可以在手机上开始观看视频,然后无缝切换到电视上继续播放。此外,鸿蒙Next还支持多设备间的实时互动,如多屏协同、跨设备文件共享等,极大地提升了用户的使用体验和效率。

回到顶部