差别
这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录 前一修订版 后一修订版 | 前一修订版 | ||
im:android:basics:multiuserconference [2018/09/25 02:57] jk |
im:android:basics:multiuserconference [2020/06/25 02:28] allenwang 移除 |
||
---|---|---|---|
行 1: | 行 1: | ||
- | ====== 多人音视频会议功能 ====== | + | ====== 多人音视频会议 ====== |
------ | ------ | ||
行 5: | 行 5: | ||
===== 产品简介 ===== | ===== 产品简介 ===== | ||
- | 为满足不同场景需求,3.5.0版本开始将实时音视频会议划分了不同的类型,不同类型对应了不同场景,使你能够轻松地将实时音视频功能集成到你的应用或者网站中。 | + | 多人音视频采用的是媒体流发布和订阅的技术架构。发布是指参会者发布媒体流(即发言,包括视频流和音频流)到服务器,其他人收到发布事件然后去订阅拉取媒体流。 |
- | + | ||
- | 可以创建以下几种类型的会议: | + | 多人音视频里有管理员,主播和观众三种角色。 |
- | + | * 管理员拥有最高权限,可以发布媒体流,订阅媒体流,设定其他人是主播还是观众 | |
- | 1.Communication:普通通信会议,最多支持参会者6人,会议里的每个参会者都可以自由说话和发布视频,该会议类型在服务器不做语音的再编码,音质最好,适用于远程医疗,在线客服等场景; | + | |
- | + | * 主播可以发布媒体流,订阅媒体流 | |
- | 2.Large Communication:大型通信会议,最多参会者30人,会议里的每个参会者都可以自由说话,最多支持6个人发布视频,该会议模式在服务器做混音处理,支持更多的人说话,适用于大型会议等场景; | + | |
- | + | * 观众只有订阅媒体流权限 | |
- | 3.Live:互动视频会议,会议里支持最多6个主播和600个观众,观众可以通过连麦与主播互动,该会议类型适用于在线教育,互动直播等场景; | + | |
- | + | 环信多人音视频有2种比较常见的使用模式:多人音视频会议和多人音视频互动直播。2种模式使用的是相同的技术架构,开发者可以根据业务场景设置不同的参数,主要区别是: | |
- | ==== 产品特性 ==== | + | |
+ | * 多人音视频会议场景中,在创建会议时指定默认角色为主播,即每个参会者加入会议后都可以发言。开发者也可以根据场景需要,设定一些参会者是以观众角色加入会议。 | ||
+ | |||
+ | * 多人音视频互动直播场景中,在创建会议时指定默认角色为观众,除了管理员(同时也可以是主播)以外,其他人默认是观众。管理员可以设置指定观众成主播实现上麦操作;或设置指定主播为观众,实现下麦操作。 | ||
+ | |||
+ | 多人音视频会议和多人音视频互动直播模式都支持白板、共享桌面。 | ||
+ | |||
+ | ''注意:会议类型将只支持SmallCommunication类型,原LargeCommunication和Live会议类型将弃用。'' | ||
+ | |||
+ | ===== 产品特性 ===== | ||
+ | * 多人音视频会议的音频会议支持千人参会者。视频会议支持最多30个主播,支持最多9个主播发布视频; | ||
* SDK采用模块化设计,极简的 API 设计,方便用户使用单一模块实现特定功能; | * SDK采用模块化设计,极简的 API 设计,方便用户使用单一模块实现特定功能; | ||
行 29: | 行 40: | ||
1080p: 2M ~ 5Mbps,推荐 3Mbps | 1080p: 2M ~ 5Mbps,推荐 3Mbps | ||
- | ==== 音视频通信的简要步骤 ==== | + | ===== 音视频通信的简要步骤 ===== |
- | SDK 能够支持音频和视频通信。创建音视频通信的过程简单来说,可以分为以下几步: | + | SDK端创建和操作音视频会议的过程简单来说,可以分为以下几步: |
- 设置监听 | - 设置监听 | ||
- create: 创建会议 | - create: 创建会议 | ||
行 38: | 行 49: | ||
- sub: 订阅并播放音视频数据流 | - sub: 订阅并播放音视频数据流 | ||
- leave: 离开会议 | - leave: 离开会议 | ||
+ | - destroy:销毁会议 | ||
- | ==== 基本知识 ==== | + | ===== 基本知识 ===== |
* EMConference: 会议实例,保存着与当前会议的相关信息,会议加入成功后会在callback中返回该实例 | * EMConference: 会议实例,保存着与当前会议的相关信息,会议加入成功后会在callback中返回该实例 | ||
行 60: | 行 72: | ||
>> 成员如果想改变自己角色,必须想办法通知管理员,只有管理员才能修改 | >> 成员如果想改变自己角色,必须想办法通知管理员,只有管理员才能修改 | ||
- | * EMConferenceType:多人会议类型 | + | * EMConferenceType:多人会议类型, **目前已经优化,请使用SmallCommunication类型。** |
- | 1. Communication:普通通信会议,最多支持参会者6人,成员都可以自由说话和发布视频,成员角色Speaker | + | 1. SmallCommunication:普通通信会议,最多支持参会者9人,成员都可以自由说话和发布视频,成员角色Speaker |
- | 2. Large Communication:大型通信会议,最多参会者30人,成员都可以自由说话和发布视频,成员角色Speaker | + | 2. LargeCommunication:大型通信会议,最多参会者30人,成员都可以自由说话和发布视频,成员角色Speaker< |
- | 3. Live:互动视频会议,会议里支持最多6个主播和600个观众 | + | 3. LiveStream:互动视频会议,会议里支持最多9个主播和600个观众 |
- | ===== 多人通信会议功能实现 ===== | + | ===== 多人音视频会议功能实现 ===== |
- | 如何使用SDK实现多人实时音视频会议 | + | 以下是从创建会议到离开会议完整的流程讲解: |
- | + | ||
- | Communication和Large Communication除了最大成员数不一样,流程几乎是一样的。以下是从创建会议到离开会议完整的流程讲解: | + | |
==== 注册监听 ==== | ==== 注册监听 ==== | ||
行 137: | 行 147: | ||
==== 用户A创建会议 ==== | ==== 用户A创建会议 ==== | ||
- | SDK没有提供单独的创建接口,只提供了一个createAndJoin接口,A调用该接口后,将拥有一个会议实例Conference,并且A已经是Conference的成员且角色是Admin | + | SDK没有提供单独的创建接口,提供了createAndJoinConference和joinRoom接口,A调用该接口后,将拥有一个会议实例Conference,同时A将成为该Conference的成员且角色是Admin. |
+ | 用户创建会议时可以设置参数指定是否支持小程序音视频,是否需要在服务器端录制,录制时是否合并流。 | ||
+ | |||
+ | **注意:** | ||
+ | |||
+ | 创建加入会议有两组api: createAndJoinConference 和 joinRoom. | ||
+ | joinConference 是通过会议ID,密码方式加入会议,而joinRoom是通过房间名称加入。 | ||
+ | |||
+ | 创建会议成功以后,默认超时时间为三分钟,超过三分钟没有人加入,会议会自动销毁;另外当会议中所有人离开2分钟后,会议也会被销毁。 | ||
+ | joinRoom api在会议不存在会自动去创建。 | ||
+ | |||
+ | | ||
<code java> | <code java> | ||
+ | // 第三个参数为是否支持小程序 | ||
EMClient.getInstance().conferenceManager().createAndJoinConference(emConferenceType, | EMClient.getInstance().conferenceManager().createAndJoinConference(emConferenceType, | ||
- | conference-password, new EMValueCallBack<EMConference>() { | + | password, true, false, false, new EMValueCallBack<EMConference>() { |
@Override | @Override | ||
public void onSuccess(EMConference value) { | public void onSuccess(EMConference value) { | ||
行 154: | 行 176: | ||
} | } | ||
}); | }); | ||
+ | </code> | ||
+ | |||
+ | <code java> | ||
+ | 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 | ||
+ | } | ||
+ | }); | ||
+ | </code> | ||
+ | |||
+ | 用户也可以使用下列接口加入房间。可以指定房间的名称,并且在房间不存在时会自动创建。 | ||
+ | 另外可以在加入会议时指定角色。 | ||
+ | |||
+ | <code java> | ||
+ | /** | ||
+ | * \~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) | ||
</code> | </code> | ||
行 324: | 行 390: | ||
==== 成员B离开会议 ==== | ==== 成员B离开会议 ==== | ||
- | 成员B调用SDK接口leave离开会议,会议中的其他成员会收到回调''EMConferenceListener#onMemberExited(EMConferenceMember member)'' | + | 成员B调用SDK接口离开会议,会议中的其他成员会收到回调''EMConferenceListener#onMemberExited(EMConferenceMember member)'' |
+ | |||
+ | <code java> | ||
+ | EMClient.getInstance().conferenceManager().exitConference() | ||
+ | </code> | ||
注意:当最后一个成员调用leave接口后,会议会自动销毁 | 注意:当最后一个成员调用leave接口后,会议会自动销毁 | ||
行 340: | 行 410: | ||
</code> | </code> | ||
- | ===== 互动视频会议 ===== | + | ==== 成员B销毁会议 ==== |
- | + | ||
- | 互动视频会议的基本操作(创建、邀请人、发布流、取消发布流、订阅流、取消订阅流、更新发布流程、离开)对应的接口和回调同通信会议是一样的。也可以说 互动视频会议是在通信会议的基础上,增加了角色管理功能,以下着重讲解互动视频会议中的角色管理相关知识点 | + | 成员B调用SDK接口销毁会议,会议中的其他成员会收到回调 |
- | + | ''EMConferenceListener#onPassiveLeave(final int error, final String message)'' | |
- | 1. 创建互动视频会议时,接口''EMConferenceManager#createAndJoinConference(EMConferenceType type, String password, EMValueCallBack<EMConference> callback)''传入的type参数是''EMConferenceManager#EMConferenceType#LiveStream'' | + | |
- | + | ||
- | 2. 创建者createAndJoin后的角色是Admin,其他成员第一次调用接口''EMConferenceManager#joinConference(String confId, String password, EMValueCallBack<EMConference> callback)''加入直播后的权限是观众Audience,Audience只能订阅数据流 | + | |
- | + | ||
- | 3. 观众Audience如果想发布数据流 即上麦,需要给管理员发申请。SDK没有提供申请接口,你可以自定义。 | + | |
- | + | ||
- | 管理员如果同意Audience上麦,需要调用接口''EMConferenceManager#grantRole(String confId, EMConferenceMember member, EMConferenceRole toRole, EMValueCallBack<String> callback)''将角色Audience更改为Speaker | + | |
<code java> | <code java> | ||
- | EMClient.getInstance().conferenceManager().grantRole(conference.getConferenceId() | + | EMClient.getInstance().conferenceManager().destoryConference() |
- | , new EMConferenceMember(jid, null, null) | + | </code> |
- | , EMConferenceManager.EMConferenceRole.Talker, new EMValueCallBack<String>() { | + | |
+ | 注意:只有管理员角色可以调用这个接口,可以在会议中显式调用这个接口,强制结束进行中的会议,会议中其他人在EMConferenceListener#onPassiveLeave回调里收到 error为 -411,message为 reason-conference-dismissed,收到这个以后调EMClient.getInstance().conferenceManager().exitConference() 主动退出会议。 | ||
+ | <code java> | ||
+ | EMClient.getInstance().conferenceManager().destroyConference(new EMValueCallBack() { | ||
@Override | @Override | ||
- | public void onSuccess(String value) { | + | public void onSuccess(Object value) { |
- | EMLog.i(TAG, "changeRole success, result: " + value); | + | runOnUiThread(new Runnable() { |
+ | @Override | ||
+ | public void run() { | ||
+ | Toast.makeText(activity, "destroy conference succeed!", Toast.LENGTH_SHORT).show(); | ||
+ | } | ||
+ | }); | ||
} | } | ||
- | + | ||
@Override | @Override | ||
- | public void onError(int error, String errorMsg) { | + | public void onError(int error, String errorMsg) { |
- | EMLog.i(TAG, "changeRole failed, error: " + error + " - " + errorMsg); | + | runOnUiThread(new Runnable() { |
+ | @Override | ||
+ | public void run() { | ||
+ | Toast.makeText(activity, "destroy conference failed:"+errorMsg, Toast.LENGTH_SHORT).show(); | ||
+ | } | ||
+ | }); | ||
} | } | ||
}); | }); | ||
</code> | </code> | ||
- | |||
- | 成员角色改变后,被改变的成员会在接口''EMConferenceManager#onRoleChanged(EMConferenceManager.EMConferenceRole role)''中收到回调 | ||
- | @Override | + | ==== 获取会议信息 ==== |
- | public void onRoleChanged(EMConferenceManager.EMConferenceRole role) { | + | 在会议进行中,可以通过getConferenceInfo 方法来查询会议信息,从而可以拿到主播列表,观众人数等信息。 |
- | EMLog.i(TAG, "onRoleChanged, role: " + role); | + | |
- | currentRole = role; | + | |
+ | <code java> | ||
+ | /** | ||
+ | * \~chinese | ||
+ | * 查询会议信息 | ||
+ | * | ||
+ | * @param confId 会议id | ||
+ | * @param password 会议密码 | ||
+ | * @param callback 获取结果 | ||
+ | */ | ||
+ | public void getConferenceInfo(final String confId, final String password, | ||
+ | final EMValueCallBack<EMConference> callback) | ||
+ | |||
| | ||
- | if (role == EMConferenceManager.EMConferenceRole.Talker) { | + | /** |
- | // 管理员把当前用户角色更改为主播,可以进行publish本地流等操作 | + | * 获取主播列表 |
- | } else if (role == EMConferenceManager.EMConferenceRole.Audience) { | + | */ |
- | // 管理员把当前用户角色改变为观众 | + | public String[] getTalkers() |
+ | /** | ||
+ | * 获取观众总数 | ||
+ | */ | ||
+ | public int getAudienceTotal() | ||
+ | </code> | ||
+ | |||
+ | |||
+ | |||
+ | ==== 会议角色管理 ==== | ||
+ | 会议管理员可以通过下面的接口来更改会议中成员的角色或者在会议中进行踢人。 | ||
+ | 其他成员可以通过会议属性或者IM 消息等方式来跟管理员申请。 | ||
+ | |||
+ | <code java> | ||
+ | /** | ||
+ | * \~chinese | ||
+ | * 用户角色: Admin > Talker > Audience | ||
+ | * 当角色升级时,用户需要给管理员发送申请,管理通过该接口改变用户接口. | ||
+ | * 当角色降级时,用户直接调用该接口即可. | ||
+ | * | ||
+ | * @param confId 会议id | ||
+ | * @param member {@link EMConferenceMember},目前使用memberName进行的操作 | ||
+ | * @param toRole 目标角色,{@link EMConferenceRole} | ||
+ | * @param callback 结果回调 | ||
+ | * | ||
+ | */ | ||
+ | public void grantRole(final String confId, final EMConferenceMember member, final EMConferenceRole toRole, final EMValueCallBack<String> callback) | ||
+ | </code> | ||
+ | |||
+ | ==== 管理员踢人操作 ==== | ||
+ | 管理员可以调用kickMember api强制将成员从会议中移除。 | ||
+ | |||
+ | 注意:只有管理员角色可以调用踢人接口,可以在会议中踢走主播,被踢的主播在EMConferenceListener#onPassiveLeave回调里收到 error为 -412,message为 reason-beenkicked,收到这个以后调用 EMClient.getInstance().conferenceManager().exitConference() 主动退出会议。 | ||
+ | |||
+ | <code java> | ||
+ | /** | ||
+ | * \~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) | ||
+ | </code> | ||
+ | |||
+ | 调用踢人接口,会议中被踢的主播收到回调 ''EMConferenceListener#onPassiveLeave(final int error, final String message)'' | ||
+ | |||
+ | ==== 全体静音/解除全体静音 ==== | ||
+ | 管理员可以对会议进行全体静音/解除全体静音设置,设置后,会议中的主播都将处于静音状态,新加入的主播也将自动处于静音状态。只有管理员可以调用此接口。 | ||
+ | 接口API如下: | ||
+ | <code java> | ||
+ | /** | ||
+ | * \~chinese | ||
+ | * 全体静音 取消全体静音 | ||
+ | * 用户角色: Admin > Talker > Audience | ||
+ | * 注意: 全体静音只允许Admin 去操作 | ||
+ | * | ||
+ | * @param confId 会议id | ||
+ | * @param mute 是否设置静音(ture 为设置全体静音 false 为取消全体静音) | ||
+ | * @param callback 结果回调 | ||
+ | * | ||
+ | * \~english | ||
+ | * All mute cancel all mute | ||
+ | * | ||
+ | * @param confId Conference id | ||
+ | * @param mute Whether to set mute (true to set all mute , false to cancel all mute) | ||
+ | * @param callback Result callback | ||
+ | */ | ||
+ | public void muteAll(final String confId ,final boolean mute, final EMValueCallBack<String> callback); | ||
+ | </code> | ||
+ | |||
+ | 管理员调用此接口后,会议中的主播将收到全体静音状态的回调,回调函数如下 | ||
+ | <code java> | ||
+ | /** | ||
+ | * \~chinese | ||
+ | * 被全体静音 取消全体静音通知 | ||
+ | * | ||
+ | * \~english | ||
+ | * Be or cancel all muted notification | ||
+ | */ | ||
+ | default void onMuteAll(boolean mute){}; | ||
+ | </code> | ||
+ | |||
+ | ==== 指定成员静音/解除静音 ==== | ||
+ | 管理员可以对会议中的指定成员进行静音/解除静音设置,被指定成员可以是主播也可以是管理员。设置后,被指定成员将静音/解除静音。只有管理员可以调用此接口。 | ||
+ | 接口API如下: | ||
+ | <code java> | ||
+ | /** | ||
+ | * \~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); | ||
+ | </code> | ||
+ | |||
+ | <code java> | ||
+ | /** | ||
+ | * \~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); | ||
+ | </code> | ||
+ | |||
+ | |||
+ | 管理员调用此接口后,被指定的成员将收到静音状态的回调,回调函数如下 | ||
+ | <code java> | ||
+ | /** | ||
+ | * \~chinese | ||
+ | * 被静音通知 | ||
+ | * | ||
+ | * \~english | ||
+ | * Be muted notification | ||
+ | */ | ||
+ | default void onMute(String adminId, String memId){}; | ||
+ | </code> | ||
+ | |||
+ | <code java> | ||
+ | /** | ||
+ | * \~chinese | ||
+ | * 被取消静音通知 | ||
+ | * | ||
+ | * \~english | ||
+ | * Be unmuted notification | ||
+ | */ | ||
+ | default void onUnMute(String adminId, String memId){}; | ||
+ | </code> | ||
+ | |||
+ | ==== 观众申请主播 ==== | ||
+ | 会议中的观众角色可以向管理员发申请成为主播,管理员可以选择同意或者拒绝。观众申请主持人的接口需要管理员的memId,先通过获取会议属性接口获取到管理员的memName,然后根据memName以及成员加入的回调中获取到的EMConferenceMember,获取到memId。接口如下 | ||
+ | <code java> | ||
+ | /** | ||
+ | * \~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); | ||
+ | </code> | ||
+ | 观众发出申请后,管理员将会收到以下回调: | ||
+ | <code java> | ||
+ | /** | ||
+ | * \~chinese | ||
+ | * 请求上麦通知 (只有管理员能收到) | ||
+ | * | ||
+ | * \~english | ||
+ | * Request On wheat notification | ||
+ | */ | ||
+ | default void onReqSpeaker(String memId,String memName,String nickName); | ||
+ | </code> | ||
+ | 在回调中,管理员可以选择同意或者拒绝,如果同意,需要调用改变用户权限的接口,然后调用回复接口,如果拒绝,则直接调用回复接口。回复接口如下: | ||
+ | <code java> | ||
+ | /** | ||
+ | * \~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); | ||
+ | </code> | ||
+ | |||
+ | ==== 主播申请管理员 ==== | ||
+ | 会议中的主播角色可以向管理员发申请成为管理员,管理员可以选择同意或者拒绝,成为管理员后,各管理员之间的权限是相同的。主播申请主持人的接口需要管理员的memId,先通过获取会议属性接口获取到管理员的memName,然后根据memName以及成员加入的回调中获取到的EMConferenceMember,获取到管理员memId。接口如下: | ||
+ | <code java> | ||
+ | /** | ||
+ | * \~chinese | ||
+ | * 发送申请管理员请求 | ||
+ | * | ||
+ | * @param memberId 管理员的memberId; | ||
+ | * | ||
+ | * \~english | ||
+ | * Request to be admin | ||
+ | * | ||
+ | * @param memberId of the admin; | ||
+ | */ | ||
+ | public void applyTobeAdmin(String memberId); | ||
+ | </code> | ||
+ | 主播发出申请后,管理员将会收到以下回调: | ||
+ | <code java> | ||
+ | /** | ||
+ | * \~chinese | ||
+ | * 请求成为管理员通知(只有管理员能收到) | ||
+ | * | ||
+ | * \~english | ||
+ | * Request administrator notification | ||
+ | */ | ||
+ | default void onReqAdmin(String memId,String memName,String nickName){} | ||
+ | </code> | ||
+ | 在回调中,管理员可以选择同意或者拒绝,如果同意,需要调用改变用户权限的接口,然后调用回复接口,如果拒绝,则直接调用回复接口。回复接口如下: | ||
+ | <code java> | ||
+ | /** | ||
+ | * \~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); | ||
+ | </code> | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | ===== 用户自定义数据采集及数据处理 ===== | ||
+ | 如果用户需要自己采集特定的数据或者对于数据需要先进行一些处理,可以使用SDK的外部输入数据的方法进行。 | ||
+ | |||
+ | 例如如果想要使用美颜或者变音等功能,需要用户使用系统的摄像头或者麦克风,然后启动监听系统设备,获取到数据后进行处理,处理后再调用我们输入数据的api发布出去。 | ||
+ | |||
+ | 外部视频数据的使用: | ||
+ | 在EMStreamParam中将使用外部数据打开 | ||
+ | <code java> | ||
+ | //需要初始化camera, 监听camera数据 | ||
+ | //处理数据后使用下面的接口将数据输入 | ||
+ | streamParam.setUsingExternalSource(true); | ||
+ | EMClient.getInstance().conferenceManager().inputExternalVideoData(bitmap); | ||
+ | </code> | ||
+ | |||
+ | 外部音频数据的使用: | ||
+ | |||
+ | <code java> | ||
+ | //初始化AudioRecord | ||
+ | try { | ||
+ | audioRecord = new AudioRecord(audioSource, sampleRate, AudioFormat.CHANNEL_IN_MONO, | ||
+ | AudioFormat.ENCODING_PCM_16BIT, bufferSizeInBytes); | ||
+ | } catch (IllegalArgumentException e) { | ||
+ | Log.d(TAG, "AudioRecord ctor error: " + e.getMessage()); | ||
+ | releaseAudioResources(); | ||
+ | return -1; | ||
} | } | ||
+ | | ||
+ | //在单独的线程中读取数据 | ||
+ | int bytesRead = audioRecord.read(byteBuffer, byteBuffer.capacity()); | ||
+ | | ||
+ | //输入处理后的数据 | ||
+ | int ret = | ||
+ | EMClient.getInstance().conferenceManager().inputExternalAudioData(byteBuffer.array(), byteBuffer.capacity()); | ||
+ | </code> | ||
+ | |||
+ | ===== 共享桌面 ===== | ||
+ | 在android 5.0以上系统中,可以使用外部输入视频数据的方法,将采集的桌面图像数据发布出去。 | ||
+ | 主要方法如下: | ||
+ | |||
+ | <code java> | ||
+ | |||
+ | 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); | ||
+ | } | ||
+ | }); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | </code> | ||
+ | |||
+ | 具体实现可以参考IM demo中的ConferenceActivity.java文件 | ||
+ | |||
+ | ===== 设置视频流水印 ===== | ||
+ | 在Android系统可以将图片资源设置为视频流的水印。首先将图片资源转换为Bitmap对象,然后设置WaterMarkOption中的属性,比如位置,分辨率及距离边缘的margin等。 | ||
+ | |||
+ | <code java> | ||
+ | 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); | ||
+ | </code> | ||
+ | |||
+ | ===== 多集群代理 ===== | ||
+ | 多人音视频支持不同集群区域的人员使用代理,减小延迟、丢包率。使用多集群代理需要音视频后台配置IP及端口的映射文件rtcconfig.json,sdk开启相应开关。 | ||
+ | 启用多集群代理功能开关如下,该方法在EMOptions类中,具体如下: | ||
+ | <code java> | ||
+ | public void setUseRtcConfig(boolean useRtcConfig);//true为开启,false为不开启 | ||
+ | </code> | ||
+ | |||
+ | ===== cdn合流推流 ===== | ||
+ | 多人音视频支持将会议中的音视频流合并成一个流,推送到第三方的cdn直播服务器。整个合流推流过程包括开启cdn推流,更新推流布局,停止推流。 | ||
+ | |||
+ | ==== 开启cdn推流 ==== | ||
+ | 会议的创建者在创建会议时使用EMRoomConfig的接口,可以决定是否开启cdn推流,推流配置EMLiveConfig是EMRoomConfig的一个参数,可设置cdn推流的相关信息。开启过程如下: | ||
+ | <code java> | ||
+ | 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); | ||
+ | </code> | ||
+ | |||
+ | EMLiveConfig可设置的参数如下: | ||
+ | |||
+ | <code java> | ||
+ | /** | ||
+ | * \~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 | ||
+ | * CDN推流设置 | ||
+ | * cdnurl cdn推流地址 | ||
+ | * cdnCanvas 画布设置 (cdnCanvas可以缺省) | ||
+ | * liveLayoutStyle | ||
+ | * | ||
+ | * \~English | ||
+ | * The CDN push stream config | ||
+ | * cdnurl cdn push stream address | ||
+ | * cdnCanvas canvas settings (cdnCanvas can be default) | ||
+ | */ | ||
+ | public class EMLiveConfig { | ||
+ | private String cdnurl; | ||
+ | private EMCDNCanvas cdnCanvas = null; | ||
+ | public EMLiveConfig(){ | ||
+ | } | ||
+ | public EMLiveConfig(String cdnurl, EMCDNCanvas cdnCanvas){ | ||
+ | this.cdnurl = cdnurl; | ||
+ | this.cdnCanvas = cdnCanvas; | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | |||
+ | |||
+ | ==== 更新布局 ==== | ||
+ | 当用户调用更新布局接口后,cdn推流方式将强制变成CUSTOM模式,所有流的位置信息都由用户自己定义。 | ||
+ | 更新布局的接口如下: | ||
+ | <code java> | ||
+ | /** | ||
+ | * \~chinese | ||
+ | * CDN 推流更新布局(只有管理员可操作) | ||
+ | * 用户角色: Admin > Talker > Audience | ||
+ | * 注意: 更新布局只允许Admin 去操作 | ||
+ | * | ||
+ | * @param regions EMCanvasRegion布局对象列表 | ||
+ | * @param callback 结果回调 | ||
+ | * | ||
+ | * \~english | ||
+ | * CDN pushes to update the layout | ||
+ | * | ||
+ | * @param regions Layout EMCanvasRegion list | ||
+ | * @param callback Result callback | ||
+ | */ | ||
+ | public void updateLiveLayout(List<EMLiveRegion> regions , final EMValueCallBack<String> callback) | ||
+ | </code> | ||
+ | EMLiveRegion的结构如下: | ||
+ | <code java> | ||
+ | /** | ||
+ | * \~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模式 | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | 使用方法如下: | ||
+ | <code java> | ||
+ | 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); | ||
+ | EMClient.getInstance().conferenceManager().updateLiveLayout(regionsList,new EMValueCallBack<String>(); | ||
+ | </code> | ||
+ | |||
+ | ==== 停止推流 ==== | ||
+ | 多人音视频支持停止向某一个地址的推流,停止推流接口如下: | ||
+ | <code java> | ||
+ | /** | ||
+ | * \~chinese | ||
+ | * 停止CDN推流 | ||
+ | * @param callback 结果回调 | ||
+ | * | ||
+ | * \~english | ||
+ | * Stop the CDN push | ||
+ | * | ||
+ | * @param callback Result callback | ||
+ | */ | ||
+ | private void stopLiveStream(final EMValueCallBack<String> callback) | ||
+ | </code> | ||
- | 4. 角色从Audience变为Speaker,成员就可以发布数据流了 | + | ===== 会议属性设置和获取 ===== |
- | 注意: | + | 在会议中的成员可以设置频道属性,类似于在会议中事件的一个广播,该会议中的所有人(包括自己)都会收到,下面介绍有关会议属性的设置和获取; |
- | >> MemberName和UserName是两个不同的概念,UserName是环信ID,MemberName是环信AppKey和环信ID拼接成的字符串,可通过接口EasyUtils#getMediaRequestUid(String appKey, String username)获取 | + | 会议属性主要设置过程如下: |
- | >> 接口中的MemberName参数都是一样的类型 | + | <code java> |
+ | JSONObject object = new JSONObject(); | ||
+ | object.put("creator",EMClient.getInstance().getCurrentUser()); | ||
+ | object.put("roomName",ConferenceInfo.getInstance().getRoomname()); | ||
+ | EMClient.getInstance().conferenceManager().setConferenceAttribute("whiteBoard", object.toString(), new EMValueCallBack<Void>(); | ||
+ | </code> | ||
+ | |||
+ | 有关会议属性的的获取需要实现EMConferenceListener#onAttributesUpdated 这个回调接口,当有人设置会议属性时,在这个回调函数里面会收到会议属性, | ||
+ | 会议属性的获取过程如下: | ||
+ | <code java> | ||
+ | public void onAttributesUpdated(EMConferenceAttribute[] attributes) { | ||
+ | EMConferenceAttribute conferenceAttribute; | ||
+ | int size = attributes.length; | ||
+ | for (int i = 0; i < size; i++) { | ||
+ | conferenceAttribute = attributes[i]; | ||
+ | String key = conferenceAttribute.key; | ||
+ | String value = conferenceAttribute.value; | ||
+ | |||
+ | //利用获取到的会议属性处理自己的业务逻辑 | ||
+ | JSONObject object = new JSONObject(value); | ||
+ | String creator = object.optString("creator"); | ||
+ | String roomName = object.optString("roomName"); | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
- | ===== 其他方法 ===== | + | ===== 其他方法 ===== |
<code java> | <code java> | ||
// 开启音频传输 | // 开启音频传输 |