2天时间,聊今年最热的 Agent、上下文工程、AI 产品创新等话题。2025 年最后一场~ 了解详情
写点什么

基于 Agora SDK 实现 Android 端的多人视频互动

  • 2020-02-26
  • 本文字数:3910 字

    阅读完需:约 13 分钟

基于 Agora SDK 实现 Android 端的多人视频互动

本系列教程将分为三期,分享基于 Agora SDK 在各系统平台应用中实现一对一视频通话、多人互动直播,以及结合跨平台技术进行开发。本期推送在 Android、iOS、Windows、Web、macOS 上实现多人视频互动直播。


本篇基于上一篇「Android 端一对一视频通话」教程,讲分享如何使用 Agora SDK 开发多人互动直播。

分屏

本文是采用瀑布流结合动态聊天窗实现分屏显示,这样比较方便的能够适应 UI 的变化。所谓瀑布流,就是目前比较流行的一种列表布局,会在界面上呈现参差不齐的多栏布局。我们先实现一个瀑布流:


瀑布流的实现方式很多,本文采用结合 GridLayoutManager 的 RecyclerView 来实现。我们首先自定义一个 RecyclerView,命名为 GridVideoViewContainer。核心代码如下:


int count = uids.size();if(count <= 2) {// 只有本地视频或聊天室内只有另外一个人this.setLayoutManager(newLinearLayoutManager(activity.getApplicationContext(), orientation, false));} elseif(count > 2) {// 多人聊天室int itemSpanCount = getNearestSqrt(count);this.setLayoutManager(newGridLayoutManager(activity.getApplicationContext(), itemSpanCount, orientation, false));}
复制代码


根据上面的代码可以看出,在聊天室里只有自己的本地视频或者只有另外一个人的时候,采用 LinearLayoutManager,这样的布局其实与前文的一对一聊天类似;而在真正意义的多人聊天室里,则采用 GridLayoutManager 实现瀑布流,其中 itemSpanCount 就是瀑布流的列数。


有了一个可用的瀑布流之后,下面我们就可以实现动态聊天窗了:动态聊天窗的要点在于 item 的大小由视频的宽高比决定,因此 Adapter 及其对应的 layout 就该注意不要写死尺寸。在 Adapter 里控制 item 具体尺寸的代码如下:



if(force || mItemWidth == 0|| mItemHeight == 0) {WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);DisplayMetrics outMetrics = newDisplayMetrics(); windowManager.getDefaultDisplay().getMetrics(outMetrics);
int count = uids.size();intDividerX= 1;intDividerY= 1;
if(count == 2) {DividerY= 2;} elseif(count >= 3) {DividerX= getNearestSqrt(count);DividerY= (int) Math.ceil(count * 1.f/ DividerX);}
int width = outMetrics.widthPixels;int height = outMetrics.heightPixels;
if(width > height) { mItemWidth = width / DividerY; mItemHeight = height / DividerX;} else{ mItemWidth = width / DividerX; mItemHeight = height / DividerY;}}
复制代码


以上代码根据视频的数量确定了列数和行数,然后根据列数和屏幕宽度确定了视频的宽度,接着根据视频的宽高比和视频宽度确定了视频高度。同时也考虑了手机的横竖屏情况(就是 if (width > height)这行代码)。


该 Adapter 对应的 layout 的代码如下:


<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/user_control_mask"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical">
<ImageViewandroid:id="@+id/default_avatar"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:visibility="gone"android:src="@drawable/icon_default_avatar"android:contentDescription="DEFAULT_AVATAR"/>
<ImageViewandroid:id="@+id/indicator"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerHorizontal="true"android:layout_alignParentBottom="true"android:layout_marginBottom="@dimen/video_indicator_bottom_margin"android:contentDescription="VIDEO_INDICATOR"/>
<LinearLayoutandroid:id="@+id/video_info_container"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentTop="true"android:layout_marginTop="24dp"android:layout_marginStart="15dp"android:layout_marginLeft="15dp"android:visibility="gone"android:orientation="vertical">
<TextViewandroid:id="@+id/video_info_metadata"android:layout_width="wrap_content"android:layout_height="wrap_content"android:singleLine="true"style="@style/NotificationUIText"/></LinearLayout>
</RelativeLayout>
复制代码


我们可以看到,layout 中有关尺寸的属性都 是 wrap_content,这就使得 item 大小随视频宽高比变化成为可能。


把分屏的布局写好之后,我们就可以在每一个 item 上播放聊天视频了。

播放聊天视频

在 Agora SDK 中一个远程视频的显示只和该用户的 UID 有关,所以使用的数据源只需要简单定义为包含 UID 和对应的 SurfaceView 即可,就像这样:


privatefinalHashMap<Integer, SurfaceView> mUidsList = newHashMap<>();
复制代码


每当有人加入了我们的聊天频道,都会触发 onFirstRemoteVideoDecoded(int uid, int width, int height, int elapsed)方法,第一个 uid 就是他们的 UID;接下来我们要为每个 item 新建一个 SurfaceView 并为其创建渲染视图,最后将它们加入刚才创建好的 mUidsList 里并调用 setupRemoteVideo( VideoCanvas remote )方法播放这个聊天视频。这个过程的完整代码如下:


@Overridepublicvoid onFirstRemoteVideoDecoded(int uid, int width, int height, int elapsed) {   doRenderRemoteUi(uid);}
privatevoid doRenderRemoteUi(finalint uid) { runOnUiThread(newRunnable() {@Overridepublicvoid run() {if(isFinishing()) {return;}
if(mUidsList.containsKey(uid)) {return;}
SurfaceView surfaceV = RtcEngine.CreateRendererView(getApplicationContext()); mUidsList.put(uid, surfaceV);
boolean useDefaultLayout = mLayoutType == LAYOUT_TYPE_DEFAULT;
surfaceV.setZOrderOnTop(true); surfaceV.setZOrderMediaOverlay(true);
rtcEngine().setupRemoteVideo(newVideoCanvas(surfaceV, VideoCanvas.RENDER_MODE_HIDDEN, uid));
if(useDefaultLayout) { log.debug("doRenderRemoteUi LAYOUT_TYPE_DEFAULT "+ (uid & 0xFFFFFFFFL)); switchToDefaultVideoView();} else{int bigBgUid = mSmallVideoViewAdapter == null? uid : mSmallVideoViewAdapter.getExceptedUid(); log.debug("doRenderRemoteUi LAYOUT_TYPE_SMALL "+ (uid & 0xFFFFFFFFL) + " "+ (bigBgUid & 0xFFFFFFFFL)); switchToSmallVideoView(bigBgUid);}}});}
复制代码


以上代码与前文中播放一对一视频的代码如出一撤,但是细心的读者可能已经发现我们并没有将生成的 SurfaceView 放在界面里,这正是与一对一视频的不同之处:我们要在一个抽象的 VideoViewAdapter 类里将 SurfaceView 放出来,关键代码如下:


SurfaceView target = user.mView;VideoViewAdapterUtil.stripView(target);holderView.addView(target, 0, newFrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
复制代码


一般 Android 工程师看见 holderView 就明白这是 ViewHolder 的 layout 的根 layout 了,而 user 是哪儿来的,详见文末的代码,文中不做赘述。


这样在多人聊天的时候我们就能使用分屏的方式播放用户聊天视频了,如果想放大某一个用户的视频该怎么办呢?

全屏和小窗

当用户双击某一个 item 的时候,他希望对应的视频能够全屏显示,而其他的视频则变成小窗口,那么我们先定义一个双击事件接口:


publicinterfaceVideoViewEventListener{void onItemDoubleClick(View v, Object item);}
复制代码


具体实现方式如下:


mGridVideoViewContainer.setItemEventHandler(newVideoViewEventListener() {@Overridepublicvoid onItemDoubleClick(View v, Object item) {        log.debug("onItemDoubleClick "+ v + " "+ item + " "+ mLayoutType);
if(mUidsList.size() < 2) {return;}
UserStatusData user = (UserStatusData) item;int uid = (user.mUid == 0) ? config().mUid : user.mUid;
if(mLayoutType == LAYOUT_TYPE_DEFAULT && mUidsList.size() != 1) { switchToSmallVideoView(uid);} else{ switchToDefaultVideoView();}}});
复制代码


将被选中的视频全屏播放的方法很容易理解,我们只看生成小窗列表的方法:


privatevoid switchToSmallVideoView(int bigBgUid) {HashMap<Integer, SurfaceView> slice = newHashMap<>(1);    slice.put(bigBgUid, mUidsList.get(bigBgUid));Iterator<SurfaceView> iterator = mUidsList.values().iterator();while(iterator.hasNext()) {SurfaceView s = iterator.next();        s.setZOrderOnTop(true);        s.setZOrderMediaOverlay(true);}
mUidsList.get(bigBgUid).setZOrderOnTop(false); mUidsList.get(bigBgUid).setZOrderMediaOverlay(false);
mGridVideoViewContainer.initViewContainer(this, bigBgUid, slice, mIsLandscape);
bindToSmallVideoView(bigBgUid);
mLayoutType = LAYOUT_TYPE_SMALL;
requestRemoteStreamType(mUidsList.size());}
复制代码


小窗列表要注意移除全屏的那个 UID,此外一切都和正常瀑布流视图相同,包括双击小窗的 item 将其全屏播放。


到了这里我们就已经使用 Agora SDK 完成了一个有基本功能的简单多人聊天 demo。


本文转载自 声网 Agora 公众号。


原文链接:https://mp.weixin.qq.com/s/lO5pf71goWTRFhm8mo3jKw


2020-02-26 15:49949

评论

发布
暂无评论
发现更多内容

华为云Serverless核心技术与最佳实践

平平无奇爱好科技

华为云新一代iPaaS全域融合集成平台全新升级!

YG科技

华为云应用运维管理平台获评中国信通院可观测性评估先进级

YG科技

【深入了解系统性能优化】「实战技术专题」全方面带你透彻探索服务优化技术方案(系统服务调优)

码界西柚

JVM Java虚拟机 技术推荐 技术调优 开发实战

探究直播app源码技术:视频上传功能

山东布谷科技

软件开发 APP开发 源码搭建 app源码 直播APP源码

Nautilus Chain 推出全新 Layer3 DID 公民身份,限量 10 万枚免费发放

西柚子

产品质量管理利器,华为云发布CodeArts Defect缺陷管理服务

平平无奇爱好科技

企业应用可观测性利器!华为云CodeArts APM发布

平平无奇爱好科技

2023-07-01:redis过期策略都有哪些?LRU 算法知道吗?

福大大架构师每日一题

redis 福大大架构师每日一题

是时候了!MySQL 5.7 的下一站,不如试试 TiDB?

PingCAP

MySQL 数据库 TiDB

应用在虚机和容器场景下如何优雅上下线

YG科技

高效联调,可靠发布!华为云推出CodeArts Release发布管理服务

YG科技

Python潮流周刊#9:如何在本地部署开源大语言模型?

Python猫

Python

C++中fork函数的使用及原理

芯动大师

es笔记三之term,match,match_phrase 等查询方法介绍

Hunter熊

elasticsearch

亿级日活业务稳如磐石,华为云CodeArts PerfTest发布

平平无奇爱好科技

Nautilus Chain 推出全新 Layer3 DID 公民身份,限量 10 万枚免费发放

股市老人

Nautilus Chain 推出全新 Layer3 DID 公民身份,限量 10 万枚免费发放

鳄鱼视界

免费搭建一个有脾气的聊天机器人,1行Python代码就够了!

程序员晚枫

Python 微信 机器人

【分布式技术专题】「分布式技术架构」实践见真知,手把手教你如何实现一个属于自己的RPC框架(架构技术引导篇)

码界西柚

RPC 架构分析 分布式服务

我在AIGC和数字中台方面的架构升级设计

大东(AIP智能体运营专员)

CDH5.11.2 集成 IceBerg(一)

冰心的小屋

数据湖 iceberg

CDH5.11.2集成IceBerg(二):Spark3适配

冰心的小屋

spark CDH

PixelForce - AI绘画释放产品魅力

原力在线

Nautilus Chain 推出全新 Layer3 DID 公民身份,限量 10 万枚免费发放

BlockChain先知

基于 Agora SDK 实现 Android 端的多人视频互动_行业深度_声网_InfoQ精选文章