HarmonyOS鸿蒙Next分布式多媒体互动应用
HarmonyOS鸿蒙Next分布式多媒体互动应用 #HarmonyOS征文# 分布式多媒体互动应用
简介
分布式多媒体互动应用是基于#HarmonyOS挑战赛的挑战要求结合HarmonyOS媒体组件和分布式能力等,构建的一款分布式多媒体互动的应用。通过#HarmonyOS挑战赛第二期#分布式多媒体互动应用的分布式从零学习,到#HarmonyOS挑战赛第三期#分布式多媒体互动应用-绘画版逐步掌握,到#HarmonyOS技术训练营第三期#分布式多媒体互动应用-搜索图片版的添加奇思妙想,HarmonyOS的各种能力让我这位App开发十年的老菜鸟得到了无限的想象空间。本应用功能包括滑动切换视频功能、评论功能(评论数据使用了分布式数据库实现了分布式和持久化)、分布式流转功能、远程控制播放和发弹幕互动功能、分布式流转后同步观看并发弹幕互动功能、绘画多屏互动功能、文件搜索、图片分享功能。
本应用可满足个人娱乐、家庭互助、朋友娱乐社交等场景,例如以下使用场景:
- 自己观看本地视频,并发送评论或者弹幕;
- 把手机里的视频流转到智慧屏上给小孩或者老人观看,并远程控制播放,同时可以发送弹幕互动;
- 在视频流转后,也可同步观看同一视频,并发送评论、弹幕互动、绘画、文件搜索、图片分享。
功能介绍
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,同时实现了数据的分布式和持久化。本应用的使用场景越来越偏向于家庭式分享互动,家长和小孩间可以一边观看教育或者动画视频,一边绘画互动,一边远程搜索文件图片分享图片功能,将会更加方便有趣。因为时间关系,还有如下改进方向:
- 目前播放视频为内置视频,后面可增加自定义播放本地视频;
- 目前弹幕效果比较单一,可考虑接入一些第三方弹幕库进行效果丰富;
- 目前是以评论、图片和弹幕进行互动,都属于图文类的,可接入语音模块,已了解到官方有相关工程例子,后面可方便实现。
更多关于HarmonyOS鸿蒙Next分布式多媒体互动应用的实战教程也可以访问 https://www.itying.com/category-93-b0.html
更多关于HarmonyOS鸿蒙Next分布式多媒体互动应用的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
HarmonyOS鸿蒙Next的分布式多媒体互动应用,通过其分布式技术,实现了跨设备的无缝协同。用户可以在不同设备间自由切换,享受一致的多媒体体验。例如,用户可以在手机上开始观看视频,然后无缝切换到电视上继续播放。此外,鸿蒙Next还支持多设备间的实时互动,如多屏协同、跨设备文件共享等,极大地提升了用户的使用体验和效率。