差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

两侧同时换到之前的修订记录 前一修订版
后一修订版
前一修订版
rtc:conference:ios [2020/06/29 09:04]
huanxinfudh [基本知识]
rtc:conference:ios [2020/12/25 08:18] (当前版本)
victorli [全体静音/指定成员静音]
行 1: 行 1:
-====== iOS集成多人通话 ======+====== iOS 多人音视频通话 ======
  
------- 
  
 +-----
 +
 +====== 跑通Demo ======
 + ​环信提供开源的多人视频会议项目EMiOSVideoCallDemo,在使用SDK集成App前,您可以参考相关代码
 +===== 示例代码 =====
 +进入[[http://​docs-im.easemob.com/​rtc/​scenario/​meeting#​demo_源码|环信音视频Demo源码下载]]页面,选择iOS端Demo下载,或直接进入github开源网站https://​github.com/​easemob/​videocall-ios
 +===== 前提条件 =====
 +运行Demo前你需要具备以下条件
 +  * mac操作系统10.11以上
 +  * 安装Xcode 11以上
 +  * iPhone设备iPhone 6以上,安装系统iOS 9.0以上
 +===== 运行Demo =====
 +
 +==== Demo代码目录简介 ====
 +目录 VideoCallDemo/​EMiOSVideoCallDemo 中的 Demo 关键类介绍
 +
 +{{:​rtc:​conference:​videocalldemo.png?​400|}}
 +  ​
 +多人音视频Demo主要包含3个target,SharedDesktop和SharedDesktopSetupUI用于实现共享桌面功能,EMiOSVideoCallDemo是会议target,实现类如下:
 +
 +  * RoomJoinViewController 加入房间页面
 +  * ConferenceViewController 视频展示页面
 +  * AccountSettingViewController 个人设置页面
 +  * RoomSettingViewController 房间设置页面
 +  * SpeakerListViewController 主播列表页面
 +  * KickSpeakerViewController 选人下麦页面
 +  * EMStreamView 视频小窗口类
 +  * EMDemoOption 会议管理类,存储会议的设置和单例属性
 +  * ChangeRoleView 管理员处理上麦申请页面
 +  * ProfileVidwController 个人资料页面
 +  * UpdateNicknameViewController 修改昵称页面
 +  * SelectHeadImageViewController 修改头像页面
 +
 +
 +==== 工程设置 ====
 +
 +Demo中的源码不包含SDK,SDK可以从cocospods安装,或者从官网下载,然后拷贝到当前工程下
 +  * 使用cocoapods安装SDK
 +如果当前系统上没有安装cocoapods,需要用户自己安装,安装过程参考 [[https://​guides.cocoapods.org/​using/​getting-started.html#​getting-started|Getting Started with CocoaPods 安装说明]]。
 +
 +修改VideoCallDemo/​EMiOSVideoCallDemo目录下的Podfile文件,添加如下: ​
 +<​code>​
 +pod '​Hyphenate',​ '~> 3.7.1'
 +</​code>​
 +Terminal进入该目录,运行命令:
 +<​code>​
 +pod install
 +</​code>​
 +成功安装后,Terminal 中会显示 Pod installation complete!,此时项目文件夹下会生成podfile目录,Pod已加入工作空间中
 +  * 从官网下载SDK
 +进入环信IM下载页面,选择iOS SDK + Demo下载,目录中包含HyphenateFullSDK目录,该目录下的Hyphenate.framework即为SDK,将SDK拷贝到VideoCallDemo/​EMiOSVideoCallDemo/​目录下。
 +
 +Terminal进入VideoCallDemo/​EMiOSVideoCallDemo/​目录,运行命令:
 +<​code>​
 +pod install
 +</​code>​
 +打开EMiOSVideoCallDemo.xcworkspace工作空间,进入工程TARGETS > Project Name > General > Frameworks, Libraries, and Embedded Content 菜单,将Hyphenate.framework拖拽进来完成添加。为保证动态库的签名和 app 的签名一致,需要将动态库的 Embed 属性设置为 Embed & Sign
 +
 +==== 运行 ====
 +
 +连接iPhone手机,选择目标设备,点击运行
 +
 +====== 快速集成 ======
  
-===== 音视频通信的简要步骤 ===== 
 多人音视频会议主要涉及到的环信SDK头文件如下:​ 多人音视频会议主要涉及到的环信SDK头文件如下:​
 <code objc> <code objc>
行 20: 行 81:
 </​code>​ </​code>​
  
-SDK 能够支持音频和视频通信。创建音视频通信的过程简单来说,可以分为以下几步:+创建音视频通信的过程简单来说,可以分为以下几步:
  
     1. 初始化 SDK,设置监听代理     1. 初始化 SDK,设置监听代理
行 28: 行 89:
     5. sub: 订阅并播放音视频数据流     5. sub: 订阅并播放音视频数据流
     6. leave: 离开会议     6. leave: 离开会议
-===== 基本知识 ​=====+===== 1 环信后台注册appkey ​===== 
 +在开始集成前,你需要注册环信开发者账号并创建后台应用,参见[[http://​docs-im.easemob.com/​im/​ios/​sdk/​prepare#​注册并创建应用|注册并创建应用]] 
 +===== 2 创建项目 ===== 
 +参考以下步骤创建一个iOS 应用项目,如果已有项目,可以直接进行下一步集成。创建过程如下 
 +  * 打开 Xcode 并点击 Create a new Xcode project。 
 +  * 选择项目类型为 Single View App,并点击 Next。 
 +  * 输入项目信息,如项目名称、开发团队信息、组织名称和语言,语言为Object-C,并点击 Next。 
 +  * 选择项目存储路径,并点击 Create。 
 +  * 进入工程设置页面的Signing & Capaabilities菜单,选择 Automatically manage signing,并在弹出菜单中点击 Enable Automatic 
 +===== 3 导入SDK到工程 ===== 
 +集成SDK有两种方法,分别是使用cocoapods和手动导入SDK 
 +==== 使用cocoapods导入SDK ==== 
 +开始前确保你已安装 Cocoapods。 
 +  * 在 Terminal 里进入项目根目录,并运行 pod init 命令。项目文件夹下会生成一个 Podfile 文本文件。 
 +  * 打开 Podfile 文件,修改文件为如下内容。注意将 AppName 替换为你的 Target 名称,并将 version 替换为你需集成的 SDK 版本,如3.7.1。 
 +<​code>​ 
 +target '​AppName'​ do 
 +    pod '​Hyphenate',​ '~> version'​ 
 +end 
 +</​code>​ 
 +  * 在 Terminal 内运行 pod update 命令更新本地库版本。 
 +  * 运行 pod install 命令安装Hyphenate SDK。成功安装后,Terminal 中会显示 Pod installation complete!,此时项目文件夹下会生成一个 xcworkspace 文件。 
 +  * 打开新生成的 xcworkspace 文件。
  
-IEMConferenceManager.h:多人音视频操作接口+==== 手动导入SDK ====
  
-EMConferenceManagerDelegate:会议监听类会回调会议成员变化会议数据流变化等需要在代码中设置监听+  * 将在跑通Demo阶段下载的HyphenateFullSDK下的Hyphenate.framework拷贝到项目工程目录下 
 +  * 打开工程设置/​Genaral菜单下将Hyphenate.framework拖拽到Frameworkslibraries,​and Embedded Content下设置为Embed and Signed
  
-EMConferenceType:多人会议类型,**目前已经进行优化,请使用Communication类型**。 +工程入SDK,需要头文件Hyphenate.h 
- +<​code>​ 
-    1. Communication:普通通信会议,最多支持参会者9人,成员都可以自由说话和发布视频,成员角色Speaker +#import <​Hyphenate/Hyphenate.h>
-    2. Large Communication:大型通信会议,最多参会者30人,成员都可以自由说话和发布视频,成员角色Speaker<​ +
-    3. Live:互动视频会议,会议里支持最多9个主播和600个观众 +
- +
-EMCallConference:会议类,用户可以维护SDK返回的实例,不允许进行copy和new +
- +
-    注意: EMCallConference会出现两个ID属性,分别是callId和confId,​两个ID都是标识符,callId是本地生成,confId是服务器端生成,邀请或者加所需要的均为confId  +
-  +
-EMCallMember:会议成员类,用户可以维护SDK返回的实例,不允许进行copy和new +
-  +
-EMCallStream:会议中的数据流类,包含音频数据和视频数据,用户可以维护SDK返回的实例,不允许进行copy和new +
-  +
-EMStreamParam:自己发布的数据流的各种配置,需要在调发布接口时作为参数传入 +
-  +
-EMConferenceRole:多人会议成员角色 +
- +
-    1观众Audience:只能观看收听音视频,即只能订阅流 +
-    2. 主播Speaker:能上传自己的音视频,能观看收听其他主播的音视频,即能发布流和订阅流 +
-    3. 管理员Admin:能创建会议,销毁会议,移除会议成员,切换其他成员的角色,订阅流,发布流 +
-     +
-    注意: +
-    >> 每个人必须调用join接口成功后,才算是加入会议(即成为会议成员)。会议成员才允许进行其他操作比如订阅流、发布流等 +
-    >> 成员如果想改变自己角色,必须想办法通知管理员,只有管理员才能修改 +
- +
-===== 多人音视频会议功能实现 ===== +
- +
-如何使用SDK实现多人实时音视频会议 +
-  +
-Communication和Large Communication除了最大成员数不一样,流程几乎是一样的。以下是从创建会议到离开会议完整的流程讲解: +
-==== 注册监听 ==== +
- +
-进入会议之前,调用[IEMConferenceManager addDelegate:​delegateQueue:​]方法指定回调监听对象,在该方法中: +
- +
-    ∵ 指定一个 delegate 对象,SDK 通过指定的 delegate 通知应用程序 SDK 的运行事件,如:成员加入或离开会议,数据流更新等。 +
-    ∵ 回调方法在哪个队列中调用 +
- +
-<​code ​objc+
-//​Objective-C +
-[[EMClient sharedClient].conferenceManager addDelegate:​self delegateQueue:​nil];​+
 </​code>​ </​code>​
 +===== 4 添加权限 =====
  
 +应用需要音频设备及摄像头权限,在 info.plist 文件中,点击 + 图标,添加如下信息
 +^Key                                    ^Type    ^Value ​                           ^
 +|Privacy - Microphone Usage Description |String |描述信息,如“环信需要使用您的麦克风” ​  |
 +|Privacy - Camera Usage Description |String |描述信息,如“环信需要使用您的摄像头” ​  |
  
-==== 用户A创建会议 ​====+如果希望在后台运行,还需要添加后台运行音视频权限,在info.plist文件中,点击 + 图标,添加Required background modes ,​Type为Array,在Array下添加元素App plays audio or streams audio/video using AirPlay 
 +===== 5 创建UI ====
 +video视图参考Demo中的EMStreamView,会议控制界面展示参考Demo中的ConferenceViewController
  
-SDK没有提供单独的创建接口,提供了createAndJoinConference和joinRoom接口,A调接口将拥有一个会议实例Conference,同时A将成为该Conference的成员且角色是Admin. +===== 6 初始化SDK ===== 
- +初始化HyhpenateSDK使initializeSDKWithOptions:​接口,需要设置自己的appkey调用如下: 
-用户创建会议时可以设置参数指定是否支持小程序音视频,是否需要在服务器端录制,录制时是否合并流 +<​code>​ 
- +// 这里替换成自己的appkey 
-    注意:  +EMOptions ​*retOpt ​[EMOptions optionsWithAppkey:​@"​easemob-demo#​chatdemoui"​]
-    创建加入会议有两组api:​ createAndJoinConference 和 joinRoom.  +// 这里打开日志输出 
-    joinConference 是通过会议ID,密码方式加入会议,而joinRoom是通过房间名称加入。 +retOpt.enableConsoleLog ​YES
-    创建会议成功以后,默认超时时间为三分钟,超过三分钟没有人加入,会议会动销毁;另外当会议中所有人离开2分钟后会议也会被销毁。 +[[EMClient sharedClient] ​initializeSDKWithOptions:retOpt];
-    joinRoom api在会议不存在会自动去创建。 +
- +
-<​code ​objc+
-//Objective-C +
-- (void)createAndJoinConference +
-+
-    __weak typeof(self) weakself = self; +
-    void (^block)(EMCallConference ​*aCall, NSString *aPassword, EMError *aError) ​^(EMCallConference *aCall, NSString *aPassword, EMError *aError) { +
-        if (aError) { +
-            //​错误提示 +
-            return ​+
-        } +
-             +
-        ​//更新页面显示 +
-    }; +
-         +
-    EMConferenceType type EMConferenceTypeCommunication+
-    //​EMConferenceType type = EMConferenceTypeLargeCommunication;​ +
-     +
-    /*! +
-    *  \~chinese +
-    *  创建并加入会议 +
-    * +
-    *  @param aType             ​会议类型 +
-    *  @param aPassword ​        ​会议密码 +
-    *  @param isRecord ​            ​是否开启服务端录制 +
-    *  @param isMerge ​             录制时是否合并数据流 +
-    *  @param aCompletionBlock ​ 完成的回调 +
-    * +
-    */ +
-    - (void)createAndJoinConferenceWithType:​(EMConferenceType)aType +
-                               ​password:​(NSString *)aPassword +
-                                 ​record:​(BOOL)isRecord +
-                            mergeStream:​(BOOL)isMerge +
-                                completion:​(void (^)(EMCallConference *aCall, NSString *aPassword, EMError *aError))aCompletionBlock;​ +
- +
-    // record 与 mergeStream 根据自己场景需求设置 +
-    ​[[EMClient sharedClient].conferenceManager createAndJoinConferenceWithType:type password:​@"​password"​ record:NO mergeStream:​NO completion:​block]; +
-}+
 </​code>​ </​code>​
 +===== 7 环信ID注册、登录 =====
 +在进行音视频通话前,需要首先登录IM账户,登录过程参见[[http://​docs-im.easemob.com/​im/​ios/​sdk/​basic#​登录|账号登录]]。
  
-如果想在创建会议时指定会议中的最大视频数、最大主播数或开启cdn推流可以使用带RoomCofnig参数的创建会议接口,​此时会议类型使用aType而不是roomconfig中指定的会议型。接口如下: +若您还没有IM账户,需要先注册账户,注册过程参见[[http://​docs-im.easemob.com/​im/​ios/​sdk/​basic#​注册|账号注册]] 
-<​code ​objc+===== 8 音视频功能初始化 ===== 
-/*! +账号登录成功后需要进行音视频通话功能的初始化设置监听类 
- ​* ​ \~chinese +<​code>​ 
- ​* ​ 创建并加入会议 +[[EMClient sharedClient].conferenceManager addDelegate:self delegateQueue:nil];
- * +
- ​* ​ @param aType             ​会议类型 +
- ​* ​ @param aPassword ​        ​会议密码 +
- ​* ​ @param aConfrConfig ​  ​会议属性配置 +
- ​* ​ @param aCompletionBlock ​ 完成的回调 +
- * +
- ​* ​ \~english +
- ​* ​ Create and join a conference +
- * +
- ​* ​ @param aType             The type of the conference +
- ​* ​ @param aPassword ​        The password of the conference +
- ​* ​ @param aConfrConfig ​  The config of conference +
- ​* ​ @param aCompletionBlock ​ The callback block of completion +
- */ +
-- (void)createAndJoinConferenceWithType:(EMConferenceType)aType +
-                               ​password:​(NSString *)aPassword +
-                            confrConfig:​(RoomConfig*)aConfrConfig +
-                             ​completion:(void (^)(EMCallConference *aCall, NSString *aPassword, EMError *aError))aCompletionBlock;+
 </​code>​ </​code>​
- +===== 9 加入会议 ===== 
-==== 加入房间 ​==== +用户可以根据房间名和密码,快速加入一个会议,若该会议不存在,服务器将会自动创建。
- +
-用户可以根据房间名和密码,使用加入房间的接口,快速加入一个会议,若该会议不存在,服务器将会自动创建。使用加入房间的接口,加入会议后,其他会议操作与使用createAndJoin接口加入会议后的操作完全相同+
  
 加入房间使用的接口在IEMConferenceManager.h中,用户加入时可以选择使用的角色为观众还是主播,主播可以发布自己的音视频,观众只能订阅音视频。 加入房间使用的接口在IEMConferenceManager.h中,用户加入时可以选择使用的角色为观众还是主播,主播可以发布自己的音视频,观众只能订阅音视频。
  
-加入房间有基础版和高级版两个API。基础API只能加入默认的会议选项不支持服务录制,使API如下:+加入房间时,可以选择房间的会议类型是否开启服务录制,是否混音以及是否支持小程序。调方法如下:
 <code objc> <code objc>
 //​Objective-C //​Objective-C
--(void)joinRoom:​(NSString*)roomName +    RoomConfigroomConfig = [[RoomConfig alloc] init]; 
-          ​password:​(NSString*)aPassword +    ​roomConfig.confrType = EMConferenceTypeCommunication;​ 
-                    ​role:​(EMConferenceRole)role +    ​roomConfig.nickName = @"​昵称"​
-        completion:​(void (^)(EMCallConference *aCall, EMError *aError))aCompletionBlock+    //​是否开启服务端录制 
-</code> +    ​roomConfig.isRecord = YES; 
-高级版API接口加入房间时,可以选择房间的会议类型,是否开启服务端录制,是否混音以及是否支持小程序。API如下 +    //录制时是否合并数据流 
-<code objc> +    ​roomConfig.isMerge = YES; 
-//Objective-C +    [[[EMClient sharedClient] conferenceManager] ​joinRoom:​roomName password:pswd role:role roomConfig:​roomConfig completion:​^(EMCallConference *aCall, EMError *aError) ​
--(void)joinRoom:(NSString*)roomName +    self.conference = aCall; 
-         password:(NSString*)aPassword +    }];
-                  ​role:(EMConferenceRole)role +
-     roomConfig:(RoomConfig*)roomConfig +
-      ​completion:(void (^)(EMCallConference *aCall, EMError *aError))aCompletionBlock;+
 </​code>​ </​code>​
 +加入房间成功后,返回的回调中有EMCallConference会议实例,需要本地保存下来。
 +
 其中RoomConfig定义如下: 其中RoomConfig定义如下:
 <code objc> <code objc>
行 184: 行 179:
 *  \~chinese *  \~chinese
 *  会议类型 *  会议类型
-* 
-*  \~english 
-*  The  type of conference 
 */ */
 @property (nonatomic) EMConferenceType confrType; @property (nonatomic) EMConferenceType confrType;
行 192: 行 184:
 *  \~chinese *  \~chinese
 *  录制时是否合并数据流 *  录制时是否合并数据流
-* 
-*  \~english 
-* Whether to merge data streams while recording 
 */ */
 @property (nonatomic) BOOL isMerge; @property (nonatomic) BOOL isMerge;
行 200: 行 189:
 *  \~chinese *  \~chinese
 *  是否开启服务端录制 *  是否开启服务端录制
-* 
-*  \~english 
-*  Whether to record data streams 
 */ */
 @property (nonatomic) BOOL isRecord; @property (nonatomic) BOOL isRecord;
行 208: 行 194:
 *  \~chinese *  \~chinese
 *  是否支持微信小程序 *  是否支持微信小程序
-* 
-*  \~english 
-*  Weather to support wechat mini program 
 */ */
 @property (nonatomic) BOOL isSupportWechatMiniProgram;​ @property (nonatomic) BOOL isSupportWechatMiniProgram;​
行 216: 行 199:
 *  \~chinese *  \~chinese
 *  会议中使用的昵称 *  会议中使用的昵称
-* 
-*  \~english 
-*  The nickName userd in conference 
 */ */
 @property (nonatomic) NSString* nickName; @property (nonatomic) NSString* nickName;
行 224: 行 204:
 *  \~chinese *  \~chinese
 *  成员扩展信息 *  成员扩展信息
-* 
-*  \~english 
-*  The extension info of member 
 */ */
 @property (nonatomic) NSString* ext; @property (nonatomic) NSString* ext;
行 232: 行 209:
 *  \~chinese *  \~chinese
 *  会议最大主播数 *  会议最大主播数
-* 
-*  \~english 
-*  The  limit count of talkers 
 */ */
 @property (nonatomic) NSInteger maxTalkerCount ; @property (nonatomic) NSInteger maxTalkerCount ;
行 240: 行 214:
 *  \~chinese *  \~chinese
 *  会议最大视频上传数 *  会议最大视频上传数
-* 
-*  \~english 
-*  The  limit count of video streams 
 */ */
 @property (nonatomic) NSInteger maxVideoCount;​ @property (nonatomic) NSInteger maxVideoCount;​
行 248: 行 219:
 *  \~chinese *  \~chinese
 *  会议最大观众数 *  会议最大观众数
-* 
-*  \~english 
-*  The  limit count of audience 
 */ */
 @property (nonatomic) NSInteger maxAudienceCount;​ @property (nonatomic) NSInteger maxAudienceCount;​
行 256: 行 224:
 *  \~chinese *  \~chinese
 *  cdn 直播推流配置 *  cdn 直播推流配置
-* 
-*  \~english 
-*  The  cdn live config 
 */ */
 @property (nonatomic) LiveConfig* liveConfig; @property (nonatomic) LiveConfig* liveConfig;
行 264: 行 229:
 </​code>​ </​code>​
  
-注:RoomConfig中的ext表示会议成员的扩展信息,一般用于存储头像等信息,与pub流的接口中的ext信息是不同的 +注:RoomConfig中的ext表示会议成员的扩展信息,一般用于存储头像等信息,与pub流的接口中的ext信息是不同的
-==== 管理员A邀请其他人加入会议 ====+
  
 +加入会议后,会议中的其他人员会收到回调通知,如下:
 +<​code>​
 +- (void)memberDidJoin:​(EMCallConference *)aConference
 +               ​member:​(EMCallMember *)aMember
 +{
 +    // aMember中包含加入用户的信息,如ID,昵称等
 +}
 +</​code>​
 +===== 10 发布流 =====
 +成员加入会议后,可以在会议中发布音视频流,发流过程如下:
 +<​code>​
 +EMStreamParam *pubConfig = [[EMStreamParam alloc] init];
 +EMCallLocalView *localView = [[EMCallLocalView alloc] init];
 +//​显示本地视频的页面
 +pubConfig.localView = localView;
 +//​上传本地摄像头的数据流
 + ​[[EMClient sharedClient].conferenceManager publishConference:​[EMDemoOption sharedOptions].conference streamParam:​pubConfig completion:​^(NSString *aPubId, EMError *aError) {
 +// 若发流成功,aError应为nil,否则为失败信息。
 +// 发流成功时,aPubId为本地流的pubId,此时应把localView添加到界面上,​pubId需要在本地保存,​用于操作音视频流
 +}];
 +streamParam参数提供了更丰富的发流选项
 +
 +当成员发流成功后,会议中的其他成员会收到有流加入的回调通知,通知如下:
 +<​code>​
 +//​有新的数据流上传
 +- (void)streamDidUpdate:​(EMCallConference *)aConference
 +              addStream:​(EMCallStream *)aStream
 +{
 +    // aStream中包含了流信息,此时应该订阅流
 +}
 +</​code>​
 +===== 11 订阅流 =====
 +成员收到有数据流加入的通知后,可以订阅该流,订阅后可以接收到流内的音视频,订阅过程如下
 +<​code>​
 +    // remoteView用于在UI上展示对方的视频
 +    EMCallRemoteView *remoteView = [[EMCallRemoteView alloc] init];
 +    //​订阅其他人的数据流,,即订阅当前会议上麦主播的数据流
 +    [[EMClient sharedClient].conferenceManager subscribeConference:​[EMDemoOption sharedOptions].conference streamId:​aStream.streamId remoteVideoView:​remoteView completion:​^(EMError *aError) {
 +        // 若订阅成功,aError应为nil,否则为失败信息。
 +        // 订阅成功后,应把remoteView展示到UI上
 +    }];
 +</​code>​
 +===== 12 退出会议 =====
 +用户退出会议的过程如下:
 +<​code>​
 +[[EMClient sharedClient].conferenceManager leaveConference:​[EMDemoOption sharedOptions].conference completion:​nil];​
 +</​code>​
 +用户退出会议后,会议中的其他成员会收到以下回调通知:
 +<​code>​
 +- (void)memberDidLeave:​(EMCallConference *)aConference
 +                member:​(EMCallMember *)aMember
 +{
 +   // aMember为退出的成员信息
 +}
 +</​code>​
 +====== 进阶功能 ======
 +===== 会议管理 =====
 +==== 取日志 ====
 +SDK会写入日志文件到本地。日志文件路径如下:沙箱Documents/​HyphenateSDK/​easemoblog,以真机为例,获取过程如下:
 +  * 打开Xcode连接设备,前往Xcode --> Window --> Devices and Simulators
 +  * 进入Devices选项卡,在左侧选择目标设备,界面如下:
 +
 +{{:​rtc:​one2one:​fetchlogfile.png?​400|}}
 +
 +日志文件easemob.log文件在下载包内容的AppData/​Library/​Application Support/​HyphenateSDK/​easemobLog目录下
 +==== 创建会议并加入 ====
 +除根据房间名和房间密码加入会议的api外,SDK还提供了直接创建并加入会议的api,接口如下:
 +<code objc>
 +//​Objective-C
 +- (void)createAndJoinConference
 +{
 +    __weak typeof(self) weakself = self;
 +    void (^block)(EMCallConference *aCall, NSString *aPassword, EMError *aError) = ^(EMCallConference *aCall, NSString *aPassword, EMError *aError) {
 +        if (aError) {
 +            //​错误提示
 +            return ;
 +        }
 +            ​
 +        //​更新页面显示
 +    };
 +        ​
 +    EMConferenceType type = EMConferenceTypeCommunication;​
 +    // record 与 mergeStream、isSupportWechatMiniProgram 根据自己场景需求设置
 +    [[EMClient sharedClient].conferenceManager createAndJoinConferenceWithType:​type password:​@"​password"​ record:NO mergeStream:​NO isSupportWechatMiniProgram:​NO completion:​block];​
 +}
 +</​code>​
 +调用该接口后,将拥有一个会议实例Conference,同时成员将成为该Conference的管理员.
 +
 +用户创建会议时可以设置参数指定是否支持小程序音视频,是否需要在服务器端录制,录制时是否合并流、是否支持微信小程序
 +
 +创建会议成功以后,默认超时时间为三分钟,超过三分钟没有人加入,会议会自动销毁;另外当会议中所有人离开2分钟后,会议也会被销毁。
 +
 +如果想在创建会议时指定会议中的最大视频数、最大主播数,或开启cdn推流,可以使用带RoomCofnig参数的创建会议接口,​此时roomconfig中指定的会议类型无效。接口如下:
 +<code objc>
 +/*!
 + ​* ​ \~chinese
 + ​* ​ 创建并加入会议
 + *
 + ​* ​ @param aType             ​会议类型
 + ​* ​ @param aPassword ​        ​会议密码
 + ​* ​ @param aConfrConfig ​  ​会议属性配置
 + ​* ​ @param aCompletionBlock ​ 完成的回调
 + */
 +- (void)createAndJoinConferenceWithType:​(EMConferenceType)aType
 +                               ​password:​(NSString *)aPassword
 +                            confrConfig:​(RoomConfig*)aConfrConfig
 +                             ​completion:​(void (^)(EMCallConference *aCall, NSString *aPassword, EMError *aError))aCompletionBlock;​
 +</​code>​
 +==== 邀请成员加入会议 ====
 SDK没有提供邀请接口,你可以自己实现,比如使用环信IM通过发消息邀请,比如通过发邮件邀请等等。 SDK没有提供邀请接口,你可以自己实现,比如使用环信IM通过发消息邀请,比如通过发邮件邀请等等。
  
-至于需要发送哪些邀请信息,可以参照SDK中的join接口,目前是需要Conference的confrId和password+至于需要发送哪些邀请信息,可以参照SDK中的join接口,可以发送Conference的confrId和password,如果是用joinRoom接口创建的会议,也可以发送房间名和房间密码。
  
 比如用环信IM发消息邀请 比如用环信IM发消息邀请
行 280: 行 353:
     EMConferenceType type = self.type;     EMConferenceType type = self.type;
     NSString *currentUser = [EMClient sharedClient].currentUsername;​     NSString *currentUser = [EMClient sharedClient].currentUsername;​
-    EMTextMessageBody *textBody = [[EMTextMessageBody alloc] initWithText:​[[NSString alloc] initWithFormat:​@"​%@ 邀请你加入直播室: %@", currentUser,​ confrId]];+    EMTextMessageBody *textBody = [[EMTextMessageBody alloc] initWithText:​[[NSString alloc] initWithFormat:​@"​%@ 邀请你加入会议: %@", currentUser,​ confrId]];
     EMMessage *message = [[EMMessage alloc] initWithConversationID:​aUserName from:​currentUser to:​aUserName body:​textBody ext:​@{@"​em_conference_op":​@"​invite",​ @"​em_conference_id":​confrId,​ @"​em_conference_password":​password,​ @"​em_conference_type":​@(type)}];​     EMMessage *message = [[EMMessage alloc] initWithConversationID:​aUserName from:​currentUser to:​aUserName body:​textBody ext:​@{@"​em_conference_op":​@"​invite",​ @"​em_conference_id":​confrId,​ @"​em_conference_password":​password,​ @"​em_conference_type":​@(type)}];​
     message.chatType = EMChatTypeChat;​     message.chatType = EMChatTypeChat;​
行 288: 行 361:
  
     注意:使用环信IM邀请多个人时,建议使用群组消息。如果使用单聊发消息请注意每条消息中间的时间间隔,以防触发环信的垃圾消息防御机制     注意:使用环信IM邀请多个人时,建议使用群组消息。如果使用单聊发消息请注意每条消息中间的时间间隔,以防触发环信的垃圾消息防御机制
-==== 用户B接收到邀请加入会议 ==== +     
- +    被邀请解析出邀请消息中带的confrId和password,调用SDK的join接口加入会议,成为会议成员且角色是Speaker.
- +
-用户B解析出邀请消息中带的confrId和password,调用SDK的join接口加入会议,成为会议成员且角色是Speaker.+
  
 <code objc> <code objc>
行 314: 行 385:
  
 用户B成功加入会议后,会议中其他成员会收到回调[EMConferenceManagerDelegate memberDidJoin:​member:​] 用户B成功加入会议后,会议中其他成员会收到回调[EMConferenceManagerDelegate memberDidJoin:​member:​]
 +    ​
 +==== 管理员销毁会议 ====
 +会议中的管理员可以主动销毁会议,销毁会议过程如下:
 +<​code>​
 +[[EMClient sharedClient].conferenceManager destroyConferenceWithId:​self.conference.confId completion:​nil];​
 +</​code>​
 +会议销毁后,会议中的其他成员将收到会议结束的回调通知
 +<​code>​
 +- (void)conferenceDidEnd:​(EMCallConference *)aConference
 +                  reason:​(EMCallEndReason)aReason
 +                   ​error:​(EMError *)aError
 +{
 +}
 +</​code>​
  
 +==== 设置会议人数限制 ====
 +使用createAndJoinConference接口创建会议或者第一个使用joinRoom接口加入的成员为会议创建者,会议创建者在创建会议时可以设置会议中的最大视频数、主播数、观众数、共享桌面数的上限,默认最大视频数12,最大主播数100,最大观众数600,最大共享桌面数2.创建会议时使用RoomConfig参数指定人数限制,过程如下:
 +<​code>​
 +RoomConfig* roomConfig = [[RoomConfig alloc] init];
 +    roomConfig.maxVideoCount = 6;
 +    roomConfig.maxTalkerCount = 9;
 +    roomConfig.maxAudienceCount = 30;
 +    roomConfig.maxPubDesktopCount = 1;
 +    [[[EMClient sharedClient] conferenceManager] joinRoom:​roomName password:​pswd role:role roomConfig:​roomConfig completion:​block];​
 +</​code>​
 +若加入会议时,超过最大主播数上限,加入会议返回失败,error为EMErrorCallSpeakerFull
  
-<​code ​objc> +若发视频流时,超过最大视频数上限,将收到如下回调通知: 
-//​Objective-C +<​code>​ 
-- (void)memberDidJoin:​(EMCallConference *)aConference ​member:(EMCallMember ​*)aMember+- (void)streamPubDidFailed:​(EMCallConference *)aConference ​error:(EMError*)aError
 { {
-    if ([aConference.callId isEqualToString:​ self.conference.callId]) { 
-        NSString *message = [NSString stringWithFormat:​@"​用户 %@ 加入了会议",​ aMember.memberName];​ 
-        [self showHint:​message];​ 
-    } 
 } }
 </​code>​ </​code>​
 +若发共享桌面流时,超过最大数上限,将收到如下回调通知:
 +<​code>​
 +- (void)DesktopStreamDidPubFailed:​(EMCallConference *)aConference error:​(EMError*)aError
 +{
 +}
 +</​code>​
 +==== 获取会议信息 ====
  
-==== 成员A发布音视频流 ====+在会议进行中,可以通过getConference 方法来查询会议信息,从而可以拿到主播列表,观众人数等信息。
  
 +<code objc>
 +/*!
 + ​* ​ \~chinese
 + ​* ​ 获取会议信息
 + *
 + ​* ​ @param aConfId ​          ​会议ID(EMCallConference.confId)
 + ​* ​ @param aPassword ​        ​会议密码
 + ​* ​ @param aCompletionBlock ​ 完成的回调
 + */
 +- (void)getConference:​(NSString *)aConfId
 +             ​password:​(NSString *)aPassword
 +           ​completion:​(void (^)(EMCallConference *aCall, EMError *aError))aCompletionBlock;​
 +</​code>​
 +==== 会议属性 ====
 +会议属性是会议的状态信息,由一组(key,value)组成。会议中的所有角色成员(管理员、主播、观众)都可以设置/​删除会议频道属性,设置的会议属性会通知给会议中的所有人。
  
-成员A和成员B都有发布流权限,可以调用SDK的publish接口发布流,该接口用到了EMStreamParam参数,你可以自由配置,比是否上传视频,是否上传音频,使用前置或后置摄像头,视频码率,显示视频页面等等+设置会议属性api方法下:
  
 +<​code>​
 +/**
 + * \~chinese
 + * 设置频道属性,​该会议中的所有人(包括自己)都会收到
 + * {@link EMConferenceManagerDelegate#​conferenceAttributeUpdated:​attributeAction:​attributeKey:​}回调.
 + * 该方法需要在加入会议后调用.
 + *
 + * @param attrKey
 + * @param attrValue
 + * @param aCompletionBlock
 + */
 +- (void)setConferenceAttribute:​(NSString *)attrKey
 +                         ​value:​(NSString *)attrValue
 +                    completion:​(void(^)(EMError *aError))aCompletionBlock;​
 +</​code>​
 +
 +删除会议属性的api方法如下:
 <code objc> <code objc>
-//​Objective-C +/** 
-(void)pubLocalStream + * \~chinese 
-{ + * 删除频道属性,​该会议中的所有人(包括自己)都会收到 
-    EMStreamParam *pubConfig = [[EMStreamParam alloc] init]; + {@link EMConferenceManagerDelegate#​conferenceAttributeUpdated:​attributeAction:​attributeKey:​}回调
-    pubConfig.streamName = @"​自己的音视频数据流";​ + * 该方法需要在加入会议后调用
-    pubConfig.enableVideo = YES; //​是否上传视频数据 + * 
-    ​pubConfig.localView = self.localVideoView;​ //​视频显示页面 + * @param aKey 
-    ​pubConfig.isFixedVideoResolution = YES; //​是否固定视频分辨率 + * @param aCompletionBlock 
-    ​pubConfig.videoResolution = EMCallVideoResolution640_480;​ //​视频分辨率 + *
-    ​pubConfig.maxVideoKbps = 0; //​最大视频码率 +(void)deleteAttributeWithKey:​(NSString *)aKey 
-    ​pubConfig.maxAudioKbps = 0; //​最大音频码率 +                    completion:(void(^)(EMError *aError))aCompletionBlock;
-    ​pubConfig.isBackCamera = NO; //​是否使用后置摄像头 +
-         +
-    __weak typeof(selfweakself = self; +
-    ​[[EMClient sharedClient].conferenceManager publishConference:​self.conference streamParam:​pubConfig ​completion:​^(NSString *aPubStreamId, ​EMError *aError) ​+
-        if (aError+
-            //​显示错误信息 +
-        } else { +
-            //​更新页面显示 +
-        } +
-    }]; +
-}+
 </​code>​ </​code>​
  
-    注意:如果是纯音频会议,只需要在发布数据流将EMStreamParam中的enableVideo置为NO即可 +会议属性信息改变,会议中的成员收到以下通知 
-==== 其他成员收到通知并订阅流 ====  +<code objc> 
- +- (void)conferenceAttributeUpdated:​(EMCallConference *)aConference 
-成员成功发布数据流后,会议中其他员会收监听类回调[EMConferenceManagerDelegate streamDidUpdate:​addStream:​]如果需要关注某一条流,可以调用subscribe接口进行订阅+                        attributes:​(NSArray <​EMConferenceAttribute *>​*)attrs 
 +</​code>​ 
 +每一个EMConferenceAttribute包括了会议属性中的key,value,以及本次修改的action,action包括ADD、UPDATE、DELETE 
 +==== cdn合流推流 ==== 
 +多人音视频支持将会议中的音视频流合并一个流,推送第三方的cdn直播服务器。整个合流推流过程包括开启cdn推流更新推布局停止推流。
  
 +== 开启cdn推流 ==
 +会议的创建者在创建会议时使用RoomConfig的接口,可以决定是否开启cdn推流,推流配置LiveConfig是RoomConfig的一个参数,可设置cdn推流的相关信息。开启过程如下:
 <code objc> <code objc>
-//​Objective-C +LiveConfigliveconfig = [[LiveConfig alloc] init]; 
-- (void)streamDidUpdate:​(EMCallConference ​*)aConference addStream:​(EMCallStream *)aStream +CDNCanvascanvas ​= [[CDNCanvas ​alloc] ​init]; 
-+canvas.fps 18
-    if ([aConference.callId isEqualToString:​self.conference.callId]) { +canvas.kbps 900
-        ​[self subStream:​aStream]; +canvas.codec = @"​H264"​
-    } +canvas.bgclr = 0x0000ff; 
-+canvas.width ​[EMDemoOption sharedOptions].liveWidth
-     +canvas.height = [EMDemoOption sharedOptions].liveHeight;​ 
-- (void)subStream:​(EMCallStream ​*)aStream +liveconfig.canvas = canvas; 
-+liveconfig.cdnUrl = [EMDemoOption sharedOptions].cdnUrl;​ 
-    //​构建数据流视频显示的页面,如果不支持视频可以传nil +liveconfig.layoutStyle = CUSTOM; 
-    EMCallRemoteView *remoteView ​= [[EMCallRemoteView ​alloc] ​initWithFrame:​frame]; +liveconfig.record = YES; //是否录制推流到cdn的音视频 
-    ​remoteView.autoresizingMask ​UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight+roomConfig.liveConfig = liveconfig;
-    ​remoteView.scaleMode ​EMCallViewScaleModeAspectFill+
-    [self.scrollView addSubview:​remoteView]+
-         +
-    ​__weak typeof(self) weakSelf ​self+
-    [[EMClient sharedClient].conferenceManager subscribeConference:​self.conference streamId:​aStream.streamId remoteVideoView:​remoteView completion:​^(EMError *aError) { +
-        if (aError) { +
-            //提示错误信息 +
-        } +
-    }]; +
-}+
 </​code>​ </​code>​
  
-==== 操作自己发布的流 ​====+当canvas设置width、height为0时,cdn推为**纯音频推流**
  
-成员成功的发布了自己的音视频流,在会议过程中,可以进行以下操作:+推流成功可以EMConference对象查看liveId,如果只有一路推流,可直接使用EMConference对象的liveId,如果存在多路推流,可访问EMConference对象的liveCfgs对象,liveCfgs存储了所有的推流信息
  
-    >> 切换前后摄像头: updateConferenceWithSwitchCamera:​ +LiveConfig可设置参数如下
-    >> 开关静音,即订阅成员是否能听到A声音:updateConference:​isMute:​ +
-    >> 开关视频,即订阅成员是否能看到A的视频:updateConference:​enableVideo:​ +
-    >> 重置视频显示页面updateConference:​streamId:​remoteVideoView:​completion:​+
  
-当成员对自己的数据流做以上操作成功后,会议中的其他成员会收到回调[EMConferenceManagerDelegate streamDidUpdate:stream:]+<code objc> 
 +/*! 
 +*  \~chinese 
 +*  cdn 画布设置创建会议时使用 
 +*/ 
 +@interface CDNCanvas ​NSObject 
 + /*! \~chinese 画布宽度 ​ */ 
 +@property (nonatomic) NSInteger width; 
 +/*! \~chinese 画布高度 ​ */ 
 +@property (nonatomic) NSInteger height; 
 +/*! \~chinese 画布的背景色,格式为 RGB 定义下的 Hex 值,不要带 # 号,如 0xFFB6C1 表示浅粉色。默认0x000000,黑色。 
 +*/ 
 +@property (nonatomic) NSInteger bgclr; 
 +/*! \~chinese 推流帧率,可设置范围10-30 */ 
 +@property (nonatomic) NSInteger fps; 
 +/*! \~chinese 推流码率,单位kbps,width和height较大时,码率需要提高,可设置范围1-5000 ​ */ 
 +@property (nonatomic) NSInteger kbps; 
 +/*! \~chinese 推流编码格式,目前只支持"​H264"​ */ 
 +@property (nonatomic) NSString* codec;
  
 +@end
 +
 +/*!
 +*  \~chinese
 +*  cdn推流使用的画布类型
 +*/
 +typedef NS_ENUM(NSInteger,​ LayoutStyle) {
 +    CUSTOM,
 +    DEMO,
 +    GRID
 +};
 +/*!
 +*  \~chinese
 +*  cdn推流设置
 +*/
 +@interface LiveConfig : NSObject
 +
 +/*! \~chinese 推流url地址*/​
 +@property (nonatomic,​strong) NSString *cdnUrl;
 +
 +/*! \~chinese 推流画布的配置*/​
 +@property (nonatomic) CDNCanvas* canvas;
 +
 +/*! \~chinese 推流方式,GRID或者CUSTOM,GRID将由服务器设置位置信息,CUSTOM将由用户自定义流的位置信息*/​
 +@property (nonatomic) LayoutStyle layoutStyle;​
 +
 +/*! \~chinese 是否开启自定义录制*/​
 +@property (nonatomic) BOOL record;
 +
 +/*! \~chinese 音频录制参数*/​
 +@property (nonatomic) AudioConfig* audioCfg;
 +
 +@end
 +</​code>​
 +
 +== 更新布局 ==
 +当用户调用更新布局接口后,cdn推流方式将强制变成CUSTOM模式,所有流的位置信息都由用户自己定义。
 +更新布局的接口如下:
 <code objc> <code objc>
-//Objective-C +/*! 
-- (void)streamDidUpdate:​(EMCallConference *)aConference stream:(EMCallStream ​*)aStream +*  \~chinese 
-+*  修改会议的cdn推流位置 
-    if ([aConference.callId isEqualToString:self.conference.callId] && aStream != nil{ +
-        //​判断本地缓存的EMCallStream实例与aStream有哪些属性不同,并做相应更新 +*  @param aCall             ​会议实例(自己创建的无效) 
-    } +*  @param aReagionList 媒体流的位置信息 
-}+*  @param aLiveId ​          ​推流Id 
 +*  @param aCompletionBlock 回调函数 
 +*
 +- (void)updateConference:​(EMCallConference*)aCall 
 +                  liveId:(NSString*)aLiveId 
 +              ​setRegions:(NSArray<​LiveRegion*>​*)aReagionList 
 +              ​completion:​(void(^)(EMError *aError))aCompletionBlock;​
 </​code>​ </​code>​
 +LiveRegion的结构如下:
 +<code objc>
  
-==== 操作自己订阅的流 ​====+/*! 
 +*  \~chinese 
 +*  cdn推流每一路的模式 
 +*/ 
 +typedef NS_ENUM(NSInteger,​ LiveRegionStyle) { 
 +    /*! \~chinese FIt模式 */ 
 +    LiveRegionStyleFit,​ 
 +    /*! \~chinese FIll模式 */ 
 +    LiveRegionStyleFill 
 +};
  
-对于订阅成功的流,可以有以下操作:+/*! 
 +*  \~chinese 
 +*  cdn推流每一路的区域位置信息 
 +*/ 
 +@interface LiveRegion : NSObject
  
- <​code objc> +/*\~chinese ​流ID */ 
-   /*+@property ​(nonatomic) NSString* ​streamId;
- ​* ​\~chinese +
- mute远端音频 +
- +
- ​* ​@param aStreamId ​       要操作的Steam id +
- * @param isMute ​           是否静音 +
- * +
- * \~english +
- * Mute remote audio +
- * +
- * @param aStreamId ​       Steam id +
- * @param isMute ​           is mute +
- */ +
-(void)muteRemoteAudio:​(NSString *)aStreamId mute:​(BOOL)isMute;+
  
-/** +/*! \~chinese 流的左上角在x轴坐标  ​*/ 
- * \~chinese +@property (nonatomic) NSInteger x; 
- mute远端视频 + 
- * +/*\~chinese ​流的左上角在y轴坐标  ​*/ 
- ​* ​@param aStreamId ​       要操作Steam id +@property (nonatomic) NSInteger y; 
- * @param isMute ​           是否显示 + 
- * +/*! \~chinese 流宽度  ​*
- * \~english +@property (nonatomic) NSInteger w; 
- * Mute remote video + 
- +/*\~chinese 流的高度 ​*/ 
- @param aStreamId ​       Steam id +@property (nonatomic) NSInteger h; 
- * @param isMute ​           is mute + 
- */ +/*! \~chinese 流的图层顺序,越小越在底层,从1开始 ​*/ 
-(void)muteRemoteVideo:​(NSString ​*)aStreamId mute:(BOOL)isMute;+@property ​(nonatomicNSInteger z; 
 + 
 +/*! \~chinese 流的显示模式,Fit或Fill */ 
 +@property ​(nonatomicLiveRegionStyle style; 
 + 
 +@end
 </​code>​ </​code>​
-==== 取消订阅流 ==== 
  
-成员B如果不想再看成员A的音视频,可以调SDK接口unsubscribe+使方法如下: 
 +<code objc> 
 +NSMutableArray<​LiveRegion*>​* regionsList = [NSMutableArray array]; 
 +LiveRegion* region = [[LiveRegion alloc] init]; 
 +region.streamId = _streamId;​ 
 +region.style = LiveRegionStyleFill;​ 
 +region.x = 80; 
 +region.y = 60; 
 +region.w = 320; 
 +region.h = 240; 
 +region.z = 9; 
 +[regionsList addObject:​region];​ 
 +[[[EMClient sharedClient] conferenceManager] updateConference:​[EMDemoOption sharedOptions].conference liveId:​aLiveId setRegions:​regionsList completion:​^(EMError *aError) { 
 +    }]; 
 +</​code>​
  
 +== 多路推流 ==
 +多人音视频支持加入会议后,增加一路推流,只有管理员权限可进行次操作。增加一路推流的api方法如下:
 +
 +<​code>​
 +/*!
 +*  \~chinese
 +*  添加一路推流
 +*
 +*  @param aCall             ​会议实例(自己创建的无效)
 +*  @param aLiveConfig 推流配置
 +*  @param aCompletionBlock 回调函数
 +*/
 +- (void)addConferenceLive:​(EMCallConference*)aCall
 +                  LiveCfg:​(LiveConfig*)aLiveConfig
 +               ​completion:​(void(^)(EMError *aError))aCompletionBlock;​
 +</​code>​
 +== 停止推流 ==
 +管理员可以控制停止某一路推流,停止推流接口如下:
 +<​code>​
 +/*!
 +*  \~chinese
 +*  删除一路推流
 +*/
 +- (void)deleteConferenceLive:​(EMCallConference*)aCall
 +                      liveId:​(NSString*)aLiveId
 +                  completion:​(void(^)(EMError *aError))aCompletionBlock;​
 +</​code>​
 +==== 云端录制 ====
 +多人音视频会议支持云端录制功能,包括服务器默认录制以及自定义布局录制两种
 +== 服务器默认录制 ==
 +服务器默认录制为九宫格布局,在会议创建时,由创建者指定是否开启,开启方法为创建会议时指定isRecord为YES,如下:
 <code objc> <code objc>
 //​Objective-C //​Objective-C
-- (void)unsubStream +    RoomConfig* roomConfig = [[RoomConfig alloc] init]; 
-{ +    ​roomConfig.isMerge = YES; 
-    ​__weak typeof(self) weakself ​self+    ​roomConfig.isRecord ​YES
-    [[EMClient sharedClient].conferenceManager ​unsubscribeConference:self.conference streamId:self.pubStreamId ​completion:​^(EMError *aError) { +    ​[[[EMClient sharedClient] conferenceManager] joinRoom:roomName password:pswd role:role roomConfig:​roomConfig ​completion:​^(EMCallConference *aCall, ​EMError *aError) { 
-        //​code+    ​self.conference = aCall;
     }];     }];
-}+</​code>​ 
 +== 自定义布局录制 == 
 +在推流的LiveConfig设置里,设record为YES,可以开启自定义录制,开启后会把推流到cdn的音视频按照推流布局录制下来。如果推流时未开启,也可以在推流后进行开启/​停止自定义录制布局操作。开启/​停止自定义录制布局的api如下: 
 +<​code>​ 
 +/*! 
 +*  \~chinese 
 +*  启动/​停止自定义录制 
 +
 +*  @param aCall             ​会议实例(自己创建的无效) 
 +*  @param aLiveId 推流/​录制Id 
 +*  @param aEnabled 操作,启动/​停止 
 +*  @param aCompletionBlock 回调函数 
 +*/ 
 +- (void)enableRecordLiveStream:​(EMCallConference*)aCall 
 +                        liveId:​(NSString*)aLiveId 
 +                       ​enabled:​(BOOL)aEnabled 
 +                    completion:​(void(^)(EMError *aError))aCompletionBlock;​
 </​code>​ </​code>​
  
-==== 成员A取消发布流 ​====+==== 设置昵称,头像解决方案 ​==== 
 +多人音视频通话提供设置昵称的接口,在加入会议时设置;不直接提供头像设置接口,但提供头像设置方案,在加入会议时将头像url设置到RoomConfig的ext中,过程如下: 
 +<code objc> 
 +//​Objective-C 
 +    RoomConfig* roomConfig = [[RoomConfig alloc] init]; 
 +    roomConfig.nickName = @"​昵称";​ 
 +    NSMutableDictionary* extDic = [NSMutableDictionary dictionary];​ 
 +    NSString* headImage = [EMDemoOption sharedOptions].headImage;​ 
 +    [extDic setObject:​headImage forKey:​@"​headImage"​];​ 
 +    NSError *jsonError = nil; 
 +    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:​extDic options:​NSJSONWritingPrettyPrinted error:&​jsonError];​ 
 +    NSString *jsonStr = @"";​ 
 +    if (jsonData && !jsonError) { 
 +        jsonStr = [[NSString alloc] initWithData:​jsonData encoding:​NSUTF8StringEncoding];​ 
 +    } 
 +    roomConfig.ext = jsonStr; 
 +    [[[EMClient sharedClient] conferenceManager] joinRoom:​roomName password:​pswd role:role roomConfig:​roomConfig completion:​^(EMCallConference *aCall, EMError *aError) { 
 +    self.conference = aCall; 
 +    }]; 
 +</​code>​ 
 +==== 海外代理 ==== 
 +1v1通话支持不同集群区域的人员通话使用代理,减小延迟。使用多集群代理需要音视频后台配置IP及端口的映射文件rtcconfig.json,​并禁用相关appkey的直连。启用多集群代理功能需要配置如下属性: 
 +<code objc> 
 +[EMClient sharedClient].options.isUseRtcConfig = YES; 
 +</​code>​
  
 +==== 私有部署 ====
 +私有部署设置方法参见[[http://​docs-im.easemob.com/​im/​ios/​other/​privatecloud#​%E7%A7%81%E6%9C%89%E4%BA%91sdk%E9%9B%86%E6%88%90%E9%85%8D%E7%BD%AE|私有云sdk集成配置]]
 +===== 音视频管理 =====
 +==== 设置通话参数 ====
 +通话之前,可以设置音频通话的最大音频码率,最小视频码率、最大视频码率、分辨率和是否清晰度优先,设置方法如下
 +<​code>​
 +EMCallOptions *options = [[EMClient sharedClient].callManager getCallOptions];​
 +options.maxAudioKBps = 32;
 +options.maxVideoKBps = 3000;
 +options.minVideoKBps = 500;
 +options.maxVideoFrameRate = 20;
 +options.videoResolution = EMCallVideoResolution352_288;​
 +options.isClarityFirst = YES;//​若设为清晰度优先,将在弱网环境下保证视频的分辨率
 +</​code>​
 +==== 停止发布流 ====
 成员A可以调用unpublish接口取消自己已经发布的数据流,操作成功后,会议中的其他成员会收到回调[EMConferenceManagerDelegate streamDidUpdate:​removeStream:​] ,将对应的数据流信息移除 成员A可以调用unpublish接口取消自己已经发布的数据流,操作成功后,会议中的其他成员会收到回调[EMConferenceManagerDelegate streamDidUpdate:​removeStream:​] ,将对应的数据流信息移除
  
行 475: 行 756:
 </​code>​ </​code>​
  
-==== 成员B离开会议 ​====+==== 停止订阅流 ​====
  
-成员B调用SDK接口离开会议,会议中的其他成员会收到回调[EMConferenceManagerDelegate memberDidLeave:​member:​] +成员B如果不想再看成员A的音视频,可以调用SDK接口unsubscribe
-<code objc> +
-[[EMClient sharedClient].conferenceManager leaveConference:​aCall completion:​nil];​ +
-</​code>​+
  
 <code objc> <code objc>
 //​Objective-C //​Objective-C
-- (void)memberDidLeave:​(EMCallConference *)aConference member:​(EMCallMember *)aMember+- (void)unsubStream
 { {
-    ​if ([aConference.callId isEqualToString:​self.conference.callId]) { +    ​__weak typeof(self) weakself = self; 
-        ​NSString *message = [NSString stringWithFormat:​@"​成员 %@ 已经离开会议",​ aMember.memberName];​ +    ​[[EMClient sharedClient].conferenceManager unsubscribeConference:​self.conference ​streamId:​self.pubStreamId completion:​^(EMError *aError) { 
-        [self showHint:​message];​ +        ​//code 
-    }+    }];
 } }
 </​code>​ </​code>​
  
-==== 管理员销毁会议 ​====+==== 操作自己订阅的流 ​====
  
-管理员离开会议时,可以选择离开会议或销毁会议,销毁会议后,其他成员都将收到会议结束的回调[EMConferenceManagerDelegate conferenceDidEnd:​reason:​error];离开会议后,若当前会议中无管理员,服务器会将管理员权限随机分配给某个主播。只管理员权限可进行销毁会议操作+对于订阅成功的流,可以有以操作
  
-销毁会议使用接口 + <code objc> 
-<code objc> +   ​/** 
-[[EMClient sharedClient].conferenceManager destroyConferenceWithId:​aConfId completion:​nil];​ + * \~chinese 
-</​code>​ + ​* ​mute远端音频
-离开会议使用接口 +
-<code objc> +
-[[EMClient sharedClient].conferenceManager leaveConference:​aConference completion:​nil];​ +
-</​code>​ +
- +
-当会议中的成员都离开会议后,服务器会自动将会议结束销毁 +
-==== 会议结束 ====  +
-当会议结束时,成员将收到会议结束的回调[EMConferenceManagerDelegate conferenceDidEnd:​reason:​error],该回调的详细接口如下: +
-<code objc> +
-/*! +
- ​* ​ \~chinese +
- ​* ​ ​会议已经结束+
  *  *
- ​* ​ @param ​aConference ​      ​会议实例 + * @param ​aStreamId ​       要操作的Steam id 
- ​* ​ @param ​aReason ​          ​结束原因 + * @param ​isMute ​           是否静音
- ​* ​ @param aError ​           错误信息 +
- * +
- ​* ​ \~english +
- ​* ​ The conference is over +
- * +
- ​* ​ @param aConference ​      ​EMConference instance +
- ​* ​ @param aReason ​          The end reason +
- ​* ​ @param aError ​           The error+
  */  */
-- (void)conferenceDidEnd:(EMCallConference ​*)aConference +- (void)muteRemoteAudio:(NSString ​*)aStreamId mute:(BOOL)isMute;
-                  reason:(EMCallEndReason)aReason +
-                   ​error:​(EMError *)aError; +
-</​code>​+
  
-EMCallEndReason表示会议结束的原因,管理员正常结束会议为EMCallEndReasonDestroy,被踢出会议为EMCallEndReasonBeenkicked +/** 
-==== 踢人 ==== + * \~chinese 
- + ​* ​mute远端视频
-管理员可以强制会议成员离开会议,使用接口 +
-<code objc> +
-/*! +
- ​* ​ \~chinese +
- ​* ​ ​踢人,需要管理员权限+
  *  *
- ​* ​ @param ​aConfId ​          ​会议ID(EMCallConference.confId) + * @param ​aStreamId ​       要操作Steam id 
- ​* ​ @param aMemberNameList ​  ​成员名列表 + * @param ​isMute ​           是否关闭
- ​* ​ @param aCompletionBlock ​ 完成回调 +
- * +
- ​* ​ \~english +
- ​* ​ Kick members, requires administrator privileges +
- * +
- ​* ​ @param aConfId ​          ​Conference ID (EMCallConference.confId) +
- ​* ​ @param aMemberNameList ​  ​Member Name list +
- ​* ​ @param ​aCompletionBlock ​ The callback block of completion+
  */  */
-- (void)kickMemberWithConfId:(NSString *)aConfId +- (void)muteRemoteVideo:(NSString *)aStreamId mute:(BOOL)isMute;
-                 ​memberNames:(NSArray<​NSString *> *)aMemberNameList +
-                  completion:​(void (^)(EMError *aError))aCompletionBlock;+
 </​code>​ </​code>​
  
-==== 管理员变更 ​==== +==== 通话中音视频控制 ​==== 
-当会议中的普通成员成为管理员,或管理员降级为普通成员时,会议中的其他成员将收到管理员变更的回调。管理员变更回调分为管理员新增和管理员移除接口如下 +成员发布了自己的音视频流后会议过程中,可以进行以下操作: 
-<​code ​objc+  * 切换前后摄像头,如下 
-/*! +<​code>​ 
-  \~chinese +[[[EMClient sharedClient] conferenceManager] updateConferenceWithSwitchCamera:​self.conference];​ 
-  ​管理员新增 +</code> 
- * +  * 开关静音 
- *  @param aConference ​      ​会议实例 +<​code>​ 
- ​* ​ @param adminmemid ​        ​新的管理员memid +[[[EMClient sharedClient] conferenceManager] updateConference:self.conference isMute:YES]
- * +</code> 
- ​* ​ \~english +  * 开关视频 
- ​* ​ The admin has added +<​code>​ 
- * +[[[EMClient sharedClient] conferenceManager] updateConference:self.conference enableVideo:YES];
- ​* ​ @param aConference ​      ​EMConference instance +
- ​* ​ @param adminmemid ​        The new admin memid +
- */ +
-- (void)adminDidChanged:(EMCallConference *)aConference +
-               ​newAdmin:(NSString*)adminmemid+
- +
-/*! +
-  \~chinese +
-  ​管理员放弃 +
- * +
- *  @param aConference ​      ​会议实例 +
- ​* ​ @param adminmemid ​        ​放弃管理员的memid +
- * +
- ​* ​ \~english +
- ​* ​ The admin has removed +
- * +
- ​* ​ @param aConference ​      ​EMConference instance +
- ​* ​ @param adminmemid ​        The removed admin memid +
- */ +
-- (void)adminDidChanged:(EMCallConference *)aConference +
-            removeAdmin:(NSString*)adminmemid;+
 </​code>​ </​code>​
-==== 获取会议信息 ==== 
  
-会议进行,可以通过getConferenceInfo 方法来查询议信息,从而可以拿主播列表,观众人数等信息。+当成员对自己的数据流做开关静音/​视频时,会议中的其他成员回调[EMConferenceManagerDelegate streamDidUpdate:​stream:​]
  
 <code objc> <code objc>
-/*! +//Objective-C 
- ​* ​ \~chinese +- (void)streamDidUpdate:(EMCallConference ​*)aConference stream:(EMCallStream ​*)aStream 
- ​* ​ 判断会议是否存在 +
- * +    if ([aConference.callId isEqualToString:self.conference.callId] && aStream != nil
- ​* ​ @param aConfId ​          ​会议ID(EMCallConference.confId) +        //​判断本地缓存的EMCallStream实例与aStream有哪些属性不同,并做相应更新 
- ​* ​ @param aPassword ​        ​会议密码 +    } 
- ​* ​ @param aCompletionBlock ​ 完成的回调 +}
- * +
- ​* ​ \~english +
- ​* ​ Determine if the conference exists +
- * +
- ​* ​ @param aConfId ​          ​Conference ID (EMCallConference.confId) +
- ​* ​ @param aPassword ​        The password of the conference +
- ​* ​ @param aCompletionBlock ​ The callback block of completion +
- *+
-- (void)getConference:(NSString ​*)aConfId +
-             ​password:(NSString ​*)aPassword +
-           completion:(void (^)(EMCallConference *aCall, EMError *aError))aCompletionBlock;​+
 </​code>​ </​code>​
 +==== 音视频首帧回调 ====
  
-==== 管理会议角色 ==== +当成发布流,发送第一帧音视频数据成员收到以下通知
-管理可以对其他观众、主播的角色进行升级、降级处理,接口如下: +
-<code objc> +
-/*! +
- ​* ​ \~chinese +
- ​* ​ 改变员角色需要管理员权限 +
- * 用户角色:​ Admin > Talker > Audience +
- * 当角色升级时,​用户需要给管理员发送申请,​管理通过该接口改变用户接口. +
- * 当角色降级,​用户直接调用该接口即可. +
- * +
- ​* ​ @param aConfId ​          ​会议ID(EMCallConference.confId) +
- ​* ​ @param aMember ​       ​成员 +
- ​* ​ @param aRole             ​成员角色 +
- ​* ​ @param aCompletionBlock ​ 完成的回调 +
- * +
- ​* ​ \~english +
- ​* ​ Changing member roles, requires administrator privileges +
- * 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. +
- * +
- ​* ​ @param aConfId ​          ​Conference ID (EMCallConference.confId) +
- ​* ​ @param aMember ​       Member +
- ​* ​ @param aRole             The Role +
- ​* ​ @param aCompletionBlock ​ The callback block of completion +
- */ +
-- (void)changeMemberRoleWithConfId:​(NSString *)aConfId +
-                            member:​(EMCallMember *)aMember +
-                              role:​(EMConferenceRole)toRole +
-                        completion:​(void (^)(EMError *aError))aCompletionBlock;​ +
-</​code>​+
  
-==== 全体静音/​解除全体静音 ==== +<​code>​
-管理员可以对会议进行全体静音/​解除全体静音设置,设置后,会议中的主播都将处于静音状态,新加入的主播也将自动处于静音状态。只有管理员可以调用此接口。 +
-接口API如下: +
-<​code ​objc> +
-/** +
-* \~chinese +
-* 开启/​停止全体静音,​只有管理员可调用此接口 +
-+
-* @param enable 是否启用全体静音 +
-* @param completion 回调 +
-+
-* \~english +
-* start/stop mute all members +
-* @params enable whether to start +
-* @params completion the callback functio +
-*/ +
-- (void)muteAll:​(BOOL)mute +
-     ​completion:​(void(^)(EMError *aError))aCompletionBlock;​ +
-</​code>​ +
- +
-管理员调用此接口后,会议中的主播将收到全体静音状态的回调,回调函数如下 +
-<code objc>+
 /*! /*!
  * \~chinese  * \~chinese
- * 收到全体静音/​解除全体静音的回调+ ​* ​发送第一帧音视频数据时,收到回调
  *  *
  * @param aConference ​    ​会议  * @param aConference ​    ​会议
- * @param ​aMuteAll ​  ​是否全体静 + * @param ​aType 流类型,频或视频 
- * + * @param ​streamId 流ID
- ​*\~english +
- * callback when admin set muteAll/​unmuteAll +
- * +
- * @param aConference ​    ​EMCallConference instance +
- * @param ​aMuteAll ​ Weather muteAll or not+
 */ */
-- (void)conferenceDidUpdated:​(EMCallConference *)aConference +- (void)streamDidFirstFrameSended:​(EMCallConference*)aConference ​type:(EMMediaType)aType streamId:​(NSString*)streamId;
-                  muteAll:(BOOL)aMuteAll;+
 </​code>​ </​code>​
  
-==== 指定成员静音/​解除静音 ==== +成员订阅流功,收到第一帧视频数据时,成员会收到以下通知
-管理员可以对会议中的指定员进行静/​解除静音设置被指定成员是主播也可以是管理员。设置后,被指定成员将静音/​解除静音。只有管理员可以调用此接口。 +
-接口API如: +
-<code objc> +
-/*! +
-*  \~chinese +
-*  将指定成员静音/​解除静音,管理员调用 +
-+
-*  @param aCall             ​会议实例(自己创建的无效) +
-*  @param aMemId 指定成员的memId +
-*  @param aMute 操作,YES为静音,NO为解除静音 +
-*  @param aCompletionBlock 回调函数 +
-+
-*  \~english +
-*  mute/unmute one member,only admin call this function +
-+
-*  @param aCall             ​EMConference instance (invalid by yourself) +
-*  @param aMemId The memid of member +
-*  @param aMute Opereation,​YES means mute,and NO means unmute +
-*  @param aCompletionBlock The callback function +
- */ +
-- (void)setMuteMember:​(EMCallConference *)aCall +
-                memId:​(NSString *)aMemId +
-                 ​mute:​(BOOL)aMute +
-           ​completion:​(void (^)(EMError *aError))aCompletionBlock;​ +
-</​code>​+
  
-管理员调用此接口后,被指定的成员将收到静音状态的回调,回调函数如下 +<​code>​
-<​code ​objc>+
 /*! /*!
  * \~chinese  * \~chinese
- * 收到静/​解除静音的回调+ ​* ​流第一帧视频数据时,收到此回调
  *  *
  * @param aConference ​    ​会议  * @param aConference ​    ​会议
- * @param ​aMute   ​是否静 + * @param ​aType 流类型,频或视频 
- * + * @param ​streamId 流ID
- ​*\~english +
- * callback when recv mute command +
- * +
- * @param aConference ​    ​EMCallConference instance +
- * @param ​aMute  Weather mute or not+
 */ */
-- (void)conferenceDidUpdated:​(EMCallConference*)aConference +- (void)streamDidFirstFrameReceived:​(EMCallConference*)aConference ​type:(EMMediaType)aType streamId:​(NSString*)streamId;
-                        mute:(BOOL)aMute;+
 </​code>​ </​code>​
 +==== 音视频无数据回调 ====
 +当会议中的成员A因断网或异常退出,而无音视频数据上传时,订阅该流的其他成员会收到下面的回调通知。
  
-==== 观众申请主播 ==== +<​code>​
-会议中的观众角色可以向管理员发申请成为主播,管理员可以选择同意或者拒绝。观众申请主持人的接口需要管理员的memId,先通过获取会议属性接口获取到管理员的memName,然后根据memName以及成员加入的回调中获取到的EMCallMember,获取到memId。接口如下 +
-<​code ​objc> +
-/*! +
-*  \~chinese +
-*  观众申请连麦成为主播,观众角色调用 +
-+
-*  @param aCall             ​会议实例(自己创建的无效) +
-*  @param aAdminId 管理员的memId +
-*  @param aCompletionBlock 回调函数 +
-+
-*  \~english +
-*  Request tobe Speaker,​only audience call this function +
-+
-*  @param aCall             ​EMConference instance (invalid by yourself) +
-*  @param aAdminId The memid of admin +
-*  @param aCompletionBlock The callback function +
-*/ +
-- (void)requestTobeSpeaker:​(EMCallConference *)aCall adminId:​(NSString *)aAdminId completion:​(void (^)(EMError *aError))aCompletionBlock;​ +
-</​code>​ +
-观众发出申请后,管理员将会收到以下回调: +
-<code objc>+
 /*! /*!
  * \~chinese  * \~chinese
- * 收到观众申请主播的请求,只有管理员会触发+ ​* ​下行音频流无数据时,收到此回调
  *  *
  * @param aConference ​    ​会议  * @param aConference ​    ​会议
- * @param ​aMemId ​  ​申请人memId + * @param ​aType 流类型,音频或视频 
- * @param ​aNickName 申请人昵称 + * @param ​streamId 流ID
- * @param aMemName 申请人memName +
- * +
- ​*\~english +
- * callback when admin recv the request of become speaker +
- * +
- * @param aConference ​    ​EMCallConference instance +
- * @param aMemId ​  The memId of requster +
- * @param aNickName ​ The nickname of requster +
- * @param aMemName ​ The memname of requster+
 */ */
-- (void)conferenceReqSpeaker:​(EMCallConference*)aConference ​memId:(NSString*)aMemId nickName:(NSString*)aNickName memName:​(NSString*)aMemName;+- (void)streamStateUpdated:​(EMCallConference*)aConference ​type:(EMMediaType)aType state:(EMMediaState)state streamId:​(NSString*)streamId;
 </​code>​ </​code>​
-回调中,管理员可以选择同意或者拒绝,如果同意,需要调用改变用户权限的接口,然调用回复接口如果拒绝,则直接调用回复接口。回复接口如下: + 
-<​code ​objc+该功能需要会议中开启质量统计(必须创建或者加入会议成功之后,调用此方法) 
-/*! + 
-*  \~chinese +<​code>​ 
-*  管理员同意/​拒绝观众的上麦申请,管理员调用 +[[EMClient sharedClient].conferenceManager enableStatistics:YES];
-+
-*  @param aCall             ​会议实例(自己创建的无效) +
-*  @param aMemId 上麦申请的观众的memId +
-*  @param aResult 操作结果,0为同意,1为拒绝 +
-*  @param aCompletionBlock 回调函数 +
-+
-*  \~english +
-*  Admin agree/​disagree ​ the audience request tobe speaker.only admin call this function +
-+
-*  @param aCall             ​EMConference instance (invalid by yourself) +
-*  @param aMemId The memid of member who requeset tobe speaker +
-*  @param aMute Opereation result,0 means agree,and 1 means disagree +
-*  @param aCompletionBlock The callback function +
- */ +
-- (void)responseReqSpeaker:​(EMCallConference *)aCall +
-                     ​memId:​(NSString *)aMemId +
-                    result:​(NSInteger)aResult +
-                completion:(void (^)(EMError *aError))aCompletionBlock;+
 </​code>​ </​code>​
  
-==== 主播申请管理员 ​==== +==== 弱网检测 ​==== 
-会议主播角色可以向管理员发申请成为管理员管理员可选择同意或者拒绝,成为管理员后,各管理员之间的权限是相同的主播申请主持人的接口需要管理员的memId,先通过获取会议属性接口获取到管理员的memName,然后根据memName以及成员加入的回调获取到的EMCallMember,获取到管理员memId。接口如下: +SDK提供多人音视频会议的网络连接状态检测当本地网络断开、重连、质量差时收到下回调该功能需要会议中开启质量统计 
-<​code ​objc> +<​code>​ 
-/*! +- (void)conferenceNetworkDidChange:​(EMCallConference *)aSession 
-*  \~chinese +                            status:(EMCallNetworkStatus)aStatus 
-*  主播申请成为管理员,主播角色调用 +                            { 
-+                            }
-*  @param aCall             ​会议实例(自己创建的无效) +
-*  @param aAdminId 管理员的memId +
-*  @param aCompletionBlock 回调函数 +
-+
-*  \~english +
-*  Request tobe Admin,only speraker call this function +
-+
-*  @param aCall             ​EMConference instance (invalid by yourself) +
-*  @param aAdminId The memid of admin +
-*  @param aCompletionBlock The callback function +
- */ +
-- (void)requestTobeAdmin:​(EMCallConference *)aCall adminId:(NSString *)aAdminId completion:​(void (^)(EMError *aError))aCompletionBlock;​+
 </​code>​ </​code>​
-主播发出申请后,管理员将会收到以下回调: +==== 通话质量 ==== 
-<​code ​objc>+使用[conferenceManager enableStatistics:​YES]开启视频质量统计后,用户周期性收到视频质量数据的回调,回调函数定义如下: 
 +<​code>​
 /*! /*!
  * \~chinese  * \~chinese
- ​* ​收到主播申请管理员的请求,只有管理员触发+ ​* ​当前议的媒体流质量报告回调
  *  *
  * @param aConference ​    ​会议  * @param aConference ​    ​会议
- * @param ​aMemId ​  ​申请人memId + * @param ​streamId 流ID 
- * @param ​aNickName 申请人昵称 + * @param ​aReport ​  ​会议的质量参数
- * @param aMemName 申请人memName +
- * +
- ​*\~english +
- * callback when admin recv the request of become admin +
- * +
- * @param aConference ​    ​EMCallConference instance +
- * @param aMemId ​  ​The memId of requster +
- * @param aNickName ​ The nickname of requster +
- * @param aMemName ​ The memname of requster+
 */ */
-- (void)conferenceReqAdmin:​(EMCallConference*)aConference ​memId:​(NSString*)aMemId nickName:(NSString*)aNickName memName:​(NSString*)aMemName;+- (void)conferenceDidUpdate:​(EMCallConference*)aConference ​streamId:​(NSString*)streamId statReport:(EMRTCStatsReport ​*)aReport;
 </​code>​ </​code>​
-回调中,管理员可以选择同意或者拒绝,如果同意,需要调用改变用户权限的接口然后调用回复接口,如果拒绝,则直接调用回复接口。回复接口如下: +EMRTCStatsReport的详细信息参见http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​interface_e_m_r_t_c_stats_report.html 
-<​code ​objc+==== 监听谁说话 ==== 
-/*! +多人音视频会议可以实时监听谁在说话该功能需要开启启动/​停止控制如下: 
-*  \~chinese +<​code>​ 
-*  管理员同意/拒绝主播的申请管理员请求管理员调用 +// 开始监听说话者参数间隔时间 
-+ [[[EMClient shareClient] conferenceManager] startMonitorSpeaker:​(EMCallConference *)aCall 
-*  @param aCall             ​会议实例(自己创建的无效) +                                                    timeInterval:(long long)aTimeMillisecond 
-*  @param aMemId 申请管理员的主播的memId +                                                      ​completion:​(void (^)(EMError *aError))aCompletionBlock]; 
-*  @param aResult 操作结果,0同意,1为拒绝 +  
-*  @param aCompletionBlock 回调函数 + // 停止监听说话者 
-+ ​[[[EMClient shareClient] conferenceManager] stopMonitorSpeaker:​(EMCallConference *)aCall]; 
-*  \~english +
-*  Admin agree/​disagree ​ the speaker request tobe admin.only admin call this function +
-+
-*  @param aCall             ​EMConference instance (invalid by yourself) +
-*  @param aMemId The memid of speaker who request tobe admin +
-*  @param aMute Opereation result,0 means agree,and 1 means disagree +
-*  @param aCompletionBlock The callback function +
- */ +
-- (void)responseReqAdmin:​(EMCallConference *)aCall ​memId:(NSString *)aMemId result:​(NSInteger)aResult ​completion:​(void (^)(EMError *aError))aCompletionBlock;​+
 </​code>​ </​code>​
 +有人说话时,会议成员会收到如下回调通知
 +<​code>​
 +- (void)conferenceSpeakerDidChange:​(EMCallConference *)aConference
 +                 ​speakingStreamIds:​(NSArray *)aStreamIds
 +{
 +}
 +</​code>​
 +==== mute远端音视频流 ====
 +会议成员可以对自己订阅的远端音视频流进行mute操作,操作后自己不接收远端的音视频流,不影响会议中的其他人。操作接口如下
  
-==== 视频质量统计数据 ==== 
- 
-使用[conferenceManager enableStatistics:​YES]开启视频质量统计后,用户会周期性收到视频质量数据的回调,回调函数定义如下: 
 <​code>​ <​code>​
-/*!+/**
  * \~chinese  * \~chinese
- ​* ​当前会议的媒体流质量报告回调+ ​* ​mute远端音频
  *  *
- * @param ​aConference ​    ​会议 + * @param ​aStreamId ​       要操作的Steam id 
- * @param ​streamId 流ID + * @param ​isMute ​           是否静音 
- ​* ​@param aReport ​  ​会议的质量参数+ *
 +- (void)muteRemoteAudio:​(NSString *)aStreamId mute:​(BOOL)isMute;​ 
 + 
 +/** 
 + * \~chinese 
 + * mute远端视频
  *  *
- *\~english + * @param ​aStreamId ​       要操作的Steam id 
- * get the streamId of stream.callback when publish success + * @param ​isMute ​           是否显示 
- * + */ 
- * @param ​aConference ​    ​EMCallConference instance +- (void)muteRemoteVideo:(NSString *)aStreamId mute:(BOOL)isMute;
- * @param ​streamId streamId +
- * @param aReport ​ The stat report of stream +
-*/ +
-- (void)conferenceDidUpdate:​(EMCallConference*)aConference streamId:​(NSString*)streamId statReport:(EMRTCStatsReport *)aReport;+
 </​code>​ </​code>​
-EMRTCStatsReport的详细信息参见apiDoc +==== 变声/​自定义音频 ​==== 
-===== 用户自定义数据采集数据处理 =====+用户可以通过采集音频数据,使用外部输入音频数据的接口进行通话,从而实现变声等音频数据加工功能
  
-如果用户需要自己采集特定的数据或者对于数据需要先进行一些处理,如变声、美颜等,可以使用SDK的外部输入数据的方法进行。+== 配置属性 == 
 + 
 +用户使用自定义音频数据时,需要配置外部输入音频数据的开关,以及音频采样率,通道数(当前通道数只支持1),配置方法如下: 
 +<code objc> 
 +EMCallOptions *options = [[EMClient sharedClient].callManager getCallOptions];​ 
 +options.enableCustomAudioData = YES; 
 +options.audioCustomSamples = 48000; 
 +options.audioCustomChannels = 1; 
 +//​这里调用加入会议接口 
 +</​code>​ 
 + 
 +== 输入音频数据 == 
 + 
 +音频数据采集可参考1v1音视频通话Demo中的AudioRecord类实现,​音频数据的输入必须在加入会议成功的回调后开始,否则会导致网络阻塞,影响通话质量。 
 +<code objc> 
 +[[[EMClient sharedClient] conferenceManager] joinRoom:​roomName password:​pswd role:role roomConfig:​roomConfig completion:​^(EMCallConference *aCall, EMError *aError) { 
 +    self.conference = aCall; 
 +    EMCallOptions *options = [[EMClient sharedClient].callManager getCallOptions];​ 
 +    if(options.enableCustomAudioData){ 
 +        [self audioRecorder].channels = options.audioCustomChannels;​ 
 +        [self audioRecorder].samples = options.audioCustomSamples;​ 
 +        [[self audioRecorder] startAudioDataRecord];​ 
 +    } 
 +    }]; 
 +</​code>​ 
 +音频采集过程开始后,在音频数据的回调里调用外部输入音频数据接口 
 +<code objc> 
 +[[[EMClient sharedClient] conferenceManager] inputCustomAudioData:​data];​ 
 +</​code>​ 
 +会话挂断时,停止音频采集及输入过程 
 +<code objc> 
 +//​多人会议挂断触发事件 
 +- (void)hangupAction 
 +
 +      EMCallOptions *options = [[EMClient sharedClient].callManager getCallOptions];​ 
 +      if(options.enableCustomAudioData) { 
 +           ​[[self audioRecorder] stopAudioDataRecord];​ 
 +      } 
 +
 +</​code>​ 
 +==== 美颜/​自定义视频 ==== 
 +如果用户需要自己采集特定的数据或者对于数据需要先进行一些处理,如滤镜、美颜等,可以使用SDK的外部输入视频数据的方法进行。
 == 配置属性 == == 配置属性 ==
 使用外部输入视频数据接口前,需要先进行配置,配置参数为EMStreamParam中的enableCustomizeVideoData,设为YES可开启外部输入视频功能,开启后需要在publishConference的成功回调中开始视频数据采集。 使用外部输入视频数据接口前,需要先进行配置,配置参数为EMStreamParam中的enableCustomizeVideoData,设为YES可开启外部输入视频功能,开启后需要在publishConference的成功回调中开始视频数据采集。
行 934: 行 1027:
 } }
 </​code>​ </​code>​
- +==== 共享桌面 ====
-外部音频数据的参数除开关外,还需要配置采样率及通道数,目前通道数只支持1 +
- +
-<code objc> +
- +
-EMStreamParam *streamParam = [[EMStreamParam alloc] init]; +
-streamParam.enableCustomizeAudioData = YES; // 开启自定义音频流 +
-streamParam.enableCustomizeAudioData.customAudioSamples = 48000; +
-streamParam.enableCustomizeAudioData.customAudioChannels = 1; +
-[[EMClient sharedClient].conferenceManager publishConference:​self.conference +
-                                                 ​streamParam:​streamParam +
-                                                  completion:​^(NSString *aPubStreamId,​ EMError *aError)  +
-+
- +
-}]; +
- +
-</​code>​ +
- +
-外部输入音频数据的接口为 +
-<code objc> +
-[[[EMClient sharedClient] conferenceManager] inputCustomAudioData:​data];​ +
-</​code>​ +
-开启和关闭过程与外部视频的接口使用一致。 +
- +
-===== 共享桌面 ​=====+
 使用sdk共享桌面,只能共享指定的view 使用sdk共享桌面,只能共享指定的view
  
行 985: 行 1054:
 CGFloat screenX = [UIScreen mainScreen].bounds.size.width;​ CGFloat screenX = [UIScreen mainScreen].bounds.size.width;​
 CGFloat screenY = [UIScreen mainScreen].bounds.size.height;​ CGFloat screenY = [UIScreen mainScreen].bounds.size.height;​
-streamParam.videoWidth = screenY; +streamParam.videoWidth = screenY*[UIScreen mainScreen].scale
-streamParam.videoHeight = screenX;+streamParam.videoHeight = screenX*[UIScreen mainScreen].scale;
  
 [[EMClient sharedClient].conferenceManager publishConference:​self.conference [[EMClient sharedClient].conferenceManager publishConference:​self.conference
行 1005: 行 1074:
 }]; }];
 </​code>​ </​code>​
- +==== 水印 ====
- +
-===== 设置视频流水印 =====  +
 视频通话时,可以添加图片作为水印,添加时使用[IEMConferenceManager addVideoWatermark]接口,需要指定水印图片的NSUrl,添加位置参见[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​interface_e_m_water_mark_option.html|EMWaterMarkOption]]。 视频通话时,可以添加图片作为水印,添加时使用[IEMConferenceManager addVideoWatermark]接口,需要指定水印图片的NSUrl,添加位置参见[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​interface_e_m_water_mark_option.html|EMWaterMarkOption]]。
  
行 1022: 行 1088:
 * *
 *  @param option 水印配置项,包括图片URL,marginX,​marginY以及起始点 *  @param option 水印配置项,包括图片URL,marginX,​marginY以及起始点
-+*/
-*  \~english +
-*  Enable water mark feature +
-+
-*  @param option the option of watermark picture,​include url,​margingX,​marginY,​margin point +
- */+
 - (void)addVideoWatermark:​(EMWaterMarkOption*)option;​ - (void)addVideoWatermark:​(EMWaterMarkOption*)option;​
 /*! /*!
行 1033: 行 1094:
 *  取消水印功能 *  取消水印功能
 * *
-*  \~english +*/
-*  Disable water mark feature +
-+
- */+
 - (void)clearVideoWatermark;​ - (void)clearVideoWatermark;​
 </​code>​ </​code>​
行 1056: 行 1114:
  
 </​code>​ </​code>​
- +===== 角色管理 ===== 
-===== 多集群代理 =====  +==== 观众申请主播 ==== 
-多人音视频支持不同集群区域使用代理减小延迟、丢包率。使用多集群代理需要音视频台配置IP端口映射文件rtcconfig.json,​sdk开启相应开关 +会议中观众角色可以向管理发申请成为主播员可以选择同意或者拒绝。观众申请主播的接口需要管理员的memId,先通过获取会议属性接口获取到管理员的memName,然根据memName以成员加入回调中获取到的EMCallMember,获取到memId接口如下
-启用多集群代理功能开关如下+
 <code objc> <code objc>
-[EMClient sharedClient].options.isUseRtcConfig = YES;//YES开启NO或不设置为不开启+/*! 
 +*  \~chinese 
 +*  观众申请连麦成主播观众角色调用 
 +
 +*  @param aCall             ​会议实例(自己创建的无效) 
 +*  @param aAdminId 管理员的memId 
 +*  @param aCompletionBlock 回调函数 
 +*/ 
 +- (void)requestTobeSpeaker:​(EMCallConference *)aCall adminId:​(NSString *)aAdminId completion:​(void (^)(EMError *aError))aCompletionBlock;​
 </​code>​ </​code>​
- +观众发出申请后,管理员将会到以下回调
-===== cdn合流推流 =====  +
-多人音视频支持将会议中的音视频流合并成一个流,推送第三方的cdn直播服务器。整个合流推流过程包括开启cdn推流,更新推流布局,停止推流。 +
- +
-==== 开启cdn推流 ==== +
-会议的创建者在创建会议时使用RoomConfig的接口,可决定是否开启cdn推流,推流配置LiveConfig是RoomConfig的一个参数,可设置cdn推流的相关信息。开启过程如下:+
 <code objc> <code objc>
-LiveConfigliveconfig = [[LiveConfig alloc] init]; +/*! 
-CDNCanvascanvas = [[CDNCanvas alloc] init]; + ​* ​\~chinese 
-canvas.fps = 18; + * 收到观众申请主播的请求,只有管理员会触发 
-canvas.kbps = 900; + * 
-canvas.codec = @"​H264";​ + @param aConference ​    ​会议 
-canvas.bgclr = 0x0000ff; + * @param aMemId ​  ​申请人memId 
-canvas.width = [EMDemoOption sharedOptions].liveWidth;​ + * @param aNickName 申请人昵称 
-canvas.height = [EMDemoOption sharedOptions].liveHeight;​ + * @param aMemName 申请人memName 
-liveconfig.canvas = canvas; +*/ 
-liveconfig.cdnUrl = [EMDemoOption sharedOptions].cdnUrl;​ +- (void)conferenceReqSpeaker:​(EMCallConference*)aConference memId:​(NSString*)aMemId nickName:​(NSString*)aNickName memName:​(NSString*)aMemName;
-liveconfig.layoutStyle = CUSTOM; +
-roomConfig.liveConfig = liveconfig;+
 </​code>​ </​code>​
- +在回调中,管理员以选择同意或者拒绝,如果同意,需要调用接口changeMemberRoleWithConfId授权,然后回复申请人,如果拒绝,则直接调用回复接口。回复接口如下:
-LiveConfig设置的参数如下: +
 <code objc> <code objc>
 /*! /*!
 *  \~chinese *  \~chinese
-*  ​cdn 画布设置创建会议时使+*  ​管理员同意/​拒绝观众的上麦申请管理员调
 * *
-*  ​\~english +*  ​@param aCall             ​会议实例(自己创建的无效) 
-*  ​The cdn canvas config+*  ​@param aMemId 上麦申请的观众的memId 
 +*  @param aResult 操作结果,0为同意,1为拒绝 
 +*  @param aCompletionBlock 回调函数
 */ */
-@interface CDNCanvas ​NSObject +- (void)responseReqSpeaker:(EMCallConference ​*)aCall 
- /*! \~chinese 画布宽度 \~english The width of canvas */ +                     memId:(NSString *)aMemId 
-@property ​(nonatomicNSInteger width; +                    ​result:​(NSInteger)aResult 
-/*! \~chinese 画布高度 \~english The height of canvas */ +                ​completion:​(void ​(^)(EMError ​*aError))aCompletionBlock
-@property ​(nonatomicNSInteger height; +</code>
-/*! \~chinese 画布的背景色,格式为 RGB 定义下的 Hex 值,不要带 # 号,如 0xFFB6C1 表示浅粉色。默认0x000000,黑色。 +
- * \~english The bgclr of canvas ,use ​ interger as 0x112233, 0x11 is red value,0x22 is green value,0x33 is blue value*/ +
-@property ​(nonatomicNSInteger bgclr; +
-/*! \~chinese 推流帧率,可设置范围10-30 \~english The fps of cdn live,valid value is 10-30 */ +
-@property (nonatomicNSInteger fps; +
-/*! \~chinese 推流码率,单位kbps,width和height较大时,码率需要提高,可设置范围1-5000 \~english The bps of cdn live. Unit is kbps,valid value is 1-5000 */ +
-@property (nonatomicNSInteger kbps+
-/*! \~chinese 推流编码格式,目前只支持"​H264"​ \~english The codec of cdn live,now only support "H264* codec */ +
-@property (nonatomic) NSString* codec; +
- +
-@end+
  
 +==== 主播申请管理员 ====
 +会议中的主播角色可以向管理员发申请成为管理员,管理员可以选择同意或者拒绝,成为管理员后,各管理员之间的权限是相同的。主播申请管理员的接口需要管理员的memId,先通过获取会议属性接口获取到管理员的memName,然后根据memName以及成员加入的回调中获取到的EMCallMember,获取到管理员memId,调用接口changeMemberRoleWithConfId授权,然后回复申请人,接口如下:
 +<code objc>
 /*! /*!
 *  \~chinese *  \~chinese
-*  ​cdn推流使的画布类型+*  ​主播申请成为管理员,主播角色调
 * *
-*  ​\~english +*  ​@param aCall             ​会议实例(自己创建的无效) 
-*  ​cdn  live layout style+*  ​@param aAdminId 管理员的memId 
 + @param aCompletionBlock 回调函数
 */ */
-typedef NS_ENUM(NSInteger, LayoutStyle{ +(void)requestTobeAdmin:​(EMCallConference *)aCall adminId:​(NSString *)aAdminId completion:​(void (^)(EMError *aError))aCompletionBlock;​ 
-    ​CUSTOM,​ +</​code>​ 
-    DEMO, +主播发出申请后,管理员将会收到以下回调: 
-    GRID +<code objc> 
-};+/*! 
 + * \~chinese 
 + * 收到主播申请管理员的请求,只有管理员会触发 
 + * 
 + * @param aConference ​    ​会议 
 + * @param aMemId ​  ​申请人memId 
 + * @param aNickName 申请人昵称 
 + * @param aMemName 申请人memName 
 +*/ 
 +- (void)conferenceReqAdmin:​(EMCallConference*)aConference memId:​(NSString*)aMemId nickName:​(NSString*)aNickName memName:​(NSString*)aMemName; 
 +</​code>​ 
 +在回调中,管理员可以选择同意或者拒绝,如果同意,需要调用改变用户权限的接口changeMemberRoleWithConfId,然后调用回复接口,如果拒绝,则直接调用回复接口。回复接口如下: 
 +<code objc>
 /*! /*!
 *  \~chinese *  \~chinese
-*  ​cdn推流设置+*  ​管理员同意/​拒绝主播的申请管理员请求,管理员调用
 * *
-*  ​\~english +*  ​@param aCall             ​会议实例(自己创建的无效) 
-*  ​The cdn push stream config +*  ​@param aMemId 申请管理员的主播的memId 
-*/ + ​@param aResult 操作结果,0为同意,1为拒绝 
-@interface LiveConfig ​NSObject+*  ​@param aCompletionBlock 回调函数 
 + */ 
 +- (void)responseReqAdmin:(EMCallConference *)aCall memId:​(NSString *)aMemId result:​(NSInteger)aResult completion:​(void (^)(EMError *aError))aCompletionBlock;​ 
 +</​code>​ 
 +管理员对其他观众、主播的角色进行升级、降级处理的接口如下:
  
-/*! \~chinese 推流url地址\~english The url address of cdn live*/ +aMemberName​ 是appkey拼接环信id,例如:easemob-demo#​chatdemoui_lulu1
-@property (nonatomic,​strong) NSString *cdnUrl;+
  
-/*! \~chinese ​推流画布配置\~english The config of live canvas*/ +**管理员/​主播也可以直接使用该接口对自己进行降级** 
-@property ​(nonatomicCDNCanvascanvas+<code objc> 
- +/*! 
-/*! \~chinese ​推流方式GRID或者CUSTOM,GRID将由服务器设置位置信息,CUSTOM将由用户自定义流的位置信息\~english The style of cdn live,GRID or CUSTOM.If GRID,server set region of streams.If CUSTOM,user set region of streams*/ +*  ​\~chinese 
-@property ​(nonatomicLayoutStyle layoutStyle;​ +*  改变成员角色,需要管理员权限 
- +* 用户角色:​ Admin > Talker > Audience 
-@end+* 当角色升级时,​用户需要给管理员发送申请,​管理通过该接口改变用户接口. 
 +* 当角色降级时,​用户直接调用该接口即可. 
 +
 +*  @param aConfId ​          ​会议ID(EMCallConference.confId) 
 +*  @param aMemberName ​       成员在会议中memName 
 + ​@param aRole             ​成员角色 
 +*  ​@param aCompletionBlock ​ 完成的回调 
 +*/ 
 +(void)changeMemberRoleWithConfId:​(NSString ​*)aConfId 
 +                        memberName:​(NSString *)aMemberName 
 +                              role:​(EMConferenceRole)toRole 
 +                        completion:​(void (^)(EMError *aError))aCompletionBlock
 +</​code>​ 
 +==== 管理员踢人 ==== 
 +管理员可以强制会议成员离开会议,使用接口 
 +<code objc> 
 +/*! 
 + ​*  ​\~chinese 
 + ​* ​ 踢人需要管理员权限 
 + 
 + *  ​@param aConfId ​          ​会议ID(EMCallConference.confId
 + *  @param aMemberNameList ​  ​成员名列表 
 + *  ​@param aCompletionBlock ​ 完成的回调 
 + */ 
 +- (void)kickMemberWithConfId:​(NSString *)aConfId 
 +                 ​memberNames:​(NSArray<​NSString *> *)aMemberNameList 
 +                  completion:​(void (^)(EMError *aError))aCompletionBlock;​
 </​code>​ </​code>​
  
-==== 更新布局 ​==== +==== 全体静音/​指定成员静音 ​==== 
-调用更新布局接口后,cdn推流方式强制变成CUSTOM模式所有流位置信息都由用户己定义。 +该系列接口调需要加入会议后,先获取一下会议信息,调用getConference接口 
-更新布局的接口如下:+== 全体静音 == 
 +管理员可以对会议进行全体静音/​解除全体静音设置,设置后,会议中的主播都处于静音状态新加入主播也将动处于静音状态。只有管理员可以调用此接口。 
 +接口API如下:
 <code objc> <code objc>
 /** /**
 * \~chinese * \~chinese
-更新会议的cdn推流视频流布局+开启/​停止全体静音,​只有管理员可调用此接口
 * *
-* @param ​aCall 会议对象 +* @param ​enable 是否启用全体静音 
-* @param ​aReagionList ​ 视频流列表布局信息 +* @param ​completion ​回调
-* @param aCompletionBlock ​回调 +
-+
-* \~english +
-* update stream regions in conference +
-* @params aCall whether to start +
-* @params aReagionList the stream regions array +
-* @param aCompletionBlock The callback function+
 */ */
-- (void)updateConference:(EMCallConference*)aCall +- (void)muteAll:(BOOL)mute 
-             setRegions:​(NSArray<​LiveRegion*>​*)aReagionList +     ​completion:​(void(^)(EMError *aError))aCompletionBlock;​
-              ​completion:​(void(^)(EMError *aError))aCompletionBlock;​+
 </​code>​ </​code>​
-LiveRegion的结构如下: 
-<code objc> 
  
 +管理员调用此接口后,会议中的主播将收到全体静音状态的回调,回调函数如下
 +<code objc>
 /*! /*!
- \~chinese + * \~chinese 
- ​cdn推流的每一路流模式 + ​* ​收到全体静音/​解除全体静音回调 
-+ * 
- ​\~english + ​* ​@param aConference ​    ​会议 
- The style of stream in cdn live+ ​* ​@param aMuteAll ​  ​是否全体静音
 */ */
-typedef NS_ENUM(NSInteger, LiveRegionStyle+(void)conferenceDidUpdated:​(EMCallConference ​*)aConference 
-    /*! \~chinese FIt模式\~english The fit content mode */ +                  ​muteAll:​(BOOL)aMuteAll;​ 
-    ​LiveRegionStyleFit,​ +</code>
-    /*! \~chinese FIll模式\~english The fill content mode */ +
-    LiveRegionStyleFill +
-};+
  
 +== 指定成员静音 ==
 +管理员可以对会议中的指定成员进行静音/​解除静音设置,被指定成员可以是主播也可以是管理员。设置后,被指定成员将静音/​解除静音。只有管理员可以调用此接口。
 +接口API如下:
 +<code objc>
 /*! /*!
 *  \~chinese *  \~chinese
-*  ​cdn推流的每一路流的区域位置信息+*  ​将指定成员静音/​解除静音,管理员调用
 * *
-*  ​\~english +*  ​@param aCall             ​会议实例(自己创建的无效) 
-*  ​The region info of each stream in cdn live+*  ​@param aMemId 指定成员的memId 
 +*  @param aMute 操作,YES为静音,NO为解除静音 
 +*  @param aCompletionBlock 回调函数
 */ */
-@interface LiveRegion ​NSObject +- (void)setMuteMember:(EMCallConference ​*)aCall 
- +                ​memId:​(NSString *)aMemId 
-/*! \~chinese 流ID \~english The stream Id */ +                 mute:(BOOL)aMute 
-@property ​(nonatomic) ​NSString* ​streamId; +           completion:(void (^)(EMError ​*aError))aCompletionBlock;
- +
-/*! \~chinese 流的左上角在x轴坐标 \~english The pointX of left top */ +
-@property (nonatomicNSInteger x; +
- +
-/*! \~chinese 流的左上角在y轴坐标 \~english The pointY of left top */ +
-@property ​(nonatomicNSInteger y; +
- +
-/*! \~chinese 流的宽度 \~english The width of stream */ +
-@property ​(nonatomic) NSInteger w; +
- +
-/*! \~chinese 流的高度 \~english The height of stream */ +
-@property ​(nonatomicNSInteger h; +
- +
-/*! \~chinese 流的图层顺序,越小越在底层,从1开始 \~english The zorder of stream,start from 1,the smaller is under others*/ +
-@property ​(nonatomic) NSInteger z; +
- +
-/*! \~chinese 流的显示模式,Fit或Fill \~english The content mode of stream,fit or fill*/ +
-@property (nonatomicLiveRegionStyle style; +
- +
-@end+
 </​code>​ </​code>​
  
-使方法如下+管理员调此接口后,被指定的成员将收到静音状态的回调,回调函数如下
 <code objc> <code objc>
-NSMutableArray<​LiveRegion*>regionsList = [NSMutableArray array]; +/*
-LiveRegionregion = [[LiveRegion alloc] init]; + \~chinese 
-region.streamId = _streamId; + ​* ​收到静音/​解除静音的回调 
-region.style = LiveRegionStyleFill;​ + * 
-region.x = 80; + * @param aConference ​    ​会议 
-region.y = 60; + * @param aMute   ​是否静音 
-region.w = 320; +*/ 
-region.h = 240; +- (void)conferenceDidUpdated:(EMCallConference*)aConference 
-region.z = 9; +                        ​mute:​(BOOL)aMute;
-[regionsList addObject:region]; +
-[[[EMClient sharedClient] conferenceManager] updateConference:​[EMDemoOption sharedOptions].conference setRegions:​regionsList completion:​^(EMError ​*aError{ +
-    }];+
 </​code>​ </​code>​
- +==== 本身角色变更回调 ​==== 
-==== 停止推流 ​==== +当成员本身角色发生变化时收到以回调
-多人音视频支持停止向某一个地址推流停止推流接口如+
 <code objc> <code objc>
-/** +/*! 
-* \~chinese + ​* ​ \~chinese 
-删除直播推流 + ​* ​ ​自己的角色发生变化 
-+ * 
-* @param ​aCall 会议对象 + ​* ​ @param ​aConference ​      会议实例 
-* @param aCompletionBlock 回调 + */ 
-+- (void)roleDidChanged:​(EMCallConference *)aConference;
-* \~english +
-* stop cdn live in conference +
-* @params aCall whether to start +
-* @param aCompletionBlock The callback function +
-*/ +
-- (void)deleteConferenceLive:​(EMCallConference*)aCall +
-                  completion:​(void(^)(EMError *aError))aCompletionBlock;+
 </​code>​ </​code>​
-===== 其他方法 ===== +==== 管理员变更回调 ​==== 
 +当会议中的普通成员成为管理员,或管理员降级为普通成员时,会议中的其他成员将收到管理员变更的回调。管理员变更回调分为管理员新增和管理员移除,回调接口如下:
 <code objc> <code objc>
-//​Objective-C +/*! 
-// 开启/​关闭音频传输 + *  \~chinese 
- [[[EMClient shareClient] conferenceManager] updateConference:​(EMCallConference ​*)aCall + ​* ​ ​管理员新增 
-                                                       isMute:​(BOOL)aIsMute];​ + * 
- // 开启/​关闭启视频传输 + ​* ​ ​@param aConference ​      议实例 
- ​[[[EMClient shareClient] conferenceManager] updateConference:​(EMCallConference ​*)aCall + ​* ​ ​@param adminmemid ​        新的管理员memid 
-                                                  enableVideo:​(BOOL)aEnableVideo];​ + */ 
-  +(void)adminDidChanged:​(EMCallConference *)aConference 
- ​PS:以上这四个方法都是修改 stream,群里其他成员都收到 EMConferenceListener.onStreamUpdate()回调 +               newAdmin:(NSString*)adminmemid;
-  +
- // 切换摄像头 +
- ​[[[EMClient shareClient] conferenceManager] updateConferenceWithSwitchCamera:​(EMCallConference ​*)aCall]; +
- // 更展示远端画面的 view +
- [[[EMClient shareClient] conferenceManager] updateConference:​(EMCallConference ​*)aCall +
-                                                     streamId:​(NSString *)aStreamId +
-                                              remoteVideoView:​(EMCallRemoteVideoView *)aRemoteView +
-                                                   ​completion:​(void (^)(EMError *aError))aCompletionBlock];​ +
-  +
- // 开始监听说话者,参数为间隔时间 +
- ​[[[EMClient shareClient] conferenceManager] startMonitorSpeaker:​(EMCallConference *)aCall +
-                                                    ​timeInterval:(long long)aTimeMillisecond +
-                                                      completion:​(void (^)(EMError *aError))aCompletionBlock];​ +
-  +
- // 停止监听说话者 +
- ​[[[EMClient shareClient] conferenceManager] stopMonitorSpeaker:​(EMCallConference ​*)aCall];+
  
- // 开启统计数据报告,用于debug视频过程中媒体流的具体参数,开启后在[EMConferenceManagerDelegate conferenceDidUpdate:​streamId:​statReport]回调中读取到 +/*! 
- // 各项数据参数。 + ​* ​ \~chinese 
- [[EMClient sharedClient].conferenceManager enableStatistics:YES];+ ​* ​ 管理员放弃 
 + * 
 + ​* ​ @param aConference ​      议实例 
 + *  @param adminmemid ​        ​放弃管理员的memid 
 + *
 +- (void)adminDidChanged:​(EMCallConference *)aConference 
 +            removeAdmin:(NSString*)adminmemid;
 </​code>​ </​code>​
 +====== 客户端api ======
 +多人音视频通话的API包括以下接口
 +  * EMCallOption 多人音视频通话配置类
 +  * EMConferenceManager 是多人音视频通话的主要管理类,提供了加入会议、退出、发流、订阅等接口
 +  * EMConferenceManagerDelegate 是多人音视频通话的监听回调类,多人音视频通话相关的回调
 +  * EMCallConference 多人音视频通话的会议实例接口
 +
 +== EMCallOption ==
 +^属性 ^描述 ^
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​interface_e_m_call_options.html#​aea45547ce623a0fd5d53a36f00334520|maxAudioKbps]] |最大音频码率 ​ |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​interface_e_m_call_options.html#​a4130c8f7488d6d6f58c3783dc63be5fd|maxVideoKbps]] ​      ​|最大视频码率 ​ |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​interface_e_m_call_options.html#​ae070e260597c913634a21c03aa31826a|minVideoKbps]] |最小视频码率 ​ |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​interface_e_m_call_options.html#​a45f020f84b4513fdc0cb3f8a123fe678|maxVideoFrameRate]] |最大视频帧率 ​ |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​interface_e_m_call_options.html#​af49ac4f01b9bca538fbce40e1de5f866|videoResolution]] |视频分辨率 ​   |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​interface_e_m_call_options.html#​af2129acdd0d3d73a583e8d37dfc087e2|enableReportQuality]] ​ |是否监听通话质量 ​ |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​interface_e_m_call_options.html#​ae927cb60d05c59b389517648941e7ec8|enableCustomAudioData]] ​ |是否使用自定义音频数据 ​ |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​interface_e_m_call_options.html#​a76482a8e2702e1905c036142e41c8a6d|audioCustomSamples]] ​   |自定义音频数据的采样率,默认48000 ​ |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​interface_e_m_call_options.html#​a89f38b15e05ed5d6636af5fde628d114|enableCustomizeVideoData]] |是否使用自定义视频数据 ​ |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​interface_e_m_call_options.html#​a38497058ab86b80f156ba3b362100b4e|isClarityFirst]] |是否清晰度优先 ​ |
 +
 +== EMConferenceManager ==
 +^方法 ​ ^描述 ​ ^
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a2b985523160a8a9f47346e1065667dfb|addDelegate:​delegateQueue:​]] |添加回调代理 ​ |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​acc88916806c943608d41c307012d2113|removeDelegate:​]] |移除回调代理 ​ |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a16c1d9b53642b97e07e6ae1c55925519|setAppkey:​username:​token:​]] |设置应用Appkey,​ 环信ID, 环信ID对应的Token |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a53da60e7a584697191b52734c4517669|getConference:​password:​completion:​]] |获取会议信息 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a838273c88e5264cbf0508de0d0e3aeef| createAndJoinConferenceWithType:​password:​completion:​]] |创建并加入会议 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a0f5820f9c560ca0a6ccfee963c423185| createAndJoinConferenceWithType:​password:​confrConfig:​completion:​]] |创建并加入会议 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​acead28618cffee8a9068897bbb238df8|joinConferenceWithConfId:​password:​completion:​]] ​   |加入已有会议 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a1a81bcafde9fa0d9a826a80dee15f981|joinConferenceWithTicket:​completion:​]] |加入已有会议 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a9dc56f6f8c505322baff4074964640d8|joinRoom:​password:​role:​roomConfig:​completion:​]] ​   |加入房间 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a45cb75e64fd0abd04d71ae7cbaa39668|publishConference:​streamParam:​completion:​]] ​   |上传音视频数据流 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​ab80d817ea9953a75d59284bece48e7ac|unpublishConference:​streamId:​completion:​]] ​   |取消上传本地音视频数据流 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a5bc905bb6427f586c18ea6a3b87fb58a|subscribeConference:​streamId:​remoteVideoView:​completion:​]] ​   |订阅其他人的数据流 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​af95a48dc945a95062e868db32615b1d0|unsubscribeConference:​streamId:​completion:​]] ​   |取消订阅的数据流 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​ac6df4fd1e2b16c98e4f515d45aa08d21|changeMemberRoleWithConfId:​memberName:​role:​completion:​]] ​   |改变成员角色,需要管理员权限 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a094785f9b5ee245226399234bd72fe58|kickMemberWithConfId:​memberNames:​completion:​]] ​   |踢人,需要管理员权限 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​ac3c7426a3b6dc1b50bad419d2f722ee8|destroyConferenceWithId:​completion:​]] ​   |销毁会议,需要管理员权限 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​ae54239e73e21df83450961a8263a14f7|leaveConference:​completion:​]] ​   |离开会议 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a28726fcdc8ce28006a30d713f40227c0|startMonitorSpeaker:​timeInterval:​completion:​]] ​   |开始监听说话者 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​aa67291522b649278e770c7bb1b3ee9b3|stopMonitorSpeaker:​]] ​   |结束监听说话者 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a00098d155bc5b6b8a0941f2b7b140b3f|updateConference:​liveId:​setRegions:​completion:​]] ​   |修改会议的cdn推流布局 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​ad36907936af8fcf74f64cd311d6456c7|addConferenceLive:​LiveCfg:​completion:​]] ​   |添加一路推流 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a391e86342e05c90345c6f0594008c368|enableRecordLiveStream:​liveId:​enabled:​completion:​]] ​   |启动/​停止自定义录制 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​ad879b9caafabcfcbc271e5e23937805a|deleteConferenceLive:​liveId:​completion:​]] ​   |删除一路推流 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a94e5010c1349d40fddde33b7cc489274| requestTobeSpeaker:​adminId:​completion:​]] ​   |观众申请连麦成为主播 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​af2e99d266d5d3f67490903b17818b17c|requestTobeAdmin:​adminId:​completion:​]] ​   | 主播申请成为管理员, |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a097c52f9ea833b9ac289290247c25860|responseReqSpeaker:​memId:​result:​completion:​]] ​   |管理员同意/​拒绝观众的上麦申请,管理员调用 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a34ff57a1a78c507d5118249e4863b8ff|responseReqAdmin:​memId:​result:​completion:​]] ​   |管理员同意/​拒绝主播的申请管理员请求,管理员调用 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a6432c2f2a68760b1cad2dabb6e9cba6d|setMuteMember:​memId:​mute:​completion:​]] ​   |将指定成员静音/​解除静音,管理员调用 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a28bd7490cb8e238a07a5aec62d164cea|updateConferenceWithSwitchCamera:​]] ​   |切换前后摄像头 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​abc3d1658875a99bdd1f5f1158a74e789|updateConference:​isMute:​]] ​   |设置是否静音 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a478c1c8b08758a55fdea520d6772ae9e| updateConference:​enableVideo:​]] ​   |设置视频是否可用 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​ab57a72f199fa779b7b3158712e2d0a0a| updateConference:​maxVideoKbps:​]] ​   |更新视频最大码率 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​af906e2f6de9ca0d9005bf6c46c9d46e8|inputVideoSampleBuffer:​rotation:​conference:​publishedStreamId:​completion:​]] ​   |输入自定义本地视频数据 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a544a757e0f2a16c9b265243c784f1fd4|inputVideoPixelBuffer:​sampleBufferTime:​rotation:​conference:​publishedStreamId:​completion:​]] ​   |输入自定义本地视频数据 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​ae98d762cb6aed35de22da40779455fcb|setConferenceAttribute:​value:​completion:​]] ​   |设置会议属性, |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a226dbe5816201a32fa3850dd1bb3c991|deleteAttributeWithKey:​completion:​]] ​   |删除频道属性 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​aa7e7d1a088f746f98b796d94b3eb631d|startAudioMixing:​loop:​sendMix:​]] ​   |开启本地伴音功能 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a703c15925f723cc3c4ec59f347226a91|stopAudioMixing]] ​   | 关闭本地混音功能 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a9b9a168a7e160ff08b5ccf13d2b8ab2f|adjustAudioMixingVolume:​]] ​   | 设置伴奏音量 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a28f9297ad2411c6aa9bdbb291bb8df26|muteRemoteAudio:​mute:​]] ​   | mute远端音频 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a870a36303635529c953194b9d240521b| muteRemoteVideo:​mute:​]] ​   | mute远端视频 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​aa57d7c7aaec58f84fb0f81b38f36a2e4|enableStatistics:​]] ​   | 启用统计 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a0a7340e4fe935143ba0ff529858ad295| muteAll:​completion:​]] ​   | 全体静音 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a2fb9faf9d767ed2e58315c1db354df37| inputCustomAudioData:​]] ​   | 输入自定义外部音频数据 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a64ed13e19b65e3c8fb06113a96da37c5| addVideoWatermark:​]] ​   | 开启水印功能 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_i_e_m_conference_manager-p.html#​a950faf99db0d40e2a89c62fd8113f34d|clearVideoWatermark]] ​   | 取消水印功能 |
 +
 +== EMConferenceManagerDelegate ==
 +
 +^回调事件 ^描述 ^
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​a35ae6bcf830548162cc9c7e6f6df0cd8|memberDidJoin:​member:​]] ​   | 有人加入会议 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​a2349384a054912620858346dff81eb41|memberDidLeave:​member:​]] ​   | 有人离开会议 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​ada8c07dac796d492a5165b28b50fb02c|roleDidChanged:​]] ​   | 自己的角色发生变化 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​a0532f12f9e7ae6cf5f7837793062f4ea|adminDidChanged:​newAdmin:​]] ​   | 管理员新增 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​a56cc91df21df41aad12e33940bff987c|adminDidChanged:​removeAdmin:​]] ​   | 管理员放弃 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​a99ce878eb647a0ae04b8de3829e41f9b|streamPubDidFailed:​error:​]] ​   | 本地pub流失败 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​a3d7f3ca91389d95c6fe29f15d3d41fbb|DesktopStreamDidPubFailed:​error:​]] ​   | 发布共享桌面流失败 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​a0ac987ff2ecb9e34dc786f1da50466df|streamUpdateDidFailed:​error:​]] ​   | 本地update流失败,​视频超限 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​aaec86cf13eaa8930fa5261c1f3848785|streamDidUpdate:​addStream:​]] ​   | 有新的数据流上传 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​adee2f6109508e383000d5e99e33b9bde|streamDidUpdate:​stream:​]] ​   | 数据流有更新(是否静音,视频是否可用) |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​a4a92350f846d00e913c48a3286e9da77|streamDidUpdate:​removeStream:​]] ​   | 有数据流移除 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​a9e35574e2e6fcedcc5cf3d4f0f9a26e6|conferenceDidEnd:​reason:​error:​]] ​   | 会议已经结束 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​a37025ed1fda0ff26798c31aabbcf7105|streamStartTransmitting:​streamId:​]] ​   | 数据流已经开始传输数据 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​a2db2fd8d67522325ecb8412479842ccd|conferenceDidUpdated:​muteAll:​]] ​   | 收到全体静音/​解除全体静音的回调 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​a0917065a18ca3ce9433d6677288830c3|conferenceSpeakerDidChange:​speakingStreamIds:​]] ​   | 用户A用户B在同一个会议中,用户A开始说话时,用户B会收到该回调 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​a0917065a18ca3ce9433d6677288830c3|conferenceAttributeUpdated:​attributes:​]] ​   | 会议属性变更 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​afca1af08d628fd0be0ddd1bed1bf9138|conferenceReqSpeaker:​memId:​nickName:​memName:​]] ​   | 收到观众申请主播的请求,只有管理员会触发 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​ad90e5d598c1b8cb57a02cd6b81fc929e|conferenceReqAdmin:​memId:​nickName:​memName:​]] ​   | 收到主播申请管理员的请求,只有管理员会触发 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​a2b09dd5dbb6144cc561027378bc3f1e7|conferenceDidUpdated:​mute:​]] ​   | 收到静音/​解除静音的回调 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​ae598e6c668121780573ce2402be3d745|conferenceReqSpeakerRefused:​adminId:​]] ​   | 收到申请主播请求被拒绝的回调 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​aa285cef35bc14db40ef17be9245798ed|conferenceReqAdminRefused:​adminId:​]] ​   | 收到申请管理员请求被拒绝的回调 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​acccb25d9f4eef388781be85cfeefb5ff|conferenceDidUpdated:​liveCfg:​]] ​   | 会议开启cdn推流或自定义录制,收到LiveCfg的回调,只有管理员能收到 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​adde6567323a5ffaf249841da585174b8|streamIdDidUpdate:​rtcId:​streamId:​]] ​   | 收到本地流streamId的回调,发布流成功后收到此回调 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​aff906c9db5f6be3685a44e5c83d0ca0e|streamStateUpdated:​type:​state:​streamId:​]] ​   | 下行音频流无数据时,收到此回调 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​ab7ce7a7c76c5dca0918c8f8b5875b5e9|streamDidFirstFrameSended:​type:​streamId:​]] ​   | 发送第一帧音视频数据时,收到此回调 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​a1879f50eea95cdabbf0e265bc1a03fef|streamDidFirstFrameReceived:​type:​streamId:​]] ​   | 接收流第一帧音视频数据时,收到此回调 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​ac03c8bd548fd6ae0623eb0b6f99f8b80|confrenceDidUpdated:​state:​]] ​   | 会议状态改变时,收到此回调 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​ac03c8bd548fd6ae0623eb0b6f99f8b80|confrenceDidUpdated:​state:​]] ​   | 会议状态改变时,收到此回调 |
 +|[[http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​protocol_e_m_conference_manager_delegate-p.html#​a8940c01890169f6c5a68f8d6787264e9|onferenceDidUpdate:​streamId:​statReport:​]] ​   | 当前会议的媒体流质量报告回调 |
  
 +== EMCallConference ==
 +参见http://​sdkdocs.easemob.com/​apidoc/​ios/​chat3.0/​interface_e_m_call_conference.html