分布式多媒体互动应用

分布式多媒体互动应用 #HarmonyOS挑战赛第二期#分布式多媒体互动应用

一、简介

分布式多媒体互动应用是基于#HarmonyOS挑战赛第二期# 的挑战要求结合HarmonyOS媒体组件和分布式能力等,构建一款分布式多媒体互动的应用。应用功能包括滑动切换视频功能、评论功能(评论数据使用了分布式数据库实现了分布式和持久化)、分布式流转功能、远程控制播放和发弹幕互动功能、分布式流转后同步观看并发弹幕互动功能。

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

  • 自己观看本地视频,并发送评论或者弹幕;
  • 把手机里视频流转到智慧屏上给小孩或者老人观看,并远程控制播放,同时可以发送弹幕互动;
  • 在视频流转后,也可同步观看同一视频,并发送评论、弹幕互动。

二、功能介绍

  1. 滑动切换视频功能

动画演示:

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

核心代码展示:

/**
 * 增加视频滑动切换功能
 */
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)
            {
            }
        }
    );
}

参考资料:官方工程模板VideoPlayerAbility

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

动画演示:

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

核心代码展示:

/**
 * 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();
}

参考资料:官方工程模板NewsAbility和分布式数据服务的单版本分布式数据库类型KvStoreType.SINGLE_VERSION

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

动画演示:

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

核心代码展示:

//分布式流转远程遥控
@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;
    }
}
  1. 分布式弹幕功能

动画演示:

4分布式弹幕功能.gif

核心代码展示:

//被流转端接收远程控制信息
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");
        }
    }
}
  1. 分布式流转后同步观看并发弹幕互动功能

动画演示:

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

核心代码展示:

// 发送按钮,进行评论/弹幕
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);

三、工程源码

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

四、总结

本次开发时间比较有限,但是在华为开发者联盟论坛里有大量HarmonyOS应用开发的工程例子和DevEco Studio 的模板,非常方便地实现了丰富的功能。例如本应用就是在Video PlayerAbility模板下生成工程,使用了分布式数据库单版本分布式数据库类型KvStoreType.SINGLE_VERSION,同时实现了数据的分布式和持久化。本应用的使用场景重点在于视频分享后可能进行互动,因为时间关系,还有如下改进方向:

  • 目前播放视频为内置视频,后面可增加自定义播放本地视频;
  • 目前弹幕效果比较单一,可考虑接入一些第三方弹幕库进行效果丰富;
  • 目前是以评论和弹幕进行互动,都属于文字类的,可接入语音模块,已了解到官方有相关工程例子,后面?可方便实现;
  • 目前分布式的流转是Phone端到TV端,而且是单向的,后面可以增加Phone端到Phone端、Tablet端等,同时可两端相互通讯。

五、个人感想

本人有幸参加了HDC2019和HDC2020,同步见证着鸿蒙的诞生,到现在茁壮成长,Harmony OS开发已经成为了我们开发者中不可忽视的领域,也确实值得深入研究,融入鸿蒙生态之中来创造个人更大的价值。躬身入局,才是解决困境的最佳途径。期待HDC2021!鸿蒙可期!华为可期!

最后,预祝HDC2021完满成功,华为开发者联盟越来越强大!

9 回复

楼主太厉害了,可以分享源码参考一下吗?


这里没有提供具体的HTML内容,仅有一个文本和一个链接。根据你的要求,转换后的Markdown文档如下:

这里没有提供具体的HTML内容,仅有一个文本和一个链接。需要的话再更新一下,

这个显示访问受限,楼主可以发一下邮箱alaluminium@outlook.com吗?做鸿蒙毕设想学习一下,感谢,

可以,毕设加油^^,

666,666,加油!

感谢大神,让我见识了鸿蒙分布式的厉害,666!

6666666666666

thx~~~~~~~~

分布式多媒体互动应用是一种基于网络的多媒体系统,支持多个用户在不同地点实时交互和共享多媒体内容。其核心技术包括分布式计算、流媒体传输、实时通讯协议(如WebRTC)和数据同步机制。通过负载均衡和内容分发网络(CDN),系统能够高效处理大规模并发请求,确保低延迟和高可靠性。典型应用场景包括在线教育、远程会议、协同设计和多人在线游戏。其架构通常分为客户端、服务器和存储层,采用微服务架构以提高可扩展性和维护性。

回到顶部
AI 助手
你好,我是IT营的 AI 助手
您可以尝试点击下方的快捷入口开启体验!