Android集成多人通话


跑通Demo

进入环信IM下载页面,选择视频会议Demo 下载
或者直接去Github下载 克隆代码

Android Studio 3.0 或以上版本
Android SDK API 等级 19 或以上
支持 Android 4.4 或以上版本的设备

Demo代码目录简介


源码目录如上所示,主要目录如以下介绍
ui是有关所有Activity的具体实现
runtimepermissions是有关运行是有关动态权限的获取的封装
untils是有关工具类的封装
DemoHelper和DemoApplication类是全局单例,用来做一些初始化

工程设置,SDK导入

选择如下任意一种方式将环信语音SDK集成到你的项目中:

方法一:使用 JCenter 自动集成

在项目的 /app/build.gradle 文件中,添加如下行

dependencies { 
   ... 
   // x.x.x 请填写具体版本号,如:3.7.0 
   // 可通过 SDK 发版说明取得最新版本号 
   api 'com.hyphenate:hyphenate-sdk:x.x.x' 
 } 

方法二:手动复制 SDK 文件

前往SDK载页面,获取最新版的环信音视频SDK,然后解压;
将SDK包内libs路径下的如下文件,拷贝到你的项目路径下
hyphenatechat_3.7.0.jar 文件 /app/libs/
arm-v8a 文件夹 /app/src/main/jniLibs/
armeabi-v7a 文件夹 /app/src/main/jniLibs/
x86文件夹 /app/src/main/jniLibs/
x86_64文件夹 /app/src/main/jniLibs/

运行项目

用Android Studio 运行examples\ChatDemoUI3.0 或者 连接Android 手机,直接运行即可
需要注意的的是项目build.gradle中的,配置成和自己Android Studio相匹配的 gradle 版本,如下所示

dependencies {
    classpath 'com.android.tools.build:gradle:3.2.1'
 }

快速集成

环信为管理者与开发者提供了方便易用的App工作台–环信管理后台;
通过环信管理后台可以完成应用创建、服务购买、企业信息修改基础功能;
同时,管理后台也提供了发送消息、用户管理、群组管理、聊天室管理和数据统计等管理监控功能;
有关注册appkey的详细操作大家可以参考注册AppKey

打开 Android Studio点击Start a new Android Studio project;
在Select a Project Template界面,选择 Phone and Tablet > Empty Activity;
然后点击 Next;
在 Configure Your Project界面,依次填入以下内容:Name:你的Android项目名称,如 HelloWrold;
Package name:你的项目包的名称,如 package.easemob.helloworld;
Save location:项目的存储路径;
Language:项目的编程语言,如 Java;
Minimum API level:项目的最低 API 等级;(注意本SDK 支持API 19或以上)
然后点击 Finish;
根据屏幕提示,安装可能需要的插件;
上述步骤使用 Android Studio 3.6.2 示例;
你也可以直接参考Android Studio 官网文档创建首个应用。

新建项目完成以后,可以选择以上任何一种导入SDK的方法,把环信音视频SDK集成到你的项目里面,参考导入环信SDK

根据场景需要,在 /app/src/main/AndroidManifest.xml 文件中添加如下行,获取相应的设备权限;
在清单文件 AndroidManifest.xml 里加入以下权限,以及写上你注册的 AppKey;
权限配置(实际开发中可能需要更多的权限,可参考 Demo);

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 package="com.a.b">

 <uses-feature android:name="android.hardware.camera" />
 <uses-feature android:name="android.hardware.camera.autofocus" />
 <uses-permission android:name="android.permission.VIBRATE" />
 <uses-permission android:name="android.permission.INTERNET" />
 <uses-permission android:name="android.permission.RECORD_AUDIO" />
 <uses-permission android:name="android.permission.CAMERA" />
 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
 <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
 <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
 <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
 <uses-permission android:name="android.permission.BLUETOOTH" />
 <uses-permission android:name="android.permission.READ_PHONE_STATE" />
 <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
 <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" />
 <uses-permission android:name="android.permission.BROADCAST_STICKY" />
 <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<!-- 设置环信应用的appkey -->
 <meta-data
 android:name="EASEMOB_APPKEY"
 android:value="easemob-demo#chatdemoui" />
 <!-- 声明sdk所需的service -->
 <service
 android:name="com.hyphenate.chat.EMChatService"
 android:exported="true"
 tools:ignore="ExportedService" />
 <service
 android:name="com.hyphenate.chat.EMJobService"
 android:exported="true"
 android:permission="android.permission.BIND_JOB_SERVICE" />

 <!-- 声明sdk所需的receiver -->
 <receiver android:name="com.hyphenate.chat.EMMonitorReceiver">
 <intent-filter>
 <action android:name="android.intent.action.PACKAGE_REMOVED" />

 <data android:scheme="package" />
 ...
</manifest>

注意:关于EASEMOB_APPKEY对应的value获取,在创建应用后,

    申请 AppKey并进行相关配置,环信Demo中AppKey为easemob-demo#chatdemoui;

在 app/proguard-rules.pro 文件中添加如下行,防止混淆环信SDK的代码。

-keep class com.hyphenate.** {*;}
  -dontwarn  com.hyphenate.**
  //3.6.8版本之后移除apache,无需再添加
  -keep class internal.org.apache.http.entity.** {*;}
  //如果使用了实时音视频功能
  -keep class com.superrtc.** {*;}
  -dontwarn  com.superrtc.**

初始化环信SDK,可以参考使用DemoHelper中的init方法,可设置私有部署地址或者是否允许日志输出,调用如下:

public void init(Context context) {
	EMOptions options = initChatOptions(context);
        appContext = context;
        //可设置私有服务地址
        options.setRestServer("a.b.c"); 
        options.setIMServer("1.2.3.4");
        options.setImPort(8081);
        EMClient.getInstance().init(context, options);
        //打开日志输出
        EMCallManager manager = EMClient.getInstance().callManager();
	EMClient.getInstance().setDebugMode(true);
}

设置会议监听
DemoHelper还有一个重要的功能就是设置EMConferenceListener 进行会议监听
通过这个监听可以再加入会议的时候获取到已经在会议进入会议之前的流或人员
调用EMConferenceManager#addConferenceListener(EMConferenceListener listener)方法指定回调监听
成员加入或离开会议,数据流更新等 注意:该回调监听中的所有方法运行在子线程中,请勿在其中操作UI
调用EMConferenceManager#addConferenceListener(EMConferenceListener listener)方法指定回调监听

EMConferenceListener listener = new EMConferenceListener() {
    @Override public void onMemberJoined(String username) {
        // 有成员加入
    }
                                                            
    @Override public void onMemberExited(String username) {
        // 有成员离开
    }
                                                                
    @Override public void onStreamAdded(EMConferenceStream stream) {
        // 有流加入
    }
                                                                
    @Override public void onStreamRemoved(EMConferenceStream stream) {
        // 有流移除
    }
                                                                
    @Override public void onStreamUpdate(EMConferenceStream stream) {
        // 有流更新
    }
                                                                
    @Override public void onPassiveLeave(int error, String message) {
        // 被动离开
    }
                                                                
    @Override public void onConferenceState(ConferenceState state) {
        // 聊天室状态回调
    }
                                                                
    @Override public void onStreamSetup(String streamId) {
        // 流操作成功回调
    }
                                                                    
    @Override public void onSpeakers(final List<String> speakers) {
        // 当前说话者回调
    }
                                                                    
    @Override public void onReceiveInvite(String confId, String password, String extension) {
        // 收到会议邀请
        if(easeUI.getTopActivity().getClass().getSimpleName().equals("ConferenceActivity")) {
            return;
        }
        Intent conferenceIntent = new Intent(appContext, ConferenceActivity.class);
        conferenceIntent.putExtra(Constant.EXTRA_CONFERENCE_ID, confId);
        conferenceIntent.putExtra(Constant.EXTRA_CONFERENCE_PASS, password);
        conferenceIntent.putExtra(Constant.EXTRA_CONFERENCE_IS_CREATOR, false);
        conferenceIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        appContext.startActivity(conferenceIntent);
    }
    
    // 在Activity#onCreate()中添加监听
    EMClient.getInstance().conferenceManager().addConferenceListener(conferenceListener);
});     

在进行音视频通话前,需要首先登录IM账户,登录过程参见账号登录
若您还没有IM账户,需要先注册账户,注册过程参见账号注册
登录环信ID以后,可以加入会议了,通过 EMConferenceManager#joinRoom API加入房间
创建会议成功以后,默认超时时间为三分钟,超过三分钟没有人加入,会议会自动销毁;
另外当会议中所有人离开2分钟后,会议也会被销毁;
joinRoom api在会议不存在会自动去创建。
joinRoom有以下两个接口,请看下面详细介绍(第二个多参的方法可以指定所创建会议的各种属性)

/**
     * \~chinese
     * 加入多人音视频加议房间
     * @param room        会议房间名
     * @param password    会议房间密码
     * @param role    当前用户加入时指定角色 (EMConferenceRole类型)
     * @param callback    回调
     */
    public void joinRoom(final String room ,final String password,final EMConferenceRole role, final EMValueCallBack<EMConference> callback)
 
    /**
     * \~chinese
     * 加入多人音视频加议房间
     * @param room        会议房间名
     * @param password    会议房间密码
     * @param roletype    当前用户加入时指定角色 (EMConferenceRole类型)
     * @param param       设置会议参数 (EMRoomConfig类型)
     * @param callback    回调函数
     */
    public void joinRoom(final String room ,final String password,final EMConferenceRole roletype ,final EMRoomConfig param, final EMValueCallBack<EMConference> callback)
     
   有关EMRoomConfig定义如下:
   /**
    * 会议房间属性类
    * confrTyp   会议类型
    * isSupportVxmini  是否支持小程序
    * isRecord    是否支持录制
    * isMergeRecord  是否支持和流
    * nickName  用户昵称
    * ext     用户头像
    * maxTalkerCount   房间最多主播
    * maxVideoCount    房间最多视频流媒体
    * maxAudienceCount 房间最多观众
    * maxPubDesktopCount  房间最多共享桌面流
  */
  public class EMRoomConfig {
       EMConferenceManager.EMConferenceType confrTyp = EMConferenceManager.EMConferenceType.SmallCommunication;
        private boolean isSupportMiniProgram = false;
        private boolean isRecord = false;
        private boolean isMergeRecord = false;
        private String  nickName = null;
        private String  ext = null;
        private int  maxTalkerCount = -1;
        private int  maxVideoCount = -1;
        private int  maxAudienceCount = -1;
        private int  maxPubDesktopCount = -1;
        private EMLiveConfig liveConfig = null;
           
       public EMRoomConfig(EMConferenceManager.EMConferenceType confrTyp,
                               boolean  isSupportMiniProgram,boolean isRecord,
                               boolean  isMergeRecord , String  nickName,
                               String ext, int maxTalkerCount,
                               int maxVideoCount,int maxAudienceCount,
                               int maxPubDesktopCount)
                               {...};
      }

展示从DemoHelper类EMConferenceListener中的onStreamAdded回调和onMemberJoined获取到的流和主播列表
在ConferenceActivity中实现EMConferenceListener,然后直接把 ConferenceActivity 注册监听
可以用以下方法EMClient.getInstance().conferenceManager().addConferenceListener(this)
这样就可实现 EMConferenceListener 事件的处理,比如主播 进出房间 增加流 移除流 角色变更 管理员信息变更等事件

进入会议房间以后如果用户角色为主播可以进行发布视频流 ,观众只能订阅视频流 不能发布视频流
可以调用SDK的publish接口发布流,该接口用到了EMStreamParam参数,你可以自由配置
比如是否上传视频 是否上传音频 使用前置或后置摄像头 视频码率 显示视频页面等等
具体实现可以参考中发布订阅视频流的内容,关于以上的代码逻辑如以 如以下:

pirvate void pubLocalStream() {   
    EMStreamParam param = new EMStreamParam();
    param.setStreamType(EMConferenceStream.StreamType.NORMAL);
    param.setVideoOff(false);
    param.setAudioOff(false);
    param.setVideoWidth(720);
    param.setVideoHeight(480);     
    EMClient.getInstance().conferenceManager().publish(param, new EMValueCallBack<String>() {
        @Override
        public void onSuccess(String streamId) {
        }
         
        @Override
        public void onError(int error, String errorMsg) {
            EMLog.e(TAG, "publish failed: error=" + error + ", msg=" + errorMsg);
        }
    });
}
注意:如果是纯音频会议,只需要在发布数据流时将EMStreamParam中的setVideoOff置为true即可 
      共享桌面时候发布的是桌面流 EMConferenceStream.StreamType.DESKTOP  

设置视频流的参数

如果用户想对发布的音频或者视频流的参数进行调整,可以使用EMStreamParam中的参数进行设置。 只发布音频流,可以设置关闭视频。
设置视频流的分辨率

normalParam.setVideoOff(true);
 normalParam.setVideoHeight(720);   
 normalParam.setVideoWidth(1280);

成员A成功发布数据流后,会议中其他成员会收到
监听类回调EMConferenceListener#onStreamAdded
如果成员B想看成员A的音视频,可以调用subscribe接口进行订阅

@Override
 public void onStreamAdded(final EMConferenceStream stream) {
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            ConferenceMemberView memberView = new ConferenceMemberView(activity);
            // 添加当前view到界面
            callConferenceViewGroup.addView(memberView);
            // 设置当前view的一些状态
            memberView.setUsername(stream.getUsername());
            memberView.setStreamId(stream.getStreamId());
            memberView.setAudioOff(stream.isAudioOff());
            memberView.setVideoOff(stream.isVideoOff());
            memberView.setDesktop(stream.getStreamType() == EMConferenceStream.StreamType.DESKTOP);
                 
            EMClient.getInstance().conferenceManager().subscribe(stream, memberView.getSurfaceView(), new EMValueCallBack<String>() {
                @Override
                public void onSuccess(String value) {
                }
                 
                @Override
                public void onError(int error, String errorMsg) {
                 
                }
            });
        }
    });
}

成员B可以调用调用EMConferenceManager#exitConference接口离开会议
会议中的其他成员会收到回调EMConferenceListener#onMemberExited
注意:当最后一个成员调用leave接口后,会议会自动销毁

EMClient.getInstance().conferenceManager().exitConference()
@Override
 public void onMemberExited(final EMConferenceMember member) {
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            Toast.makeText(activity, member.memberName + " removed conference!", Toast.LENGTH_SHORT).show();
        }
    });
}

进阶功能

取日志

SDK会写入日志文件到本地。日志文件路径如下:sdcard/Android/data/(自己App包名)/(appkey)/core_log/easemob.log;
以Demo为例,通过adb命令获取本地的log:

adb pull /sdcard/Android/data/com.hyphenate.chatuidemo/easemob-demo#chatdemoui/core_log/easemob.log

创建会议并加入

SDK还提供了createAndJoinConference接口创建并加入会议
A调用该接口后,将拥有一个会议实例Conference,同时A将成为该Conference的成员且角色是Admin
用户创建会议时可以设置参数指定是否支持小程序音视频,是否需要在服务器端录制,录制时是否合并流
通过这个AIP创建会议以后,其他人可以通过joinConference通过会议ID,密码方式加入会议
createAndJoinConference可以有以下四种方法去创建,请看以下方法详细介绍

**方法一:参数(会议类型,会议密码,是否支持小程序,是否录制,是否合流, 回调);**
 
 public void createAndJoinConference(final EMConferenceType type, final String password,
                                     final boolean isSupportMiniProgram,final 
                                     boolean recordOnServer,final boolean mergeStream,
                                     final EMValueCallBack<EMConference> callback)
 EMClient.getInstance().conferenceManager().createAndJoinConference(emConferenceType,
                password, true, false, false, new EMValueCallBack<EMConference>() {
                    @Override
                    public void onSuccess(EMConference value) {
                        // 返回当前会议对象实例 value
                        // 可进行推流等相关操作
                        // 运行在子线程中,勿直接操作UI
                    }
     
                    @Override
                    public void onError(int error, String errorMsg) {
                        // 运行在子线程中,勿直接操作UI
                    }
                });
                 
 
** 方法二:参数(会议类型,会议密码,回调);**
 
 public void createAndJoinConference(final EMConferenceType type, final String password,
                                     final EMValueCallBack<EMConference> callback){}
 EMClient.getInstance().conferenceManager().createAndJoinConference(emConferenceType,
                password, new EMValueCallBack<EMConference>() {
                    @Override
                    public void onSuccess(EMConference value) {
                        // 返回当前会议对象实例 value
                        // 可进行推流等相关操作
                        // 运行在子线程中,勿直接操作UI
                    }
     
                    @Override
                    public void onError(int error, String errorMsg) {
                        // 运行在子线程中,勿直接操作UI
                    }
                });
                   
 **方法三:参数(会议类型,会议密码,是否录制,是否合流, 回调);**
 
 public void createAndJoinConference(final EMConferenceType type, final String password,
                                     final  boolean recordOnServer,final boolean mergeStream,
                                     final EMValueCallBack<EMConference> callback) {};
 EMClient.getInstance().conferenceManager().createAndJoinConference(emConferenceType,
                password, false, false, new EMValueCallBack<EMConference>() {
                    @Override
                    public void onSuccess(EMConference value) {
                        // 返回当前会议对象实例 value
                        // 可进行推流等相关操作
                        // 运行在子线程中,勿直接操作UI
                    }
     
                    @Override
                    public void onError(int error, String errorMsg) {
                        // 运行在子线程中,勿直接操作UI
                    }
                });
                   
 **方法四:参数(会议类型,会议密码,房间信息对象,推流信息, 回调);** 
              
 public void createAndJoinConference(final @NonNull EMConferenceType type, final String password,
                                     final EMRoomConfig roomConfig, final EMStreamParam param,
                                     final EMValueCallBack<EMConference> callback){};
 有关EMRoomConfig定义如下:
   /**
    * 会议房间属性类
    * confrTyp   会议类型
    * isSupportVxmini  是否支持小程序
    * isRecord    是否支持录制
    * isMergeRecord  是否支持和流
    * nickName  用户昵称
    * ext     用户头像
    * maxTalkerCount   房间最多主播
    * maxVideoCount    房间最多视频流媒体
    * maxAudienceCount 房间最多观众
    * maxPubDesktopCount  房间最多共享桌面流
  */
  public class EMRoomConfig {
       EMConferenceManager.EMConferenceType confrTyp = EMConferenceManager.EMConferenceType.SmallCommunication;
        private boolean isSupportMiniProgram = false;
        private boolean isRecord = false;
        private boolean isMergeRecord = false;
        private String  nickName = null;
        private String  ext = null;
        private int  maxTalkerCount = -1;
        private int  maxVideoCount = -1;
        private int  maxAudienceCount = -1;
        private int  maxPubDesktopCount = -1;
        private EMLiveConfig liveConfig = null;
           
       public EMRoomConfig(EMConferenceManager.EMConferenceType confrTyp,
                               boolean  isSupportMiniProgram,boolean isRecord,
                               boolean  isMergeRecord , String  nickName,
                               String ext, int maxTalkerCount,
                               int maxVideoCount,int maxAudienceCount,
                               int maxPubDesktopCount)
                               {...};
      }
EMClient.getInstance().conferenceManager().joinConference(confId,
                password,new EMValueCallBack<EMConference>() {
                    @Override
                    public void onSuccess(EMConference value) {
                        // 返回当前会议对象实例 value
                        // 可进行推流等相关操作
                        // 运行在子线程中,勿直接操作UI
                    }
     
                    @Override
                    public void onError(int error, String errorMsg) {
                        // 运行在子线程中,勿直接操作UI
                    }
                });

邀请成员加入会议

SDK没有提供邀请接口,你可以自己实现,比如使用环信IM通过发消息邀请
比如通过发邮件邀请等等。 至于需要发送哪些邀请信息,可以参照SDK中的join接口
目前是需要Conference的confrId和password比如用环信IM发消息邀请,如下所示

final EMMessage message = EMMessage.createTxtSendMessage("邀请你观看直播", to);
message.setAttribute(Constant.EM_CONFERENCE_ID, conference.getConferenceId());
message.setAttribute(Constant.EM_CONFERENCE_PASSWORD, conference.getPassword());
EMClient.getInstance().chatManager().sendMessage(message);

管理员销毁会议

会议中的成员可以调用SDK中的 EMConferenceManager#destoryConference接口销毁会议
会议被销毁以后,会议中的其他成员会收到回调EMConferenceListener#onPassiveLeave

EMClient.getInstance().conferenceManager().destoryConference()
  注意:只有管理员角色可以调用这个接口,可以在会议中显式调用这个接口,强制结束进行中的会议,会议中其他人在EMConferenceListener#onPassiveLeave回调里收到 error为 -411,message为 reason-conference-dismissed,收到这个以后调EMClient.getInstance().conferenceManager().exitConference() 主动退出会议。
EMClient.getInstance().conferenceManager().destroyConference(new EMValueCallBack() {
            @Override
            public void onSuccess(Object value) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(activity, "destroy conference succeed!", Toast.LENGTH_SHORT).show();
                    }
                });
            }

            @Override
            public void onError(int error, String errorMsg) {             
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(activity, "destroy conference failed:"+errorMsg, Toast.LENGTH_SHORT).show();
                    }
                });
            }
        });

设置会议人数限制

通过带EMRoomConfig参数的joinRoom接口加入会议的时候 可以设置
房间最多视频主播数 房间最多视频数 房间最多观众 房间最多共享桌面流等会议信息
默认最多主播是100个,最大视频数是9个,房间最多观众是600个 ,最多共享桌面流2个
设置的人数限制有效,如下所示:

/**
     * \~chinese
     * 加入多人音视频加议房间
     * @param room        会议房间名
     * @param password    会议房间密码
     * @param roletype    当前用户加入时指定角色 (EMConferenceRole类型)
     * @param param       设置会议参数 (EMRoomConfig类型)
     * @param callback    回调函数
     */
    public void joinRoom(final String room ,final String password,final EMConferenceRole roletype ,final EMRoomConfig param, final EMValueCallBack<EMConference> callback)
     
   有关EMRoomConfig定义如下:
   /**
    * 会议房间属性类
    * confrTyp   会议类型
    * isSupportVxmini  是否支持小程序
    * isRecord    是否支持录制
    * isMergeRecord  是否支持和流
    * nickName  用户昵称
    * ext     用户头像
    * maxTalkerCount   房间最多主播
    * maxVideoCount    房间最多视频流媒体
    * maxAudienceCount 房间最多观众
    * maxPubDesktopCount  房间最多共享桌面流
  */
  public class EMRoomConfig {
       EMConferenceManager.EMConferenceType confrTyp = EMConferenceManager.EMConferenceType.SmallCommunication;
        private boolean isSupportMiniProgram = false;
        private boolean isRecord = false;
        private boolean isMergeRecord = false;
        private String  nickName = null;
        private String  ext = null;
        private int  maxTalkerCount = -1;
        private int  maxVideoCount = -1;
        private int  maxAudienceCount = -1;
        private int  maxPubDesktopCount = -1;
        private EMLiveConfig liveConfig = null;
           
       public EMRoomConfig(EMConferenceManager.EMConferenceType confrTyp,
                               boolean  isSupportMiniProgram,boolean isRecord,
                               boolean  isMergeRecord , String  nickName,
                               String ext, int maxTalkerCount,
                               int maxVideoCount,int maxAudienceCount,
                               int maxPubDesktopCount)
                               {...};
      }
注意:设置会议人数限制只有是加入会议的第一个设置才有效,也就是会议的创建者。

获取会议信息

在会议进行中,可以通过getConferenceInfo 方法来查询会议信息,从而可以拿到主播列表,观众人数等信息。

/**
     * \~chinese
     * 查询会议信息
     *
     * @param confId   会议id
     * @param password 会议密码
     * @param callback 获取结果
     */
    public void getConferenceInfo(final String confId, final String password,
                                          final EMValueCallBack<EMConference> callback)
                                          
    
    /**
     * 获取主播列表
     */
    public String[] getTalkers() 
    /**
     * 获取观众总数
     */                                     
    public int getAudienceTotal()

cdn合流推流

多人音视频支持将会议中的音视频流合并成一个流,推送到第三方的cdn直播服务器。
整个合流推流过程包括开启cdn推流,更新推流布局,停止推流。

开启cdn推流

会议的创建者在创建会议时使用EMRoomConfig的接口,可以决定是否开启cdn推流,推流配置EMLiveConfig是EMRoomConfig的一个参数,可设置cdn推流的相关信息,然后调用 创建会议接口,可以开启cdn推流(注意:只有会议创建者才能开启cdn推流,如果会议已经创建好,其他人再调用开启cdn推流无效)。 开启过程如下:

EMCDNCanvas canvas = new EMCDNCanvas(ConferenceInfo.CanvasWidth, ConferenceInfo.CanvasHeight, 0,30,900,"H264");
 String url = PreferenceManager.getInstance().getCDNUrl();
 EMLiveConfig liveConfig = new EMLiveConfig(url, canvas);
 roomConfig.setLiveConfig(liveConfig);
 EMClient.getInstance().conferenceManager().joinRoom(currentRoomname, currentPassword, conferenceRole,roomConfig,
                                                     new EMValueCallBack<EMConference>()                                                    

当EMCDNCanvas设置的width、height为0时,cdn推流为纯音频推流

推流成功后,可以在EMConferenceManager中 getLiveCfgs() 方法可以获取到liveId,liveCfgs存储了所有的推流信息,liveCfgs为一个Map,key为liveId,value为对应的推流Url。

EMLiveConfig可设置的参数如下:

/**
 *  \~chinese
 *  CDN 画布设置,创建会议时使用
 *  width (画布 宽)
 *  height(画布 高)
 * 	bgclr (画布 背景色 ,格式为 RGB 定义下的 Hex 值,不要带 # 号,
 * 	       如 0xFFB6C1 表示浅粉色。默认0x000000,黑色)
 *  fps (推流帧率,可设置范围10-30 )
 *  kpbs (推流码率,单位kbps,width和height较大时,码率需要提高,可设置范围1-5000)
 *  codec (推流编码格式,目前只支持"H264")
 *
 *  \~english
 *  The cdn canvas config
 *  width (Canvas width)
 *  height(Canvas height)
 *  bgclr (Canvas background color, Hex value defined by RGB, no #,
 *         such as 0xFFB6C1 means light pink.Default 0x000000, black))
 *  fps (The fps of cdn live,valid valuei is 10-30)
 *  kpbs (The birateBps of cdn live,the unit is kbps,valid value is 1-5000 )
 *  codec (The codec of cdn live,now only support H264)
 */
public class EMCDNCanvas {
    private  int  width = -1;
    private  int  height = -1;
    private  int  bgclr = 0;
    private  int  fps = -1;
    private  int  kpbs = -1;
    private  String  codec = null;

    public EMCDNCanvas(){

    }
    public EMCDNCanvas(int width, int height, int bgclr,int fps,int kpbs,String codec){
        this.width = width;
        this.height = height;
        this.bgclr = bgclr;
        this.fps = fps;
        this.kpbs = kpbs;
        this.codec  = codec;
    } 
}

/**
 \~chinese
 *  音频录制的配置信息
 *  bps        音频比特率,类型为 EMAudioBpsType枚举;
 *  channels   音频通道数,可选(1,2),类型为ChannelsType枚举;
 *  samples    音频采样率,类型为 EMAudioSamplesType枚举;
 *
 *
 *  \~english
 *  The config of audio record.
 *  bps       audio bit rate,  type is EMAudioBpsType enum;
 *  channels  number of audio channels, optional (1,2), type is ChannelsType enum;
 *  samples   audio sampling rate, type is EMAudioSamplesType enum;
 *
 */

public class EMAudioConfig {
    EMAudioBpsType bps = EMAudioBpsType.BPS_64K;
    ChannelsType channels = ChannelsType.DUAL;
    EMAudioSamplesType samples = EMAudioSamplesType.SAMPLES_16K;

    public enum ChannelsType{
        SINGLE,  //单声道
        DUAL    //双声道
    }

    public EMAudioConfig(EMAudioBpsType bps, ChannelsType channels,EMAudioSamplesType samples){
        this.bps = bps;
        this.channels = channels;
        this.samples = samples;
    }
 }

/**
 *  \~Chinese
 *  CDN推流设置
 *  cdnurl     cdn推流地址
 *  cdnCanvas  画布设置 (cdnCanvas可以缺省)
 *  record     设置是否录制推往CDN的流
 *  audioConfig  设置音频录制的配置信息
 *  liveLayoutStyle 设置CDN推流使用的画布类型
 *
 *  \~English
 *  The CDN push stream config
 *  cdnurl      cdn push stream address
 *  cdnCanvas   canvas settings (cdnCanvas can be default)
 *  record      whether to enable the recording push network CDN to remain
 *  audioConfig   set the audio recording configuration information
 *  liveLayoutStyle  sets the canvas type used by the CDN push stream
 */

public class EMLiveConfig {
    private String cdnurl;
    private EMCDNCanvas cdnCanvas = null;
    private boolean record = false;
    private EMAudioConfig audioConfig = null;

    private EMLiveLayoutStyle liveLayoutStyle = EMLiveLayoutStyle.GRID;

    public EMLiveConfig(){

    }
 }

**获取cdn推流LiveId**

推流成功后,可以在EMConferenceManager中 getLiveCfgs() 方法可以获取到liveId,liveCfgs存储了所有的推流信息,liveCfgs为一个Map,key为liveId,value为对应的推流Url。

/**
     * CDN推流liveID (key为推流liveID,value为推流URL)
     * @return
     */
    public Map<String, String> getLiveCfgs();

更新布局

当用户调用更新布局接口后,cdn推流方式将强制变成CUSTOM模式,所有流的位置信息都由用户自己定义。 更新布局的接口如下:

/**
     * \~chinese
     * CDN 推流更新布局(只有管理员可操作)
     * 用户角色: Admin > Talker > Audience
     * 注意:  更新布局只允许Admin 去操作
     *
     * @param liveId  推流CDN的liveId
     * @param regions EMCanvasRegion布局对象列表
     * @param callback 结果回调
     *
     * \~english
     * CDN pushes to update the layout
     *
     * @param liveId  Push the liveId of CDN
     * @param regions Layout EMCanvasRegion list
     * @param callback Result callback
     */
    public void updateLiveLayout(String liveId ,List<EMLiveRegion> regions , final EMValueCallBack<String> callback){}

EMLiveRegion的结构如下:

/**
 *  \~Chinese
 *  视频流在画布宽高及显示位置等参数
 *  x         在画布横坐标位置
 *  y         在画布纵坐标位置
 *  width     视频流宽度(64~1280)
 *  height    视频流高度(64~1280)
 *  zorder    视频流zorder层位置(最小值为 0(默认值),表示该区域图像位于最下层
 *                              最大值为 100,表示该区域图像位于最上层)
 *  style     视频流显示方式(fit模式或者fill模式)
 *  streamId  视频流ID
 *
 *
 *  \~English
 *  Video streaming in the canvas width and height and display location and other parameters
 *  x         On the horizontal position of the canvas
 *  y         On the vertical position of the canvas
 *  width     Video stream width
 *  height    Video stream height
 *  zorder    Video stream zorder layer location
 *  style     Video stream display mode (fit mode or fill mode)
 *  streamId  The video stream ID
 */
public class EMLiveRegion {
    private int x;
    private int y;
    private int width;
    private int height;
    private int zorder;
    private EMRegionStyle style;
    private String streamId;

    public EMLiveRegion(){}
    public EMLiveRegion(int x,int y, int width, int height, int zorder, EMRegionStyle style, String streamId){
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
        this.zorder = zorder;
        this.style = style;
        this.streamId = streamId;
    }  
    public enum EMRegionStyle {
        FIT, // fit模式
        FILL // fill模式
    }
}

使用方法如下:

List<EMLiveRegion> regionsList = new LinkedList<>();
EMLiveRegion region = new EMLiveRegion();
region.setStreamId(streamInfo.getStreamId());           
region.setStyle(EMLiveRegion.EMRegionStyle.FILL);
region.setX(80);
region.setY(60);
region.setWidth(320);
region.setHeight(640);
region.setZorder(1);
regionsList.add(region);
Map<String, String> livecfgs = EMClient.getInstance().conferenceManager().getLiveCfgs();
String liveId = null;      
Iterator<String> iter = livecfgs.keySet().iterator();
while(iter.hasNext()) {
       liveId = iter.next();
       break;
}
EMClient.getInstance().conferenceManager().updateLiveLayout(liveId,regionsList,new EMValueCallBack<String>();

多路推流

多人音视频支持加入会议后,增加一路推流,只有管理员权限可进行次操作。增加一路推流的api方法如下:

/**
     * \~chinese
     * 增加CDN多路推流
     * @param liveConfig  CDN推流布局配置
     * @param callback 结果回调
     *
     * \~english
     * Add CDN multiplex push stream
     * @param liveConfig  CDN push stream layout configuration
     * @param callback Result callback
     */
    public void addLiveStream(EMLiveConfig liveConfig , final EMValueCallBack<String> callback){}

自定义录制布局

在推流的EMLiveConfig设置里,设record为true,可以开启自定义录制,开启后会把推流到cdn的音视频按照推流布局录制下来。如果推流时未开启,也可以在推流后进行开启/停止自定义录制布局操作。开启/停止自定义录制布局的api如下:

/**
     * \~chinese
     * 开启或停止录制CDN的流
     * @param liveId  CDN推流 liveID
     * @param enable  是否开启录制,true为开启 false为停止
     * @param callback 结果回调
     *
     * \~english
     * Start or stop recording CDN streams
     * @param liveId  CDN liveID push stream
     * @param enable  Whether recording is on or off, true means false or stop
     * @param callback Result callback
     *
     */
    public void enableRecordLivestream(String liveId, boolean enable, final EMValueCallBack<String> callback){}

停止推流

多人音视频支持停止向某一个地址的推流,停止推流接口如下:

/**
     * \~chinese
     * 停止CDN推流
     * @param liveId  推流CDN的liveId
     * @param callback 结果回调
     *
     * \~english
     *@param liveId  Push the liveId of CDN
     *@param callback Result callback
     * Stop the CDN push
     *
     * @param callback Result callback
     */
    public void stopLiveStream(String liveId, final EMValueCallBack<String> callback)

云端录制

通过带EMRoomConfig参数的joinRoom接口加入会议的时候 可以通过
isRecord(是否支持录制)和isMergeRecord(是否支持和流)这两个参数
可以设置是否开始云端录制和合流,如下所示:

/**
     * \~chinese
     * 加入多人音视频加议房间
     * @param room        会议房间名
     * @param password    会议房间密码
     * @param roletype    当前用户加入时指定角色 (EMConferenceRole类型)
     * @param param       设置会议参数 (EMRoomConfig类型)
     * @param callback    回调函数
     */
    public void joinRoom(final String room ,final String password,final EMConferenceRole roletype ,
                         final EMRoomConfig param, final EMValueCallBack<EMConference> callback)
     
   有关EMRoomConfig定义如下:
   /**
    * 会议房间属性类
    * confrTyp   会议类型
    * isSupportVxmini  是否支持小程序
    * isRecord    是否支持录制
    * isMergeRecord  是否支持和流
    * nickName  用户昵称
    * ext     用户头像
    * maxTalkerCount   房间最多主播
    * maxVideoCount    房间最多视频流媒体
    * maxAudienceCount 房间最多观众
    * maxPubDesktopCount  房间最多共享桌面流
  */
  public class EMRoomConfig {
       EMConferenceManager.EMConferenceType confrTyp = EMConferenceManager.EMConferenceType.SmallCommunication;
        private boolean isSupportMiniProgram = false;
        private boolean isRecord = false;
        private boolean isMergeRecord = false;
        private String  nickName = null;
        private String  ext = null;
        private int  maxTalkerCount = -1;
        private int  maxVideoCount = -1;
        private int  maxAudienceCount = -1;
        private int  maxPubDesktopCount = -1;
        private EMLiveConfig liveConfig = null;
           
       public EMRoomConfig(EMConferenceManager.EMConferenceType confrTyp,
                               boolean  isSupportMiniProgram,boolean isRecord,
                               boolean  isMergeRecord , String  nickName,
                               String ext, int maxTalkerCount,
                               int maxVideoCount,int maxAudienceCount,
                               int maxPubDesktopCount)
                               {...};
      }
注意:设置会议人数限制只有是加入会议的第一个设置才有效,也就是会议的创建者。

设置昵称 头像

通过带EMRoomConfig参数的joinRoom接口加入会议的时候 可以通过
nickName(昵称)ext(可设置扩展字段比如 用户头像等)这两个参数
可以设置昵称和头像等扩展字段,如下所示:

/**
     * \~chinese
     * 加入多人音视频加议房间
     * @param room        会议房间名
     * @param password    会议房间密码
     * @param roletype    当前用户加入时指定角色 (EMConferenceRole类型)
     * @param param       设置会议参数 (EMRoomConfig类型)
     * @param callback    回调函数
     */
    public void joinRoom(final String room ,final String password,final EMConferenceRole roletype ,
                         final EMRoomConfig param, final EMValueCallBack<EMConference> callback)
     
   有关EMRoomConfig定义如下:
   /**
    * 会议房间属性类
    * confrTyp   会议类型
    * isSupportVxmini  是否支持小程序
    * isRecord    是否支持录制
    * isMergeRecord  是否支持和流
    * nickName  用户昵称
    * ext     用户头像
    * maxTalkerCount   房间最多主播
    * maxVideoCount    房间最多视频流媒体
    * maxAudienceCount 房间最多观众
    * maxPubDesktopCount  房间最多共享桌面流
  */
  public class EMRoomConfig {
       EMConferenceManager.EMConferenceType confrTyp = EMConferenceManager.EMConferenceType.SmallCommunication;
        private boolean isSupportMiniProgram = false;
        private boolean isRecord = false;
        private boolean isMergeRecord = false;
        private String  nickName = null;
        private String  ext = null;
        private int  maxTalkerCount = -1;
        private int  maxVideoCount = -1;
        private int  maxAudienceCount = -1;
        private int  maxPubDesktopCount = -1;
        private EMLiveConfig liveConfig = null;
           
       public EMRoomConfig(EMConferenceManager.EMConferenceType confrTyp,
                               boolean  isSupportMiniProgram,boolean isRecord,
                               boolean  isMergeRecord , String  nickName,
                               String ext, int maxTalkerCount,
                               int maxVideoCount,int maxAudienceCount,
                               int maxPubDesktopCount)
                               {...};
      }

会议属性

会议属性是会议的状态信息,由一组(key,value)组成 会议中的所有角色成员(管理员、主播、观众)都可以设置/删除会议频道属性 设置的会议属性会通知给会议中的所有人 设置会议属性的api方法如下

/**
     * \~chinese
     * 设置频道属性,该会议中的所有人(包括自己)都会收到{@link EMConferenceListener#onAttributesUpdated}回调.
     * 该方法需要在加入会议后调用.
     *
     * @param key
     * @param value
     * @param callback
     *
     * \~english
     * Set conference attribute,All members in this conference(include myself) will receive a callback
     * in {@link EMConferenceListener#onAttributesUpdated}.
     * this method can only be used after join a conference.
     *
     * @param key
     * @param value
     * @param callback
     */
    public void setConferenceAttribute(@NonNull String key, @NonNull String value, final EMValueCallBack<Void> callback) {}

删除会议属性的api方法如下:

/**
     * \~chinese
     * 删除频道属性,该会议中的所有人(包括自己)都会收到{@link EMConferenceListener#onAttributesUpdated}回调.
     * 该方法需要在加入会议后调用.
     *
     * @param key
     * @param callback
     *
     * \~english
     * Delete conference attribute,All members in this conference(include myself) will receive a callback
     * in {@link EMConferenceListener#onAttributesUpdated}.
     * this method can only be used after join a conference.
     *
     * @param key
     * @param callback
     */
    public void deleteConferenceAttribute(@NonNull String key, final EMValueCallBack<Void> callback) {}

当会议属性信息改变时,会议中的在EMConferenceListener中的 onAttributesUpdated 回调方法收到通知,如下:

public void onAttributesUpdated(EMConferenceAttribute[] attributes) {}

每一个EMConferenceAttribute包括了会议属性中的key,value,以及本次修改的action,action包括ADD、UPDATE、DELETE

海外代理

1v1通话支持不同集群区域的人员通话使用代理,减小延迟;
使用多集群代理需要音视频后台配置IP及端口的映射文件rtcconfig.json,并禁用相关appkey的直连,
启用多集群代理功能需要配置如下方法:

EMOptions options = new EMOptions();
  //开启代理
  options.setUseRtcConfig(true);

私有部署

私有部署设置方法参见私有云sdk集成配置

设置视频流参数

如果用户想对发布的音频或者视频流的参数进行调整,可以进行一些设置
用EMStreamParam中的参数进行设置,只发布音频流,可以设置关闭视频
设置视频流的分辨率 最小码率 最大码率 最大音频码率等,如下所示:

//设置分辨率
   normalParam.setVideoOff(true);
 normalParam.setVideoHeight(720);   
 normalParam.setVideoWidth(1280);

//设置最大 最小码率
 normalParam.setMinVideoKbps(200);
 normalParam.setMaxVideoKbps(300);
 normalParam.setMaxAudioKbps(300);

设置流畅度或者清晰度优先

可以通过以下方法设置视频流畅度优先还是清晰度优先(true 为清晰度优先,false为流畅度优先)。

EMClient.getInstance().callManager().getCallOptions().setClarityFirst(true);

停止发布流

成员A可以调用unpublish接口取消自己已经发布的数据流,操作成功后,会议中的其他成员会收到回调EMConferenceListener#onStreamRemoved(EMConferenceStream stream),将对应的数据流信息移除

@Override
public void onStreamRemoved(final EMConferenceStream stream) {
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            Toast.makeText(activity, stream.getUsername() + " stream removed!", Toast.LENGTH_SHORT).show();
            if (streamList.contains(stream)) {
                removeConferenceView(stream);
            }
        }
    });
}

停止订阅流

成员B如果不想再看成员A的音视频,可以调用SDK接口unsubscribe

EMClient.getInstance().conferenceManager().unsubscribe(emConferenceStream, new EMValueCallBack<String>() {
    @Override
    public void onSuccess(String value) {
    }
    
    @Override
    public void onError(int error, String errorMsg) {
    
    }
});

通话中音视频控制

会议过程中可以做一些音视频进行控制,比如开启关闭音频 视频 切换前后摄像头
具体API如下所示:

// 开启音频传输  
 EMClient.getInstance().conferenceManager().openVoiceTransfer(); 
 // 关闭音频传输  
 EMClient.getInstance().conferenceManager().closeVoiceTransfer();  
 // 开启视频传输  
 EMClient.getInstance().conferenceManager().openVideoTransfer();  
 // 关闭视频传输 
 EMClient.getInstance().conferenceManager().closeVideoTransfer();

PS:以上这四个方法都是修改 stream,群里其他成员都会收到 EMConferenceListener.onStreamUpdate()回调

// 切换摄像头 
 EMClient.getInstance().conferenceManager().switchCamera();  
 // 设置展示本地画面的 view  
 EMClient.getInstance().conferenceManager().setLocalSurfaceView(localView); 
 // 更新展示本地画面 view 
 EMClient.getInstance().conferenceManager().updateLocalSurfaceView(localView);  
 // 更新展示远端画面的 view  
 EMClient.getInstance().conferenceManager().updateRemoteSurfaceView(streamId, remoteView); 

音视频首帧回调

当成员发布流成功,发送第一帧音视频数据时,会触发EMConferenceListener的以下回调函数

/**
     * \~chinese
     * Pub 首帧回调
     * streamId  流ID
     * frameType    the first frame callback type of the stream
     *
     * \~english
     * Pub first frame callback
     * streamId   streamId
     * frameType    the first frame callback type of the stream
     */
    public void onFirstFrameSent(String stremId,StreamFrameType frameType){}

当成员订阅流成功,收到第一帧音视频数据时,会触发EMConferenceListener的以下回调函数

/**
     * \~chinese
     * Sub 首帧回调
     * streamId  流ID
     * frameType    the first frame callback type of the stream
     *
     * \~english
     * Sub first frame callback
     * streamId  streamId
     * state    the first frame callback type of the stream
     *
     */
    public void onFirstFrameRecived(String streamId,StreamFrameType frameType){}

音视频无数据回调

当会议中的成员A因断网或异常退出,而无音视频数据上传时,订阅该流的其他成员会收到EMConferenceListener 中的以下回调通知。

/**
     * \~chinese
     * 订阅流的数据状态回调
     * @param streamId  订阅的流ID
     * @param state  流的视频或音频数据状态
     *
     * \~english
     * A data state callback for a subscription stream
     * @param streamId  StreamId
     * @param state  Stream video or audio data status
     */
    default void onStreamStateUpdated(String streamId,StreamState state){};

该功能需要会议中开启质量统计

/**
     * \~chinese
     * 启用统计
     *
     * @param enable 是否启用统计
     *
     * \~english
     * enable statistics
     * @params enable enable statistics
     */
    public void enableStatistics(boolean enable) {};

弱网监控

在会议中可以实时获取到自己的网络状态,通过EMConferenceListener中的以下回调通知

@Override
  public void onConferenceState(final ConferenceState state) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
            }
        });
    }
    //有以下状态
     STATE_NORMAL,   // 正常状态
     STATE_DISCONNECTION,   // 连接断开
     STATE_RECONNECTION,    // 重新连接
     STATE_POOR_QUALITY,    // 通话质量差

通话质量

通过过程中可以实时获取到自己活着订阅流的通话信息,比如视频宽高 丢包率 帧率等等
具体信息可参考 EMStreamStatistics 该功能需要会议中开启质量统计,如下面的接口:

/**
     * \~chinese
     * 启用统计
     *
     * @param enable 是否启用统计
     *
     * \~english
     * enable statistics
     * @params enable enable statistics
     */
    public void enableStatistics(boolean enable) {};

开启质量统计后,在视频过程中会实时触发EMConferenceListener 中的回调函数 onStreamStatistics
如下所示,从而可以获取到每路流的质量统计信息:

@Override
    public void onStreamStatistics(EMStreamStatistics statistics) {
        EMLog.e(TAG,  "Encode Resolution: " + statistics.getLocalEncodedWidth() + "  " 
        + statistics.getLocalEncodedHeight() + " bps: " +statistics.getLocalVideoActualBps() + 
        "  FPS: " + statistics.getLocalEncodedFps());
    }

监听谁在说话

多人音视频会议可以实时监听谁在说话,该功能需要开启,启动/停止控制如下:

//开始监听说话者,参数为间隔时间
 EMClient.getInstance().conferenceManager().startMonitorSpeaker(int interval);
 
 //停止监听说话者
EMClient.getInstance().conferenceManager().stopMonitorSpeaker();

有人说话时,会议成员会收到如下回调通知

@Override
public void onSpeakers(final List<String> speakers)(){ }

mute远端音视频流

在视频会议过程中可以 mute订阅的流的音频或者视频 方法如下所示:

/**
     * \~chinese
     * mute远端音频
     *
     * \~english
     * Mute remote audio
     *
     * @param mute
     */
    public void muteRemoteAudio(String streamId, boolean mute) {
        mediaManager.muteRemoteAudio(streamId, mute);
    }

    /**
     * ~\chinese
     * mute远端视频
     *
     * \~english
     * Mute remote video
     *
     * @param mute
     */
    public void muteRemoteVideo(String streamId, boolean mute) {
        mediaManager.muteRemoteVideo(streamId, mute);
    }

变声/自定义音频

开启外边音频输入

用户使用自定义音频数据时,需要配置外部输入音频数据的开关,
以及音频采样率,通道数(当前通道数只支持1),开启方式如下(true 为开启,false为不开启)。

EMClient.getInstance().callManager().getCallOptions().setExternalAudioParam(true, 44100,1);

输入音频数据

音频数据采集可参考Demo中的ExternalAudioInputRecord.java类实现,
音频数据的输入必须在会话接通后开始,否则会导致网络阻塞,影响通话质量。
建议用户将音频数据采集的开始放在会话接通的回调里,及callDidAccept回调中

EMClient.getInstance().conferenceManager().publish(normalParam, new EMValueCallBack<String>() {
            @Override
            public void onSuccess(String value) {

                //如果启动外部音频输入 ,启动音频录制
                if(PreferenceManager.getInstance().isExternalAudioInputResolution()){
                    ExternalAudioInputRecord.getInstance().startRecording();
                }
                ....
 }               

音频采集过程参考Demo中的ExternalAudioInputRecord类实现;
音频采集过程开始后,在音频数据采集线程里调用外部输入音频数据接口,具体参考Demo中的实现。

EMClient.getInstance().conferenceManager().inputExternalAudioData(byteBuffer.array(), byteBuffer.capacity());

停止音频输入

会话挂断时,停止音频采集及输入过程

if(PreferenceManager.getInstance().isExternalAudioInputResolution()){
                     ExternalAudioInputRecord.getInstance().stopRecording();
  }

美颜/自定义视频

如果用户需要自己采集特定的数据或者对于数据需要先进行一些处理,可以使用SDK的外部输入数据的方法进行。
例如如果想要使用美颜等功能,需要用户使用系统的摄像头,
然后启动监听系统设备,获取到数据后进行处理,处理后再调用我们输入数据的api发布出去。
使用自定义视频接口如下:开启外边视频输入;
用户使用自定义视频数据时,需要配置外部输入数据数据的开关(true 为开启,false为不开启)。

EMStreamParam normalParam = new EMStreamParam();
normalParam.setUsingExternalSource(true); //设置使用外部视频数据输入
EMClient.getInstance().conferenceManager().publish(normalParam, new EMValueCallBack<String>() {}

输入视频数据

然后就是自己获取视频数据,进行美颜等处理,循环调用以下方法输入数据就行了,
这个调用频率就相当于你的帧率,调用间隔可以自己进行控制,一般最大30帧/秒)
输入视频数据的方法如下:

/**
 *
 * 视频数据的格式是摄像头采集的格式即:NV21 420sp 自己手动传入时需要将自己处理的数据转为 yuv 格式输入
 */
 EMClient.getInstance().conferenceManager().inputExternalVideoData(data, width, height, rotate);

视频水印

在Android系统可以将图片资源设置为视频流的水印。首先将图片资源转换为Bitmap对象,
然后设置WaterMarkOption中的属性,比如位置,分辨率及距离边缘的margin等。

try {
    InputStream in = this.getResources().getAssets().open("watermark.png");
    watermarkbitmap = BitmapFactory.decodeStream(in);
} catch (Exception e) {
    e.printStackTrace();
}
watermark = new WaterMarkOption(watermarkbitmap, 75, 25, WaterMarkPosition.TOP_RIGHT, 8, 8);
//设置水印
EMClient.getInstance().conferenceManager().setWaterMark(watermark);

共享桌面

在android 5.0以上系统中,可以使用外部输入视频数据的方法,将采集的桌面图像数据发布出去。 主要方法如下:

desktopParam = new EMStreamParam();
desktopParam.setAudioOff(true);
desktopParam.setVideoOff(true);
desktopParam.setStreamType(EMConferenceStream.StreamType.DESKTOP);

public void publishDesktop() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            desktopParam.setShareView(null);
        } else {
            desktopParam.setShareView(activity.getWindow().getDecorView());
        }
        EMClient.getInstance().conferenceManager().publish(desktopParam, new EMValueCallBack<String>() {
            @Override
            public void onSuccess(String value) {
                conference.setPubStreamId(value, EMConferenceStream.StreamType.DESKTOP);
                startScreenCapture();
            }

            @Override
            public void onError(int error, String errorMsg) {

            }
        });
    }
    
    
    private void startScreenCapture() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            if (ScreenCaptureManager.getInstance().state == ScreenCaptureManager.State.IDLE) {
                ScreenCaptureManager.getInstance().init(activity);
                ScreenCaptureManager.getInstance().setScreenCaptureCallback(new ScreenCaptureManager.ScreenCaptureCallback() {
                    @Override
                    public void onBitmap(Bitmap bitmap) {
                        EMClient.getInstance().conferenceManager().inputExternalVideoData(bitmap);
                    }
                });
            }
        }
    }
   

具体实现可以参考videocall-android中的ConferenceActivity.java文件

角色管理

申请主播

会议中的观众角色可以向管理员发申请成为主播,管理员可以选择同意或者拒绝
观众申请主持人的接口需要管理员的memId,先通过获取会议属性接口获取到管理员的memName
然后根据memName以及成员加入的回调中获取到的EMConferenceMember,获取到memId接口如下

/**
     * \~chinese
     * 发送上麦请求
     *
     * @param memberId 管理员的memberId(只有管理员可处理上麦请求);
     *
     * \~english
     * Request to be speaker
     *
     * @param  memberId of the admin (only the admin can process the request on the mic);
     */
    public void applyTobeSpeaker(String memberId);

观众发出申请后,管理员将会收到以下回调:

/**
     * \~chinese
     * 请求上麦通知 (只有管理员能收到)
     *
     * \~english
     * Request On wheat notification
     */
    default void onReqSpeaker(String memId,String memName,String nickName);

在回调中,管理员可以选择同意或者拒绝;
同意:先调用改变用户权限的接口,然后调用回复接口;
拒绝:则直接调用回复接口。

修改用户的权限接口如下:

/**
     * \~chinese
     * 用户角色: Admin > Talker > Audience
     * 当角色升级时,用户需要给管理员发送申请,管理通过该接口改变用户接口.
     * 当角色降级时,用户直接调用该接口即可.
     * 注意: 暂时不支持Admin降级自己
     *
     * @param confId   会议id
     * @param member   {@link EMConferenceMember},目前使用memberName进行的操作
     * @param toRole   目标角色,{@link EMConferenceRole}
     * @param callback 结果回调
     *
     * \~english
     * Role: Admin > Talker > Audience
     * When role upgrade, you need to send a request to Admin, only Admin can upgrade a role.
     * When role degrade, you can degrade with this method yourself.
     * Attention: Admin can not degrade self.
     *
     * @param confId   Conference id
     * @param member   {@link EMConferenceMember}
     * @param toRole   Target role,{@link EMConferenceRole}
     * @param callback Result callback
     */
    public void grantRole(final String confId, final EMConferenceMember member, final EMConferenceRole toRole,
                          final EMValueCallBack<String> callback);

回复接口如下:

/**
     * \~chinese
     * 管理员处理 上麦请求
     * 注意:只允许Admin 去操作
     *
     * @param memberId  请求者 memberId;
     * @param accept   true 代表同意  false 代表不同意
     *
     * \~english
     * Admin handle to be speak requests
     *
     * @param memberId  requestor memberId;
     * @param accept   true means agree , false means disagree
     * */
    public void handleSpeakerApplication(String memberId, boolean accept);

申请管理员

会议中的主播角色可以向管理员发申请成为管理员,
管理员可以选择同意或者拒绝,
成为管理员后,各管理员之间的权限是相同的
主播申请主持人的接口需要管理员的memId,
先通过获取会议属性接口获取到管理员的memName,
然后根据memName以及成员加入的回调中获取到的EMConferenceMember,
获取到管理员memId。接口如下:

/**
     * \~chinese
     * 发送申请管理员请求
     *
     * @param memberId 管理员的memberId;
     *
     * \~english
     * Request to be admin
     *
     * @param memberId of the admin;
     */
    public void applyTobeAdmin(String memberId);

主播发出申请后,管理员将会收到以下回调:

/**
     * \~chinese
     * 请求成为管理员通知(只有管理员能收到)
     *
     * \~english
     * Request administrator notification
     */
    default void onReqAdmin(String memId,String memName,String nickName){}

在回调中,管理员可以选择同意或者拒绝;
同意:先调用改变用户权限的接口,然后调用回复接口;
拒绝:则直接调用回复接口。

修改用户的权限接口如下:

/**
     * \~chinese
     * 用户角色: Admin > Talker > Audience
     * 当角色升级时,用户需要给管理员发送申请,管理通过该接口改变用户接口.
     * 当角色降级时,用户直接调用该接口即可.
     * 注意: 暂时不支持Admin降级自己
     *
     * @param confId   会议id
     * @param member   {@link EMConferenceMember},目前使用memberName进行的操作
     * @param toRole   目标角色,{@link EMConferenceRole}
     * @param callback 结果回调
     *
     * \~english
     * Role: Admin > Talker > Audience
     * When role upgrade, you need to send a request to Admin, only Admin can upgrade a role.
     * When role degrade, you can degrade with this method yourself.
     * Attention: Admin can not degrade self.
     *
     * @param confId   Conference id
     * @param member   {@link EMConferenceMember}
     * @param toRole   Target role,{@link EMConferenceRole}
     * @param callback Result callback
     */
    public void grantRole(final String confId, final EMConferenceMember member, final EMConferenceRole toRole,
                          final EMValueCallBack<String> callback);

回复接口如下:

/**
     * \~chinese
     * 管理员处理 申请管理员请求
     * 注意: 只允许Admin 去操作
     *
     * @param memberId  请求者 memberId;
     * @param accept   true 代表同意  false 代表不同意
     *
     * \~english
     * Admin handle to be admin requests
     *
     * @param memberId  requestor memberId;
     * @param accept   true means agree false means disagree
     * */
    public void handleAdminApplication(String memberId, boolean accept);

管理员踢人

管理员可以调用kickMember api强制将成员从会议中移除。

注意:只有管理员角色可以调用踢人接口,可以在会议中踢走主播被踢的主播在EMConferenceListener#onPassiveLeave回调里
收到  error为 -412,message为 reason-beenkicked 收到这个以后调用 EMClient.getInstance().conferenceManager().
exitConference()主动退出会议。
/**
     * \~chinese
     * 踢走会议中存在的主播
     * 用户角色: Admin > Talker > Audience
     * 注意: 踢人只允许Admin 去操作,Admin 不能踢自己
     *
     * @param confId   会议id
     * @param members  移除的主播列表
     * @param callback 结果回调
     *
     * \~english
     * Remove talkers from the Conference
     *
     * @param confId   Conference id
     * @param members  Removed list of talkers
     * @param callback Result callback
     */
    public void kickMember(final String confId, final List<String> members, final EMValueCallBack<String> callback)

调用踢人接口,会议中被踢的主播收到回调

EMConferenceListener#onPassiveLeave(final int error, final String message)

全体静音/管理员指定成员静音

管理员可以对会议中的指定成员进行静音/解除静音设置
被指定成员可以是主播也可以是管理员
设置后,被指定成员将静音/解除静音。只有管理员可以调用此接口。
接口API如下:

/**
     * \~chinese
     * 发送静音命令
     * 注意: 只允许Admin 去操作
     *
     * @param memberId  memberId  被静音的成员的memberId;
     *
     * \~english
     * Send the mute command
     *
     * @param memberId  MemberId of the member being mute (only admin can sends mute command);
     *
     * */
    public void muteMember(String memberId);
/**
     * \~chinese
     * 发送解除静音命令
     * 注意: 只允许Admin 去操作
     *
     * @param memberId  被取消静音的成员的memberId;
     *
     * \~english
     * Send the unmute command
     *
     * @param memberId  Send the memberId of the member whose unmute command is cancelled;
     *
     *
     * */
    public void unmuteMember(String memberId);

管理员调用此接口后,被指定的成员将收到静音状态的回调,回调函数如下

/**
     * \~chinese
     * 被静音通知
     *
     * \~english
     * Be muted notification
     */
    default void onMute(String adminId, String memId){};
/**
     * \~chinese
     * 被取消静音通知
     *
     * \~english
     * Be unmuted notification
     */
    default void onUnMute(String adminId, String memId){};

本身角色变更回调

在会议中如果 申请主播或者管理员成功等角色改变 则触发EMConferenceListener中以下角色变更回调
回调中参数为自己目前的角色

@Override
 public void onRoleChanged(EMConferenceManager.EMConferenceRole role) {
        EMLog.i(TAG, "onRoleChanged, role: " + role);
 }

管理员变更回调

在会议中如果其他人新增管理员或者移除管理员则会触发 EMConferenceListener中以下回调函数

/**
     * 主持人增加回调
     *
     * @param streamId
     */
    @Override
    public void onAdminAdded(String streamId){
       ...
    }
    
    /**
     * 主持人移除回调
     *
     * @param streamId
     */
    @Override
    public void onAdminRemoved(String streamId) {
     ...
    }

客户端api

多人视频会议的API包括以下接口

  • EMConferenceManager 会议管理单例类
  • EMConference 会议实体类
  • EMConferenceListener 会议监听类
  • EMConferenceMember 会议人员实体类
  • EMConferenceStream 会议流实体类
  • EMStreamParam 会议流参数类
  • EMStreamStatistics 音视频通话统计信息实体类
  • EMConferenceAttribute 会议属性实体类
  • EMRoomConfig 会议房间属性类
  • EMLiveConfig CDN推流设置类
  • EMAudioConfig 音频录制的配置信息
  • EMCDNCanvas CDN画布设置类
  • EMLiveRegion 视频流在画布宽高及显示位置等参数类
EMConferenceManager
方法 描述
joinRoom 加入会议房间
createAndJoinConference 创建并加入会议
isCreator是否为会议创建者
addConferenceListener 增加会议监听
removeConferenceListener 移除会议监听
startAudioMixing 开启本地伴音功能
stopAudioMixing 停止本地伴音功能
adjustAudioMixingVolume 设置伴奏音量
getConferenceInfo 获取会议信息
joinConference 加入会议房间
updateLiveLayout 更新CDN推流布局
stopLiveStream 停止CDN推流
addLiveStream 增加CDN旁路推流
stopLiveStream 停止CDN推流
enableRecordLivestream 开启或停止录制CDN的流
joinConferenceWithTicket 通过Ticket加入会议
inviteUserToJoinConference 邀请其他人加入会议
grantRole 改变会议角色
kickMember 会议中踢人
muteAll 设置全体静音
setConferenceAttribute 设置会议属性
deleteConferenceAttribute 删除会议属性
destroyConference 销毁会议
exitConference 退出会议
publish 开始推流
unpublish 停止推流
subscribe 订阅流
updateSubscribe 更新订阅流
unsubscribe 取消订阅流
setWaterMark 设置水印
clearWaterMark 取消水印
inputExternalAudioData 外部属于音频
inputExternalVideoData 外部视频输入
startMonitorSpeaker 开启正在说话监听器
stopMonitorSpeaker 停止正在说话监听器
setLocalSurfaceView 设置本地视频视图
updateLocalSurfaceView 更新本地视频视图
updateRemoteSurfaceView 更新远端视图
switchCamera 切换前后置摄像头
closeVideoTransfer 停止视频传输
openVideoTransfer 恢复视频传输
closeVoiceTransfer 停止音频传输
openVoiceTransfer 恢复音频传输
muteRemoteAudio mute远端音频
muteRemoteVideo mute远端视频
enableStatistics 开始会议质量统计
getConferenceMemberList 获取当前会议成员
getAvailableStreamMap 获取当前会议可订阅Stream
getSubscribedStreamMap 获取当前会议已订阅 Stream
setLocalVideoViewMirror 设置本地视频view镜像
setRotation 设置视频会议中Camera采集到的VideoFrame的rotation
createWhiteboardRoom 创建白板房间
destroyWhiteboardRoom 销毁白板房间
joinWhiteboardRoomWithId 通过白板id加入房间
joinWhiteboardRoomWithName 通过白板名称加入房间
updateWhiteboardRoomWithRoomId 通过白板id修改白板操作权限
applyTobeSpeaker 发送上麦请求
handleSpeakerApplication 管理员处理上麦请求
applyTobeAdmin 发送申请管理员请求
applyTobeAdmin 管理员处理申请管理员请求
muteMember 发送静音命令
unmuteMember 发送解除静音命令
EMConference
属性 描述
PubStreamId pub流id
ConferenceId 会议id
ConferenceType 会议类型
Admins 会议中的管理员列表
PubStreamId pub流id
Talkers 会议中的主播列表
RecordOnServer 是否开启云端录制
AudienceTotal 会议中的观众人数
PubStreamId pub流id
PubStreamId pub流id
PubStreamId pub流id
Extension 会议扩展信息
EMConferenceListener
方法 描述
onMemberJoined 成员加入会议
onMemberExited 成员离开会议
onStreamAdded 有新的成员推流
onStreamUpdate 有成员更新自己的推流
onPassiveLeave 被动离开会议
onAdminAdded 管理员增加通知
onAdminRemoved 管理员移除通知
onApplyAdminRefused 申请管理员失败通知(只有申请管理者收到)
onApplySpeakerRefused 申请主播失败通知(只有申请管理者收到)
onPubStreamFailed pub 流失败
onUpdateStreamFailed update 流失败
onConferenceState 会议状态通知回调
onStreamStatistics 统计信息回调
onStreamSetup 推本地流或订阅成员流成功回调
onStreamStateUpdated 订阅流的数据状态回调
onSpeakers 当前说话者回调
onReceiveInvite 收到会议邀请
onRoleChanged 当前登录用户角色被管理员改变
onReqSpeaker 请求成为主播通知
onReqAdmin 请求成为管理员通知
onMuteAll 被全体静音
onMute 被静音通知
onUnMute 被取消静音通知
onGetLivecfg 获取直播推流CDN 信息
onGetLivecfg 获取直播推流CDN 信息
onGetLocalStreamId 获取自己StreamId
onPubDesktopStreamFailed 发布共享桌面流失败回调
onFirstFrameSent Pub 首帧回调streamId流ID
onFirstFrameRecived Pub 首帧回调
onFirstFrameSent Sub 首帧回调
EMConferenceMember
属性 描述
memberName 会议成员Name
memberId 会议成员Id
nickName 会议成员昵称
extension 会议成员扩展字段
EMConferenceStream
属性 描述
StreamId 流id
StreamName 流Name
MemberName 流的所属成员的MemberName
Username 流的所属成员的Username
Extension 流的扩展字段
VideoOff 流视频是否关闭
AudioOff 流音频是否关闭
StreamType 流的类型
EMStreamParam
属性 描述
name 推流配置名称
videoOff 是否关闭视频
audioOff 是否静音
useBackCamera 使用后置摄像头
videoHeight 视频高度
videoWidth 视频宽度
extension 扩展字段
maxAudioKbps 设置最大音频比特率
minVideoKbps 设置最小的网络带宽
maxVideoKbps 最大视频码率
streamType stream 类型
shareView 分享的view
audioSampleRate 设置音频采样频率
EMStreamStatistics

音视频通话统计信息实体类,具体请看EMStreamStatistics 的详细介绍

EMConferenceAttribute

会议属性设置类,具体请看EMConferenceAttribute 的详细介绍

EMRoomConfig

会议房间属性类 ,具体请看EMRoomConfig 的详细介绍

EMLiveConfig

CDN推流设置类,具体请看EMLiveConfig 的详细介绍

EMAudioConfig

自定义录制格式定义类,具体请看EMAudioConfig 的详细介绍

EMCDNCanvas

CDN 画布设置类,具体请看EMCDNCanvas 的详细介绍

EMLiveRegion

视频流在画布宽高及显示位置等参数类,具体请看EMLiveRegion 的详细介绍