写点什么

基于 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:49966

评论

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

配电 低压电工经验总结(17)

万里无云万里天

工业 工厂运维

搞跨端渲染?你绕不开的HarfBuzz原理

非专业程序员Ping

大前端 ios 开发 an'droid

软件外包公司选择指南 - 郑州寻路科技专业建议

寻路科技

软件开发公司

内网视频会议与外网视频会议的区别是什么?

BeeWorks

即时通讯 IM 视频会议 局域网

黑龙江龙江网络安全:等保测评的核心作用与覆盖范畴

等保测评

2026年境外舆情监测网站选型白皮书:功能、厂商与案例

沃观Wovision

舆情监测 沃观Wovision 海外舆情监测 境外舆情监测 舆情监测网站

双碳目标下,MyEMS 为何成为制造企业的 “刚需工具”?

开源能源管理系统

开源 能源管理系统

基于华为开发者空间云主机的软件安全栈溢出攻击实践

华为云开发者联盟

云主机 华为开发者空间

领域驱动设计(DDD)领域对象一定要讲究充血模型吗?

canonical

领域驱动设计 DDD 聚合根 可逆计算

打破 “封闭垄断”:MyEMS 开源生态如何有效控制企业能源管理成本

开源能源管理系统

开源 能源管理系统

基于华为开发者空间云主机部署Typora高效内容创作,实现图片自动上传

华为云开发者联盟

Typora PicGo 华为开发者空间 云主机环境 对象存储服务 (OBS)

选对黑龙江等保测评公司:从合规准入到服务质量的全维度指南

等保测评

提示词工程-复杂项目-VB Coding

Jxin

AI 软件工程 提示词工程 氛围编程

基于华为开发者空间,实现RFM分析与CLTV预测的电商客户细分与营销策略优化

华为云开发者联盟

RFM模型 华为开发者空间 CLTV

区块链 Web3 项目的开发流程

北京木奇移动技术有限公司

区块链开发 软件外包公司 web3开发

读懂5G新通话:可能是AI落地千行万业的首个全民级场景

Alter

AI 5G

MIAOYUN | 每周AI新鲜事儿(10.17-10.24)

MIAOYUN

AI 机器人 AIGC AI大语言模型 多模态模型

AI 友好的云开发 MySQL SDK 它来了!微信小程序能直连关系型数据库了

蛋先生DX

微信小程序 云开发 AI‘’ mysql'

华为开发者空间,基于仓颉与DeepSeek的MCP智能膳食助手

华为云开发者联盟

MaaS DeepSeek v3 华为开发者空间

BeeWorks企业即时通讯好用吗,马上简单了解一下!

BeeWorks

即时通讯 IM 私有化部署

【FAQ】HarmonyOS SDK 闭源开放能力 — AppGallery Kit

HarmonyOS SDK

HarmonyOS SDK应用服务

基于华为开发者空间-云开发环境,Vanna+MaaS实现自然语言与数据库对话

华为云开发者联盟

MaaS 华为开发者空间 云开发环境 Vanna Text-to-SQL

为什么说境外舆情监测是全球化企业的必要投资?

沃观Wovision

舆情监测 沃观Wovision 海外舆情监测 境外舆情监测 舆情监测软件

想了解ABAQUS,有没有比较好的SMULIA代理商公司推荐?

思茂信息

abaqus软件 达索 SMULIA代理商 思茂信息

信息化系统数据安全建设方案

金陵老街

数据安全 医疗安全

AI风险评估系统:技术架构、行业落地与风控效能革新

上海拔俗

领域驱动设计(DDD)中聚合根的最主要职责真的是维护一致性吗?

canonical

领域驱动设计 DDD 领域模型 可逆计算 Nop平台

不止节能:MyEMS 为企业带来的成本控制、合规风控与数字化转型价值

开源能源管理系统

开源 开源能源管理系统

大数据-136 - ClickHouse 集群 表引擎详解 选型实战:TinyLog/Log/StripeLog/Memory/Merge

武子康

大数据 flink spark 分布式 Clickhouse

基于 Agora SDK 实现 Android 端的多人视频互动_行业深度_RTE开发者社区_InfoQ精选文章