为满足不同场景需求,3.5.0版本开始将实时音视频会议划分了不同的类型,不同类型对应了不同场景,使你能够轻松地将实时音视频功能集成到你的应用或者网站中。
可以创建以下几种类型的会议:
1.Communication:普通通信会议,最多支持参会者6人,会议里的每个参会者都可以自由说话和发布视频,该会议类型在服务器不做语音的再编码,音质最好,适用于远程医疗,在线客服等场景;
2.Large Communication:大型通信会议,最多参会者30人,会议里的每个参会者都可以自由说话,最多支持6个人发布视频,该会议模式在服务器做混音处理,支持更多的人说话,适用于大型会议等场景;
3.Live:互动视频会议,会议里支持最多6个主播和600个观众,观众可以通过连麦与主播互动,该会议类型适用于在线教育,互动直播等场景;
240p:150k~400kbps,推荐 300kbps 480p:300k~1Mbps,推荐 500kbps 720p:900k ~2.5Mbps,推荐 1Mbps 1080p: 2M ~ 5Mbps,推荐 3Mbps
SDK 能够支持音频和视频通信。创建音视频通信的过程简单来说,可以分为以下几步:
1. 观众Audience:只能观看收听音视频,即只能订阅流 2. 主播Speaker:能上传自己的音视频,能观看收听其他主播的音视频,即能发布流和订阅流 3. 管理员Admin:能创建会议,销毁会议,移除会议成员,切换其他成员的角色,订阅流,发布流 注意: >> 每个人必须调用join接口成功后,才算是加入会议(即成为会议成员)。会议成员才允许进行其他操作比如订阅流、发布流等 >> 成员如果想改变自己角色,必须想办法通知管理员,只有管理员才能修改
1. Communication:普通通信会议,最多支持参会者6人,成员都可以自由说话和发布视频,成员角色Speaker 2. Large Communication:大型通信会议,最多参会者30人,成员都可以自由说话和发布视频,成员角色Speaker 3. Live:互动视频会议,会议里支持最多6个主播和600个观众
如何使用SDK实现多人实时音视频会议
Communication和Large Communication除了最大成员数不一样,流程几乎是一样的。以下是从创建会议到离开会议完整的流程讲解:
进入会议之前,调用EMConferenceManager#addConferenceListener(EMConferenceListener listener)
方法指定回调监听,成员加入或离开会议,数据流更新等
注意:该回调监听中的所有方法运行在子线程中,请勿在其中操作UI
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);
// 在Activity#onDestroy()中移除监听
EMClient.getInstance().conferenceManager().removeConferenceListener(conferenceListener);
SDK没有提供单独的创建接口,只提供了一个createAndJoin接口,A调用该接口后,将拥有一个会议实例Conference,并且A已经是Conference的成员且角色是Admin
EMClient.getInstance().conferenceManager().createAndJoinConference(emConferenceType,
conference-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);
注意: 使用环信IM邀请多个人时,建议使用群组消息,如果使用单聊发消息请注意每条消息中间的时间间隔,以防触发环信的垃圾消息防御机制
用户B解析出邀请消息中带的confrId和password,调用SDK的join接口加入会议,成为会议成员且角色是Speaker.
EMClient.getInstance().conferenceManager().joinConference(conferenceId, password, new
EMValueCallBack<EMConference>() {
@Override
public void onSuccess(EMConference value) {
// 返回当前会议对象实例 value
// 可进行推流等相关操作
// 运行在子线程中,勿直接操作UI
}
@Override
public void onError(int error, String errorMsg) {
// 运行在子线程中,勿直接操作UI
}
});
用户B成功加入会议后,会议中其他成员会收到回调EMConferenceListener#onMemberJoined(EMConferenceMember member)
@Override
public void onMemberJoined(final EMConferenceMember member) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(activity, member.memberName + " joined conference!", Toast.LENGTH_SHORT).show();
}
});
}
成员A和成员B都有发布流的权限,可以调用SDK的publish接口发布流,该接口用到了EMStreamParam参数,你可以自由配置,比如是否上传视频,是否上传音频,使用前置或后置摄像头,视频码率,显示视频页面等等
pirvate void pubLocalStream() {
EMStreamParam param = new EMStreamParam();
param.setStreamType(EMConferenceStream.StreamType.NORMAL);
param.setVideoOff(false);
param.setAudioOff(false);
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即可
成员A成功发布数据流后,会议中其他成员会收到监听类回调EMConferenceListener#onStreamAdded(EMConferenceStream stream)
,如果成员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) {
}
});
}
});
}
当成员A对自己的数据流做以上操作成功后,会议中的其他成员会收到回调EMConferenceListener#onStreamUpdate(EMConferenceStream stream)
@Override
public void onStreamUpdate(final EMConferenceStream stream) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(activity, stream.getUsername() + " stream update!", Toast.LENGTH_SHORT).show();
// 更新当前stream所对应View
updateConferenceMemberView(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) {
}
});
成员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调用SDK接口leave离开会议,会议中的其他成员会收到回调EMConferenceListener#onMemberExited(EMConferenceMember member)
注意:当最后一个成员调用leave接口后,会议会自动销毁
@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();
}
});
}
互动视频会议的基本操作(创建、邀请人、发布流、取消发布流、订阅流、取消订阅流、更新发布流程、离开)对应的接口和回调同通信会议是一样的。也可以说 互动视频会议是在通信会议的基础上,增加了角色管理功能,以下着重讲解互动视频会议中的角色管理相关知识点
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
EMClient.getInstance().conferenceManager().grantRole(conference.getConferenceId()
, new EMConferenceMember(jid, null, null)
, EMConferenceManager.EMConferenceRole.Talker, new EMValueCallBack<String>() {
@Override
public void onSuccess(String value) {
EMLog.i(TAG, "changeRole success, result: " + value);
}
@Override
public void onError(int error, String errorMsg) {
EMLog.i(TAG, "changeRole failed, error: " + error + " - " + errorMsg);
}
});
成员角色改变后,被改变的成员会在接口EMConferenceManager#onRoleChanged(EMConferenceManager.EMConferenceRole role)
中收到回调
@Override public void onRoleChanged(EMConferenceManager.EMConferenceRole role) { EMLog.i(TAG, "onRoleChanged, role: " + role); currentRole = role; if (role == EMConferenceManager.EMConferenceRole.Talker) { // 管理员把当前用户角色更改为主播,可以进行publish本地流等操作 } else if (role == EMConferenceManager.EMConferenceRole.Audience) { // 管理员把当前用户角色改变为观众 } }
4. 角色从Audience变为Speaker,成员就可以发布数据流了
注意: >> MemberName和UserName是两个不同的概念,UserName是环信ID,MemberName是环信AppKey和环信ID拼接成的字符串,可通过接口EasyUtils#getMediaRequestUid(String appKey, String username)获取 >> 接口中的MemberName参数都是一样的类型
// 开启音频传输
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);
// 开始监听说话者,参数为间隔时间
EMClient.getInstance().conferenceManager().startMonitorSpeaker(300);
// 停止监听说话者
EMClient.getInstance().conferenceManager().stopMonitorSpeaker();