Browse Source

Merge branch 'conference'

* conference:
  conference code
  update ui
  update
  no message
  update proto
  conference

# Conflicts:
#	wfclient/WFChatClient.xcodeproj/project.pbxproj
#	wfclient/WFChatClient/Proto/mars.framework/Headers/comm/verinfo.h
#	wfclient/WFChatClient/Proto/mars.framework/mars
#	wfuikit/WFChatUIKit.xcodeproj/project.pbxproj
#	wfuikit/WFChatUIKit/MessageList/ViewController/WFCUMessageListViewController.m
heavyrain2012 4 years ago
parent
commit
38258bea35
31 changed files with 2112 additions and 26 deletions
  1. 8 1
      wfchat/WildFireChat/Discover/DiscoverViewController.m
  2. 9 0
      wfclient/WFChatClient.xcodeproj/project.pbxproj
  3. 1 1
      wfclient/WFChatClient/Client/Common.h
  4. 7 0
      wfclient/WFChatClient/Client/WFCCIMService.h
  5. 9 0
      wfclient/WFChatClient/Client/WFCCIMService.mm
  6. 17 0
      wfclient/WFChatClient/Client/WFCCNetworkService.h
  7. 22 2
      wfclient/WFChatClient/Client/WFCCNetworkService.mm
  8. 2 0
      wfclient/WFChatClient/Messages/WFCCCallStartMessageContent.h
  9. 4 0
      wfclient/WFChatClient/Messages/WFCCCallStartMessageContent.m
  10. 55 0
      wfclient/WFChatClient/Messages/WFCCConferenceInviteMessageContent.h
  11. 84 0
      wfclient/WFChatClient/Messages/WFCCConferenceInviteMessageContent.m
  12. 1 1
      wfclient/WFChatClient/Proto/mars.framework/Headers/comm/verinfo.h
  13. 9 1
      wfclient/WFChatClient/Proto/mars.framework/Headers/proto/proto.h
  14. BIN
      wfclient/WFChatClient/Proto/mars.framework/mars
  15. 1 0
      wfclient/WFChatClient/WFCChatClient.h
  16. 40 0
      wfuikit/WFChatUIKit.xcodeproj/project.pbxproj
  17. 0 15
      wfuikit/WFChatUIKit/AVEngine/WFAVEngineKit.framework/Headers/WFAVCallSession.h
  18. 62 1
      wfuikit/WFChatUIKit/AVEngine/WFAVEngineKit.framework/Headers/WFAVEngineKit.h
  19. BIN
      wfuikit/WFChatUIKit/AVEngine/WFAVEngineKit.framework/WFAVEngineKit
  20. 3 3
      wfuikit/WFChatUIKit/AVEngine/WFAVEngineKit.framework/_CodeSignature/CodeResources
  21. 12 0
      wfuikit/WFChatUIKit/MessageList/Cell/WFCUConferenceInviteCell.h
  22. 75 0
      wfuikit/WFChatUIKit/MessageList/Cell/WFCUConferenceInviteCell.m
  23. 8 0
      wfuikit/WFChatUIKit/MessageList/ViewController/WFCUMessageListViewController.m
  24. 19 0
      wfuikit/WFChatUIKit/Voip/Conference/WFCUConferenceInviteViewController.h
  25. 340 0
      wfuikit/WFChatUIKit/Voip/Conference/WFCUConferenceInviteViewController.m
  26. 27 0
      wfuikit/WFChatUIKit/Voip/Conference/WFCUConferenceViewController.h
  27. 1204 0
      wfuikit/WFChatUIKit/Voip/Conference/WFCUConferenceViewController.m
  28. 17 0
      wfuikit/WFChatUIKit/Voip/Conference/WFCUCreateConferenceViewController.h
  29. 73 0
      wfuikit/WFChatUIKit/Voip/Conference/WFCUCreateConferenceViewController.m
  30. 2 1
      wfuikit/WFChatUIKit/Voip/WFCUMultiVideoViewController.m
  31. 1 0
      wfuikit/WFChatUIKit/WFChatUIKit.h

+ 8 - 1
wfchat/WildFireChat/Discover/DiscoverViewController.m

@@ -35,7 +35,8 @@
           @"image":@"chat_channel",@"des":@"channel"},
         @{@"title":LocalizedString(@"DevDocs"),
           @"image":@"dev_docs",@"des":@"Dev"},@{@"title":@"Things",
-          @"image":@"discover_things",@"des":@"Things"}]];
+          @"image":@"discover_things",@"des":@"Things"},@{@"title":@"Conference",
+          @"image":@"discover_things",@"des":@"Conference"}]];
     
     if(NSClassFromString(@"SDTimeLineTableViewController")) {
         [self.dataSource insertObject:@{@"title":LocalizedString(@"Moments"),@"image":@"AlbumReflashIcon",@"des":@"moment"} atIndex:0];
@@ -140,6 +141,12 @@
         vc.hidesBottomBarWhenPushed = YES;
         [self.navigationController pushViewController:vc animated:YES];
     }
+    
+    if ([des isEqualToString:@"Conference"]) {
+        WFCUCreateConferenceViewController *vc = [[WFCUCreateConferenceViewController alloc] init];
+        vc.hidesBottomBarWhenPushed = YES;
+        [self.navigationController pushViewController:vc animated:YES];
+    }
 
 }
 

+ 9 - 0
wfclient/WFChatClient.xcodeproj/project.pbxproj

@@ -86,6 +86,8 @@
 		2F19723722C6FC31009F7055 /* WFCCGroupSetManagerNotificationContent.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F19723522C6FC31009F7055 /* WFCCGroupSetManagerNotificationContent.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		2F1A931A21B250F5006625CD /* WFCCTypingMessageContent.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F1A931821B250F5006625CD /* WFCCTypingMessageContent.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		2F1A931B21B250F5006625CD /* WFCCTypingMessageContent.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F1A931921B250F5006625CD /* WFCCTypingMessageContent.m */; };
+		2F1EB5BF2499AB4600FD3575 /* WFCCConferenceInviteMessageContent.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F1EB5BD2499AB4500FD3575 /* WFCCConferenceInviteMessageContent.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		2F1EB5C02499AB4600FD3575 /* WFCCConferenceInviteMessageContent.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F1EB5BE2499AB4600FD3575 /* WFCCConferenceInviteMessageContent.m */; };
 		2F3223D52144C5FB0016A2C4 /* WFCCVideoMessageContent.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F3223D32144C5FB0016A2C4 /* WFCCVideoMessageContent.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		2F3223D62144C5FB0016A2C4 /* WFCCVideoMessageContent.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F3223D42144C5FB0016A2C4 /* WFCCVideoMessageContent.m */; };
 		2F48C9C5214D16250092B167 /* WFCCRecallMessageContent.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F48C9C3214D16240092B167 /* WFCCRecallMessageContent.m */; };
@@ -224,6 +226,8 @@
 		2F19723522C6FC31009F7055 /* WFCCGroupSetManagerNotificationContent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WFCCGroupSetManagerNotificationContent.h; sourceTree = "<group>"; };
 		2F1A931821B250F5006625CD /* WFCCTypingMessageContent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WFCCTypingMessageContent.h; sourceTree = "<group>"; };
 		2F1A931921B250F5006625CD /* WFCCTypingMessageContent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WFCCTypingMessageContent.m; sourceTree = "<group>"; };
+		2F1EB5BD2499AB4500FD3575 /* WFCCConferenceInviteMessageContent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WFCCConferenceInviteMessageContent.h; sourceTree = "<group>"; };
+		2F1EB5BE2499AB4600FD3575 /* WFCCConferenceInviteMessageContent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WFCCConferenceInviteMessageContent.m; sourceTree = "<group>"; };
 		2F3223D32144C5FB0016A2C4 /* WFCCVideoMessageContent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WFCCVideoMessageContent.h; sourceTree = "<group>"; };
 		2F3223D42144C5FB0016A2C4 /* WFCCVideoMessageContent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WFCCVideoMessageContent.m; sourceTree = "<group>"; };
 		2F48C9C3214D16240092B167 /* WFCCRecallMessageContent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WFCCRecallMessageContent.m; sourceTree = "<group>"; };
@@ -367,6 +371,8 @@
 				2F7709962453DC740020DB77 /* Things */,
 				90D1F595208D65B500C2A9CA /* WFCCCallStartMessageContent.h */,
 				90D1F594208D65B500C2A9CA /* WFCCCallStartMessageContent.m */,
+				2F1EB5BD2499AB4500FD3575 /* WFCCConferenceInviteMessageContent.h */,
+				2F1EB5BE2499AB4600FD3575 /* WFCCConferenceInviteMessageContent.m */,
 				2E6B50CB1FAE9B10006B6E31 /* WFCCAddGroupeMemberNotificationContent.h */,
 				2E6B50CC1FAE9B10006B6E31 /* WFCCAddGroupeMemberNotificationContent.m */,
 				2E6B50CD1FAE9B10006B6E31 /* WFCCCreateGroupNotificationContent.h */,
@@ -530,6 +536,8 @@
 				2B6336762470DAB2008D6B38 /* WFCCDeliveryReport.h in Headers */,
 				2FDEEFD024A49C690032428A /* WFCCEnums.h in Headers */,
 				2F77099E2453DCB10020DB77 /* WFCCThingsLostEventContent.h in Headers */,
+				2FF2A46122C4DDF5006A6D4C /* WFCCGroupPrivateChatNotificationContent.h in Headers */,
+				2F1EB5BF2499AB4600FD3575 /* WFCCConferenceInviteMessageContent.h in Headers */,
 				2E6B51131FAE9B10006B6E31 /* WFCCConversation.h in Headers */,
 				2F7709A02453DCB10020DB77 /* WFCCThingsDataContent.h in Headers */,
 				2FD9513D243ACF3600EE4F60 /* WFCCPCOnlineInfo.h in Headers */,
@@ -725,6 +733,7 @@
 				2E6B511C1FAE9B10006B6E31 /* WFCCGroupInfo.m in Sources */,
 				90B1373D22D9D1CC005F103C /* WFCCGroupJoinTypeNotificationContent.m in Sources */,
 				2BC9EDDF24D99CB3004B3F0B /* WFCCGroupMemberAllowNotificationContent.m in Sources */,
+				2F1EB5C02499AB4600FD3575 /* WFCCConferenceInviteMessageContent.m in Sources */,
 				2E6B51161FAE9B10006B6E31 /* WFCCConversationInfo.m in Sources */,
 				6E6C69A720B959F100006628 /* WFCCGroupSearchInfo.m in Sources */,
 				2E6B50B41FAE990D006B6E31 /* wav_amr.mm in Sources */,

+ 1 - 1
wfclient/WFChatClient/Client/Common.h

@@ -98,7 +98,7 @@
 #define VOIP_CONTENT_TYPE_ACCEPT_T 405
 #define VOIP_CONTENT_TYPE_ADD_PARTICIPANT 406
 #define VOIP_CONTENT_MUTE_VIDEO 407
-
+#define VOIP_CONTENT_CONFERENCE_INVITE 408
 
 
 #define THINGS_CONTENT_TYPE_DATA 501

+ 7 - 0
wfclient/WFChatClient/Client/WFCCIMService.h

@@ -1526,4 +1526,11 @@ typedef NS_ENUM(NSInteger, WFCCPlatformType) {
 是否支持已送达报告和已阅读报告
 */
 - (BOOL)isReceiptEnabled;
+
+- (void)sendConferenceRequest:(long long)sessionId
+                         room:(NSString *)roomId
+                      request:(NSString *)request
+                         data:(NSString *)data
+                      success:(void(^)(NSString *authorizedUrl))successBlock
+                        error:(void(^)(int error_code))errorBlock;
 @end

+ 9 - 0
wfclient/WFChatClient/Client/WFCCIMService.mm

@@ -2307,4 +2307,13 @@ public:
 - (BOOL)isReceiptEnabled {
     return mars::stn::IsReceiptEnabled() == true;
 }
+
+- (void)sendConferenceRequest:(long long)sessionId
+                         room:(NSString *)roomId
+                      request:(NSString *)request
+                         data:(NSString *)data
+                      success:(void(^)(NSString *authorizedUrl))successBlock
+                        error:(void(^)(int error_code))errorBlock {
+    mars::stn::sendConferenceRequest(sessionId, roomId?[roomId UTF8String]:"", [request UTF8String], data ? [data UTF8String]:"", new IMGeneralStringCallback(successBlock, errorBlock));
+}
 @end

+ 17 - 0
wfclient/WFChatClient/Client/WFCCNetworkService.h

@@ -130,6 +130,19 @@ typedef NS_ENUM(NSInteger, ConnectionStatus) {
 - (BOOL)onReceiveMessage:(WFCCMessage *)message;
 @end
 
+/**
+ 会议事件的监听
+ */
+@protocol ConferenceEventDelegate <NSObject>
+
+/**
+ 会议事件的回调
+
+ @param event 事件
+ */
+- (void)onConferenceEvent:(NSString *)event;
+@end
+
 #pragma mark - 连接服务
 /**
  连接服务
@@ -153,6 +166,10 @@ typedef NS_ENUM(NSInteger, ConnectionStatus) {
  */
 @property(nonatomic, weak) id<ReceiveMessageDelegate> receiveMessageDelegate;
 
+/**
+会议事件监听
+*/
+@property(nonatomic, weak) id<ConferenceEventDelegate> conferenceEventDelegate;
 /**
  当前是否处于登陆状态
  */

+ 22 - 2
wfclient/WFChatClient/Client/WFCCNetworkService.mm

@@ -121,6 +121,20 @@ public:
     id<ReceiveMessageDelegate> m_delegate;
 };
 
+class CONFCB : public mars::stn::ConferenceEventCallback {
+public:
+  CONFCB(id<ConferenceEventDelegate> delegate) : m_delegate(delegate) {
+  }
+  void onConferenceEvent(const std::string &event) {
+    if (m_delegate) {
+        [m_delegate onConferenceEvent:[NSString stringWithUTF8String:event.c_str()]];
+    }
+  }
+    
+  id<ConferenceEventDelegate> m_delegate;
+};
+
+
 WFCCUserInfo* convertUserInfo(const mars::stn::TUserInfo &tui) {
     WFCCUserInfo *userInfo = [[WFCCUserInfo alloc] init];
     userInfo.userId = [NSString stringWithUTF8String:tui.uid.c_str()];
@@ -294,7 +308,7 @@ public:
 
 
 
-@interface WFCCNetworkService () <ConnectionStatusDelegate, ReceiveMessageDelegate, RefreshUserInfoDelegate, RefreshGroupInfoDelegate, WFCCNetworkStatusDelegate, RefreshFriendListDelegate, RefreshFriendRequestDelegate, RefreshSettingDelegate, RefreshChannelInfoDelegate, RefreshGroupMemberDelegate>
+@interface WFCCNetworkService () <ConnectionStatusDelegate, ReceiveMessageDelegate, RefreshUserInfoDelegate, RefreshGroupInfoDelegate, WFCCNetworkStatusDelegate, RefreshFriendListDelegate, RefreshFriendRequestDelegate, RefreshSettingDelegate, RefreshChannelInfoDelegate, RefreshGroupMemberDelegate, ConferenceEventDelegate>
 @property(nonatomic, assign)ConnectionStatus currentConnectionStatus;
 @property (nonatomic, strong)NSString *userId;
 @property (nonatomic, strong)NSString *passwd;
@@ -420,7 +434,12 @@ static WFCCNetworkService * sharedSingleton = nil;
             [self.receiveMessageDelegate onMessageDelivered:delivereds];
         }
     });
-    
+}
+
+- (void)onConferenceEvent:(NSString *)event {
+    dispatch_async(dispatch_get_main_queue(), ^{
+        [self.conferenceEventDelegate onConferenceEvent:event];
+    });
 }
 
 - (void)addReceiveMessageFilter:(id<ReceiveMessageFilter>)filter {
@@ -631,6 +650,7 @@ static WFCCNetworkService * sharedSingleton = nil;
   mars::app::SetCallback(mars::app::AppCallBack::Instance());
   mars::stn::setConnectionStatusCallback(new CSCB(self));
   mars::stn::setReceiveMessageCallback(new RPCB(self));
+    mars::stn::setConferenceEventCallback(new CONFCB(self));
   mars::stn::setRefreshUserInfoCallback(new GUCB(self));
   mars::stn::setRefreshGroupInfoCallback(new GGCB(self));
     mars::stn::setRefreshGroupMemberCallback(new GGMCB(self));

+ 2 - 0
wfclient/WFChatClient/Messages/WFCCCallStartMessageContent.h

@@ -49,4 +49,6 @@ WFAVCallEndReason
  */
 @property (nonatomic, assign, getter=isAudioOnly)BOOL audioOnly;
 
+@property (nonatomic, strong)NSString *pin;
+
 @end

+ 4 - 0
wfclient/WFChatClient/Messages/WFCCCallStartMessageContent.m

@@ -28,6 +28,9 @@
     if (self.status) {
         [dataDict setObject:@(self.status) forKey:@"s"];
     }
+    if (self.pin) {
+        [dataDict setObject:self.pin forKey:@"p"];
+    }
     
     [dataDict setObject:self.targetIds forKey:@"ts"];
     //多人音视频与单人音视频兼容
@@ -53,6 +56,7 @@
         self.status = dictionary[@"s"] ? [dictionary[@"s"] intValue] : 0;
         self.audioOnly = [dictionary[@"a"] intValue] ? YES : NO;
         self.targetIds = dictionary[@"ts"];
+        self.pin = dictionary[@"p"];
         if (self.targetIds.count == 0) {
             NSString *target = dictionary[@"t"];
             NSMutableArray *arr = [[NSMutableArray alloc] init];

+ 55 - 0
wfclient/WFChatClient/Messages/WFCCConferenceInviteMessageContent.h

@@ -0,0 +1,55 @@
+//
+//  WFCCConferenceInviteMessageContent.h
+//  WFChatClient
+//
+//  Created by heavyrain on 2017/8/16.
+//  Copyright © 2017年 WildFireChat. All rights reserved.
+//
+
+#import "WFCCMessageContent.h"
+
+/**
+ 电话总结消息
+ */
+@interface WFCCConferenceInviteMessageContent : WFCCMessageContent
+
+
+/**
+ CallId
+ */
+@property (nonatomic, strong)NSString *callId;
+
+/**
+ 会议主持人
+*/
+@property (nonatomic, strong)NSString *host;
+/**
+ 会议标题
+ */
+@property (nonatomic, strong)NSString *title;
+
+/**
+ 会议描述
+ */
+@property (nonatomic, strong)NSString *desc;
+/**
+ 会议开始时间
+ */
+@property (nonatomic, assign)long long startTime;
+
+/*
+ 是否音频会议
+ */
+@property (nonatomic, assign, getter=isAudioOnly)BOOL audioOnly;
+
+/*
+ 会议密码
+*/
+@property (nonatomic, strong)NSString *pin;
+
+/*
+ 是否是会议观众
+*/
+@property (nonatomic, assign)BOOL audience;
+
+@end

+ 84 - 0
wfclient/WFChatClient/Messages/WFCCConferenceInviteMessageContent.m

@@ -0,0 +1,84 @@
+//
+//  WFCCConferenceInviteMessageContent.m
+//  WFChatClient
+//
+//  Created by heavyrain on 2017/8/16.
+//  Copyright © 2017年 WildFireChat. All rights reserved.
+//
+
+#import "WFCCConferenceInviteMessageContent.h"
+#import "WFCCIMService.h"
+#import "Common.h"
+
+
+@implementation WFCCConferenceInviteMessageContent
+- (WFCCMessagePayload *)encode {
+    
+    WFCCMessagePayload *payload = [super encode];
+    payload.contentType = [self.class getContentType];
+    payload.content = self.callId;
+    
+    NSMutableDictionary *dataDict = [NSMutableDictionary dictionary];
+    if (self.host) {
+        [dataDict setObject:self.host forKey:@"h"];
+    }
+    if (self.startTime) {
+        [dataDict setObject:@(self.startTime) forKey:@"s"];
+    }
+    if (self.title) {
+        [dataDict setObject:self.title forKey:@"t"];
+    }
+    if (self.desc) {
+        [dataDict setObject:self.desc forKey:@"d"];
+    }
+    if (self.pin) {
+        [dataDict setObject:self.pin forKey:@"p"];
+    }
+    
+    [dataDict setValue:@(self.audioOnly?1:0) forKey:@"a"];
+    [dataDict setValue:@(self.audience?1:0) forKey:@"audience"];
+    
+    payload.binaryContent = [NSJSONSerialization dataWithJSONObject:dataDict
+                                                            options:kNilOptions
+                                                              error:nil];
+    return payload;
+}
+
+- (void)decode:(WFCCMessagePayload *)payload {
+    [super decode:payload];
+    self.callId = payload.content;
+    NSError *__error = nil;
+    NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:payload.binaryContent
+                                                               options:kNilOptions
+                                                                 error:&__error];
+    if (!__error) {
+        self.host = dictionary[@"h"];
+        self.startTime = dictionary[@"s"] ? [dictionary[@"s"] longLongValue] : 0;
+        self.title = dictionary[@"t"];
+        self.desc = dictionary[@"d"];
+        self.audioOnly = [dictionary[@"a"] intValue] ? YES : NO;
+        self.audience = [dictionary[@"audience"] intValue] ? YES : NO;
+        self.pin = dictionary[@"p"];
+    }
+}
+
++ (int)getContentType {
+    return VOIP_CONTENT_CONFERENCE_INVITE;
+}
+
++ (int)getContentFlags {
+    return WFCCPersistFlag_PERSIST_AND_COUNT;
+}
+
++ (void)load {
+    [[WFCCIMService sharedWFCIMService] registerMessageContent:self];
+}
+
+- (NSString *)digest:(WFCCMessage *)message {
+    if (_audioOnly) {
+        return @"[音频会议邀请]";
+    } else {
+        return @"[视频会议邀请]";
+    }
+}
+@end

+ 1 - 1
wfclient/WFChatClient/Proto/mars.framework/Headers/comm/verinfo.h

@@ -5,7 +5,7 @@
 #define MARS_REVISION "99765985"
 #define MARS_PATH "firechat"
 #define MARS_URL ""
-#define MARS_BUILD_TIME "2020-08-19 18:19:43"
+#define MARS_BUILD_TIME "2020-08-24 12:16:52"
 #define MARS_TAG ""
 
 #endif

+ 9 - 1
wfclient/WFChatClient/Proto/mars.framework/Headers/proto/proto.h

@@ -691,6 +691,11 @@ namespace mars{
             virtual void onUserReceivedMessage(const std::map<std::string, int64_t> &userReceived) = 0;
             virtual void onUserReadedMessage(const std::list<TReadEntry> &userReceived) = 0;
         };
+    
+        class ConferenceEventCallback {
+        public:
+            virtual void onConferenceEvent(const std::string &event) = 0;
+        };
 
         extern bool setAuthInfo(const std::string &userId, const std::string &token);
         extern void Disconnect(uint8_t flag);
@@ -698,6 +703,7 @@ namespace mars{
         extern void AppWillTerminate();
         extern void setConnectionStatusCallback(ConnectionStatusCallback *callback);
         extern void setReceiveMessageCallback(ReceiveMessageCallback *callback);
+        extern void setConferenceEventCallback(ConferenceEventCallback *callback);
     
         extern void setDNSResult(std::vector<std::string> serverIPs);
         extern void setRefreshUserInfoCallback(GetUserInfoCallback *callback);
@@ -813,7 +819,9 @@ namespace mars{
 
         extern bool IsCommercialServer();
         extern bool IsReceiptEnabled();
-
+    
+    extern void sendConferenceRequest(int64_t sessionId, const std::string &roomId, const std::string &request, const std::string &data, GeneralStringCallback *callback);
+    
         extern bool filesystem_exists(const std::string &path);
 		extern bool filesystem_create_directories(const std::string &path);
         extern bool filesystem_copy_file(const std::string &source, const std::string &dest, bool overwrite);

BIN
wfclient/WFChatClient/Proto/mars.framework/mars


+ 1 - 0
wfclient/WFChatClient/WFCChatClient.h

@@ -59,6 +59,7 @@ FOUNDATION_EXPORT const unsigned char WFChatClientVersionString[];
 
 #import <WFChatClient/WFCCThingsDataContent.h>
 #import <WFChatClient/WFCCThingsLostEventContent.h>
+#import <WFChatClient/WFCCConferenceInviteMessageContent.h>
 #import <WFChatClient/WFCCConversation.h>
 #import <WFChatClient/WFCCConversationInfo.h>
 #import <WFChatClient/WFCCConversationSearchInfo.h>

+ 40 - 0
wfuikit/WFChatUIKit.xcodeproj/project.pbxproj

@@ -89,6 +89,14 @@
 		2BEF78A923F6A84D000DE285 /* WFCUParticipantCollectionViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 2BEF789D23F6A84D000DE285 /* WFCUParticipantCollectionViewCell.h */; };
 		2F1439F5217F5DAA00B3E38A /* WFCUSearchGroupTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F1439F3217F5DAA00B3E38A /* WFCUSearchGroupTableViewCell.h */; };
 		2F1439F6217F5DAA00B3E38A /* WFCUSearchGroupTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F1439F4217F5DAA00B3E38A /* WFCUSearchGroupTableViewCell.m */; };
+		2F1EB5B22499A21A00FD3575 /* WFCUConferenceViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F1EB5B02499A21A00FD3575 /* WFCUConferenceViewController.m */; };
+		2F1EB5B32499A21A00FD3575 /* WFCUConferenceViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F1EB5B12499A21A00FD3575 /* WFCUConferenceViewController.h */; };
+		2F1EB5B72499A30500FD3575 /* WFCUCreateConferenceViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F1EB5B52499A30500FD3575 /* WFCUCreateConferenceViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		2F1EB5B82499A30500FD3575 /* WFCUCreateConferenceViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F1EB5B62499A30500FD3575 /* WFCUCreateConferenceViewController.m */; };
+		2F1EB5BB2499AA5E00FD3575 /* WFCUConferenceInviteViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F1EB5B92499AA5E00FD3575 /* WFCUConferenceInviteViewController.h */; };
+		2F1EB5BC2499AA5E00FD3575 /* WFCUConferenceInviteViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F1EB5BA2499AA5E00FD3575 /* WFCUConferenceInviteViewController.m */; };
+		2F1EB5C32499AFF900FD3575 /* WFCUConferenceInviteCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F1EB5C12499AFF900FD3575 /* WFCUConferenceInviteCell.h */; };
+		2F1EB5C42499AFF900FD3575 /* WFCUConferenceInviteCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F1EB5C22499AFF900FD3575 /* WFCUConferenceInviteCell.m */; };
 		2F337EE6219EE1840086B0F3 /* WFCUTextCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F337EE5219EE1840086B0F3 /* WFCUTextCell.h */; };
 		2F341A4F235EF12B00F0D1B6 /* WFCUGroupAnnouncement.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F341A4D235EF12B00F0D1B6 /* WFCUGroupAnnouncement.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		2F341A50235EF12B00F0D1B6 /* WFCUGroupAnnouncement.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F341A4E235EF12B00F0D1B6 /* WFCUGroupAnnouncement.m */; };
@@ -513,6 +521,14 @@
 		2F1439F4217F5DAA00B3E38A /* WFCUSearchGroupTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WFCUSearchGroupTableViewCell.m; sourceTree = "<group>"; };
 		2F143A68217F7D4100B3E38A /* Stickers.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Stickers.bundle; sourceTree = "<group>"; };
 		2F143A69217F7D4100B3E38A /* Emoj.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Emoj.plist; sourceTree = "<group>"; };
+		2F1EB5B02499A21A00FD3575 /* WFCUConferenceViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WFCUConferenceViewController.m; sourceTree = "<group>"; };
+		2F1EB5B12499A21A00FD3575 /* WFCUConferenceViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WFCUConferenceViewController.h; sourceTree = "<group>"; };
+		2F1EB5B52499A30500FD3575 /* WFCUCreateConferenceViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WFCUCreateConferenceViewController.h; sourceTree = "<group>"; };
+		2F1EB5B62499A30500FD3575 /* WFCUCreateConferenceViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WFCUCreateConferenceViewController.m; sourceTree = "<group>"; };
+		2F1EB5B92499AA5E00FD3575 /* WFCUConferenceInviteViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WFCUConferenceInviteViewController.h; sourceTree = "<group>"; };
+		2F1EB5BA2499AA5E00FD3575 /* WFCUConferenceInviteViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WFCUConferenceInviteViewController.m; sourceTree = "<group>"; };
+		2F1EB5C12499AFF900FD3575 /* WFCUConferenceInviteCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WFCUConferenceInviteCell.h; sourceTree = "<group>"; };
+		2F1EB5C22499AFF900FD3575 /* WFCUConferenceInviteCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WFCUConferenceInviteCell.m; sourceTree = "<group>"; };
 		2F337EE5219EE1840086B0F3 /* WFCUTextCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WFCUTextCell.h; sourceTree = "<group>"; };
 		2F341A4D235EF12B00F0D1B6 /* WFCUGroupAnnouncement.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WFCUGroupAnnouncement.h; sourceTree = "<group>"; };
 		2F341A4E235EF12B00F0D1B6 /* WFCUGroupAnnouncement.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WFCUGroupAnnouncement.m; sourceTree = "<group>"; };
@@ -977,6 +993,19 @@
 			path = Resources;
 			sourceTree = "<group>";
 		};
+		2F1EB5B42499A2CA00FD3575 /* Conference */ = {
+			isa = PBXGroup;
+			children = (
+				2F1EB5B92499AA5E00FD3575 /* WFCUConferenceInviteViewController.h */,
+				2F1EB5BA2499AA5E00FD3575 /* WFCUConferenceInviteViewController.m */,
+				2F1EB5B12499A21A00FD3575 /* WFCUConferenceViewController.h */,
+				2F1EB5B02499A21A00FD3575 /* WFCUConferenceViewController.m */,
+				2F1EB5B52499A30500FD3575 /* WFCUCreateConferenceViewController.h */,
+				2F1EB5B62499A30500FD3575 /* WFCUCreateConferenceViewController.m */,
+			);
+			path = Conference;
+			sourceTree = "<group>";
+		};
 		2F341A4C235EF11700F0D1B6 /* Model */ = {
 			isa = PBXGroup;
 			children = (
@@ -996,6 +1025,7 @@
 		2F4991052189239E005F6A84 /* Voip */ = {
 			isa = PBXGroup;
 			children = (
+				2F1EB5B42499A2CA00FD3575 /* Conference */,
 				2BEF789723F6A84D000DE285 /* WFCUFloatingWindow.h */,
 				2BEF789C23F6A84D000DE285 /* WFCUFloatingWindow.m */,
 				2BEF789323F6A84C000DE285 /* WFCUMultiVideoViewController.h */,
@@ -1111,6 +1141,8 @@
 				2FD550EB2442AA0B00B3EE09 /* WFCURecallCell.m */,
 				2B46D4DA24ACC9B900721792 /* MediaMessageGridViewCell.h */,
 				2B46D4DB24ACC9B900721792 /* MediaMessageGridViewCell.m */,
+				2F1EB5C12499AFF900FD3575 /* WFCUConferenceInviteCell.h */,
+				2F1EB5C22499AFF900FD3575 /* WFCUConferenceInviteCell.m */,
 			);
 			path = Cell;
 			sourceTree = "<group>";
@@ -1848,6 +1880,7 @@
 				2F36433321C136C400904CAB /* WFCUFavChannelTableViewController.h in Headers */,
 				2F5511F3217F5CC300F56C26 /* UITabBar+badge.h in Headers */,
 				2BD671A42336E821007A9FEC /* WFCUConfigManager.h in Headers */,
+				2F1EB5B72499A30500FD3575 /* WFCUCreateConferenceViewController.h in Headers */,
 				2F5510BD217F5CC200F56C26 /* WFCUGeneralModifyViewController.h in Headers */,
 				2F551155217F5CC200F56C26 /* KZVideoConfig.h in Headers */,
 				2F5511F7217F5CC300F56C26 /* BubbleTipView.h in Headers */,
@@ -1875,6 +1908,7 @@
 				2F5510AC217F5CC200F56C26 /* WFCUMessageListViewController.h in Headers */,
 				2FF70EAC2402101000946D2D /* WFCUWaitingAnimationView.h in Headers */,
 				2F551222217F5CC300F56C26 /* WFCUModifyMyProfileViewController.h in Headers */,
+				2F1EB5C32499AFF900FD3575 /* WFCUConferenceInviteCell.h in Headers */,
 				2F551223217F5CC300F56C26 /* WFCUMessageNotificationViewController.h in Headers */,
 				2BDF57AC24AD70C8003C1F08 /* DNAlbumCell.h in Headers */,
 				2BDF579324AD70C8003C1F08 /* DNImagePickerHelper.h in Headers */,
@@ -1951,6 +1985,7 @@
 				2F5510BE217F5CC200F56C26 /* WFCUSearchViewController.h in Headers */,
 				2F551197217F5CC200F56C26 /* SDRefreshHeaderView.h in Headers */,
 				2F551104217F5CC200F56C26 /* CCHMapClusterAnnotation.h in Headers */,
+				2F1EB5BB2499AA5E00FD3575 /* WFCUConferenceInviteViewController.h in Headers */,
 				2BEF78A123F6A84D000DE285 /* WFCUParticipantCollectionViewLayout.h in Headers */,
 				2F5511D1217F5CC300F56C26 /* TYShowAlertView.h in Headers */,
 				2F5511FC217F5CC300F56C26 /* WFCUConversationSettingMemberCell.h in Headers */,
@@ -1990,6 +2025,7 @@
 				1450EA8924389CA200BF51FC /* WFCUSeletedUserSearchResultViewController.h in Headers */,
 				2FD550EC2442AA0B00B3EE09 /* WFCURecallCell.h in Headers */,
 				2F5511D7217F5CC300F56C26 /* TYAlertController.h in Headers */,
+				2F1EB5B32499A21A00FD3575 /* WFCUConferenceViewController.h in Headers */,
 				2F5510F4217F5CC200F56C26 /* CCHCenterOfMassMapClusterer.h in Headers */,
 				2F5510F6217F5CC200F56C26 /* CCHMapAnimator.h in Headers */,
 				2F5511AF217F5CC300F56C26 /* FLAnimatedImageView.h in Headers */,
@@ -2198,6 +2234,7 @@
 				2F5511C4217F5CC300F56C26 /* NSImage+WebCache.m in Sources */,
 				2B46D4D924ACC64300721792 /* WFCUMediaMessageGridViewController.m in Sources */,
 				2F55120A217F5CC300F56C26 /* WFCUConversationTableViewController.m in Sources */,
+				2F1EB5C42499AFF900FD3575 /* WFCUConferenceInviteCell.m in Sources */,
 				2F551167217F5CC200F56C26 /* AFURLResponseSerialization.m in Sources */,
 				2F551112217F5CC200F56C26 /* VideoPlayerSampleViewController.m in Sources */,
 				2F551115217F5CC200F56C26 /* FullScreenView.m in Sources */,
@@ -2211,6 +2248,7 @@
 				2F5511C7217F5CC300F56C26 /* UIView+WebCacheOperation.m in Sources */,
 				2F551195217F5CC200F56C26 /* SDRefreshFooterView.m in Sources */,
 				2F5510E3217F5CC200F56C26 /* WFCUEmotionTextAttachment.m in Sources */,
+				2F1EB5BC2499AA5E00FD3575 /* WFCUConferenceInviteViewController.m in Sources */,
 				2F5511AB217F5CC300F56C26 /* FLAnimatedImageView+WebCache.m in Sources */,
 				1450EA8A24389CA200BF51FC /* WFCUSeletedUserSearchResultViewController.m in Sources */,
 				2F341A50235EF12B00F0D1B6 /* WFCUGroupAnnouncement.m in Sources */,
@@ -2243,6 +2281,7 @@
 				2BDF579F24AD70C8003C1F08 /* DNImagePickerHelper.m in Sources */,
 				2F5511F6217F5CC300F56C26 /* TabbarButton.m in Sources */,
 				2F5510BB217F5CC200F56C26 /* WFCUInviteGroupMemberViewController.m in Sources */,
+				2F1EB5B22499A21A00FD3575 /* WFCUConferenceViewController.m in Sources */,
 				2F551228217F5CC300F56C26 /* WFCUFriendRequestViewController.m in Sources */,
 				2F5511D9217F5CC300F56C26 /* TYAlertDropDownAnimation.m in Sources */,
 				2F5EB44522C2EE1900BC8C90 /* GroupMuteTableViewController.m in Sources */,
@@ -2258,6 +2297,7 @@
 				2F5511A0217F5CC200F56C26 /* UIImage+WebP.m in Sources */,
 				2F551192217F5CC200F56C26 /* SDRefreshHeaderView.m in Sources */,
 				2F5511CC217F5CC300F56C26 /* UIButton+WebCache.m in Sources */,
+				2F1EB5B82499A30500FD3575 /* WFCUCreateConferenceViewController.m in Sources */,
 				2F551113217F5CC200F56C26 /* VideoPlayerSampleView.m in Sources */,
 				2F5EB44922C2EEFC00BC8C90 /* WFCUGeneralSwitchTableViewCell.m in Sources */,
 				2F5511BE217F5CC300F56C26 /* SDWebImageDownloaderOperation.m in Sources */,

+ 0 - 15
wfuikit/WFChatUIKit/AVEngine/WFAVEngineKit.framework/Headers/WFAVCallSession.h

@@ -1,15 +0,0 @@
-//
-//  WFAVCallSession.h
-//  WFAVEngineKit
-//
-//  Created by dali on 2020/1/18.
-//  Copyright © 2020 wildfirechat. All rights reserved.
-//
-
-#import <Foundation/Foundation.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-
-
-NS_ASSUME_NONNULL_END

+ 62 - 1
wfuikit/WFChatUIKit/AVEngine/WFAVEngineKit.framework/Headers/WFAVEngineKit.h

@@ -304,6 +304,37 @@ typedef NS_ENUM(NSInteger, WFAVCallEndReason) {
                   conversation:(WFCCConversation *)conversation
                sessionDelegate:(id<WFAVCallSessionDelegate>)sessionDelegate;
 
+
+
+
+- (void)listConference:(void(^_Nullable)(NSArray<NSDictionary *> * _Nullable conferences))successBlock
+                 error:(void(^_Nullable)(int error_code))errorBlock;
+
+/**
+ 发起会议
+
+ @param audioOnly 是否语音会议
+ @param sessionDelegate 通话Session的监听
+ @return 通话Session
+ */
+- (WFAVCallSession *_Nonnull)startConference:(NSString *_Nullable)callId
+                                   audioOnly:(BOOL)audioOnly
+                                         pin:(NSString *_Nonnull)pin
+                                        host:(NSString *_Nullable)host
+                                       title:(NSString *_Nullable)title
+                                        desc:(NSString *_Nullable)desc
+                                    audience:(BOOL)audience
+                             sessionDelegate:(id<WFAVCallSessionDelegate>_Nonnull)sessionDelegate;
+
+- (WFAVCallSession *_Nonnull)joinConference:(NSString *_Nonnull)callId
+                                  audioOnly:(BOOL)audioOnly
+                                        pin:(NSString *_Nonnull)pin
+                                       host:(NSString *_Nullable)host
+                                      title:(NSString *_Nullable)title
+                                       desc:(NSString *_Nullable)desc
+                                   audience:(BOOL)audience
+                             sessionDelegate:(id<WFAVCallSessionDelegate>_Nonnull)sessionDelegate;
+
 /**
  开启画面预览
  */
@@ -394,10 +425,40 @@ typedef NS_ENUM(NSInteger, WFAVCallEndReason) {
 @property(nonatomic, assign, readonly)WFAVCallEndReason endReason;
 
 /**
- 是否是语音电话
+ 是否是扬声器
  */
 @property(nonatomic, assign, getter=isSpeaker, readonly)BOOL speaker;
 
+/**
+是否是会议
+*/
+@property(nonatomic, assign, getter=isConference, readonly) BOOL conference;
+
+/**
+是否观众,仅当会议有效
+*/
+@property(nonatomic, assign, getter=isAudience, readonly) BOOL audience;
+
+/**
+会议密码,仅当会议有效
+*/
+@property(nonatomic, strong) NSString * _Nullable pin;
+
+/**
+会议主持人,仅当会议有效
+*/
+@property(nonatomic, strong) NSString * _Nullable host;
+
+/**
+会议标题,仅当会议有效
+*/
+@property(nonatomic, strong) NSString * _Nullable title;
+
+/**
+会议描述,仅当会议有效
+*/
+@property(nonatomic, strong) NSString * _Nullable desc;
+
 
 /**
 通话成员(不包含自己)

BIN
wfuikit/WFChatUIKit/AVEngine/WFAVEngineKit.framework/WFAVEngineKit


+ 3 - 3
wfuikit/WFChatUIKit/AVEngine/WFAVEngineKit.framework/_CodeSignature/CodeResources

@@ -6,7 +6,7 @@
 	<dict>
 		<key>Headers/WFAVEngineKit.h</key>
 		<data>
-		VGTYHr0n5s+4sAOqdfMX2mKqZXY=
+		5asAL9/cGuLr8uQsGBenW3CVziw=
 		</data>
 		<key>Info.plist</key>
 		<data>
@@ -23,11 +23,11 @@
 		<dict>
 			<key>hash</key>
 			<data>
-			VGTYHr0n5s+4sAOqdfMX2mKqZXY=
+			5asAL9/cGuLr8uQsGBenW3CVziw=
 			</data>
 			<key>hash2</key>
 			<data>
-			mlOuwYEssxsjtuK4Cqa1MAxVKzhryVAR0q3JfMyYofk=
+			RPciSpa4vRLF6e9lA8xKT4wXnjAicolSX1gNWTbvHUQ=
 			</data>
 		</dict>
 		<key>LICENSE.md</key>

+ 12 - 0
wfuikit/WFChatUIKit/MessageList/Cell/WFCUConferenceInviteCell.h

@@ -0,0 +1,12 @@
+//
+//  WFCUConferenceInviteCell.h
+//  WFChat UIKit
+//
+//  Created by WF Chat on 2017/9/1.
+//  Copyright © 2017年 WildFireChat. All rights reserved.
+//
+
+#import "WFCUMessageCell.h"
+
+@interface WFCUConferenceInviteCell : WFCUMessageCell
+@end

+ 75 - 0
wfuikit/WFChatUIKit/MessageList/Cell/WFCUConferenceInviteCell.m

@@ -0,0 +1,75 @@
+//
+//  InformationCell.m
+//  WFChat UIKit
+//
+//  Created by WF Chat on 2017/9/1.
+//  Copyright © 2017年 WildFireChat. All rights reserved.
+//
+
+#import "WFCUConferenceInviteCell.h"
+#import <WFChatClient/WFCChatClient.h>
+#import "WFCUUtilities.h"
+#import "UILabel+YBAttributeTextTapAction.h"
+
+#define TEXT_TOP_PADDING 6
+#define TEXT_BUTTOM_PADDING 6
+#define TEXT_LEFT_PADDING 8
+#define TEXT_RIGHT_PADDING 8
+
+
+#define TEXT_LABEL_TOP_PADDING TEXT_TOP_PADDING + 4
+#define TEXT_LABEL_BUTTOM_PADDING TEXT_BUTTOM_PADDING + 4
+#define TEXT_LABEL_LEFT_PADDING 30
+#define TEXT_LABEL_RIGHT_PADDING 30
+
+@interface WFCUConferenceInviteCell ()
+@property (nonatomic, strong)UILabel *infoLabel;
+@end
+
+@implementation WFCUConferenceInviteCell
+
++ (CGSize)sizeForClientArea:(WFCUMessageModel *)msgModel withViewWidth:(CGFloat)width {
+    return CGSizeMake(width, 80);
+}
+
+- (void)setModel:(WFCUMessageModel *)model {
+    [super setModel:model];
+    
+    CGRect frame = self.contentArea.bounds;
+    
+    WFCCConferenceInviteMessageContent *content = (WFCCConferenceInviteMessageContent *)model.message.content;
+    [self.infoLabel yb_removeAttributeTapActions];
+    
+    self.infoLabel.text = @"邀请您参加会议";
+    self.infoLabel.textColor = [UIColor grayColor];
+    
+    self.infoLabel.layoutMargins = UIEdgeInsetsMake(TEXT_TOP_PADDING, TEXT_LEFT_PADDING, TEXT_BUTTOM_PADDING, TEXT_RIGHT_PADDING);
+    self.infoLabel.frame = frame;
+}
+
+- (UILabel *)infoLabel {
+    if (!_infoLabel) {
+        _infoLabel = [[UILabel alloc] init];
+        _infoLabel.numberOfLines = 0;
+        _infoLabel.font = [UIFont systemFontOfSize:14];
+        
+        _infoLabel.textColor = [UIColor whiteColor];
+        _infoLabel.numberOfLines = 0;
+        _infoLabel.lineBreakMode = NSLineBreakByTruncatingTail;
+        _infoLabel.textAlignment = NSTextAlignmentCenter;
+        _infoLabel.font = [UIFont systemFontOfSize:14.f];
+        _infoLabel.layer.masksToBounds = YES;
+        _infoLabel.layer.cornerRadius = 5.f;
+        _infoLabel.textAlignment = NSTextAlignmentCenter;
+        _infoLabel.backgroundColor = [UIColor clearColor];
+        
+        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTaped:)];
+        [_infoLabel addGestureRecognizer:tap];
+        tap.cancelsTouchesInView = NO;
+        [_infoLabel setUserInteractionEnabled:YES];
+        
+        [self.contentArea addSubview:_infoLabel];
+    }
+    return _infoLabel; 
+}
+@end

+ 8 - 0
wfuikit/WFChatUIKit/MessageList/ViewController/WFCUMessageListViewController.m

@@ -21,6 +21,7 @@
 #import "WFCUStickerCell.h"
 #import "WFCUVideoCell.h"
 #import "WFCURecallCell.h"
+#import "WFCUConferenceInviteCell.h"
 #import "WFCUBrowserViewController.h"
 #import <WFChatClient/WFCChatClient.h>
 #import "WFCUProfileTableViewController.h"
@@ -60,6 +61,7 @@
 #import "WFCUConversationSearchTableViewController.h"
 
 #import "WFCUMediaMessageGridViewController.h"
+#import "WFCUConferenceViewController.h"
 
 @interface WFCUMessageListViewController () <UITextFieldDelegate, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UINavigationControllerDelegate, WFCUMessageCellDelegate, AVAudioPlayerDelegate, WFCUChatInputBarDelegate, SDPhotoBrowserDelegate, UIGestureRecognizerDelegate>
 @property (nonatomic, strong)NSMutableArray<WFCUMessageModel *> *modelList;
@@ -682,6 +684,7 @@
     [self registerCell:[WFCUInformationCell class] forContent:[WFCCTipNotificationContent class]];
     [self registerCell:[WFCUInformationCell class] forContent:[WFCCUnknownMessageContent class]];
     [self registerCell:[WFCURecallCell class] forContent:[WFCCRecallMessageContent class]];
+    [self registerCell:[WFCUConferenceInviteCell class] forContent:[WFCCConferenceInviteMessageContent class]];
     
     
     [self.collectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"HeaderView"];
@@ -1436,6 +1439,11 @@
         } else {
             [self startPlay:model];
         }
+    } else if([model.message.content isKindOfClass:[WFCCConferenceInviteMessageContent class]]) {
+        WFCCConferenceInviteMessageContent *invite = (WFCCConferenceInviteMessageContent *)model.message.content;
+        
+        WFCUConferenceViewController *vc = [[WFCUConferenceViewController alloc] initWithInvite:invite];
+        [[WFAVEngineKit sharedEngineKit] presentViewController:vc];
     }
 }
 

+ 19 - 0
wfuikit/WFChatUIKit/Voip/Conference/WFCUConferenceInviteViewController.h

@@ -0,0 +1,19 @@
+//
+//  WFCUConferenceInviteViewController.h
+//  WildFireChat
+//
+//  Created by heavyrain lee on 2018/9/27.
+//  Copyright © 2018 WildFireChat. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+#import <WFChatClient/WFCChatClient.h>
+
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface WFCUConferenceInviteViewController : UIViewController
+@property (nonatomic, strong) WFCCConferenceInviteMessageContent *invite;
+@end
+
+NS_ASSUME_NONNULL_END

+ 340 - 0
wfuikit/WFChatUIKit/Voip/Conference/WFCUConferenceInviteViewController.m

@@ -0,0 +1,340 @@
+//
+//  ForwardViewController.m
+//  WildFireChat
+//
+//  Created by heavyrain lee on 2018/9/27.
+//  Copyright © 2018 WildFireChat. All rights reserved.
+//
+
+#import "WFCUConferenceInviteViewController.h"
+#import "SDWebImage.h"
+#import "WFCUForwardMessageCell.h"
+#import "WFCUContactTableViewCell.h"
+#import "WFCUSearchGroupTableViewCell.h"
+#import "TYAlertView.h"
+#import "TYAlertController.h"
+#import "WFCUShareMessageView.h"
+#import "UIView+TYAlertView.h"
+#import "UIView+Toast.h"
+#import "WFCUContactListViewController.h"
+#import "WFCUConfigManager.h"
+#import "UIImage+ERCategory.h"
+
+@interface WFCUConferenceInviteViewController () <UITableViewDataSource, UISearchControllerDelegate, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating>
+@property (nonatomic, strong)UITableView *tableView;
+@property (nonatomic, strong)UISearchController *searchController;
+@property (nonatomic, strong)NSMutableArray<WFCCConversationInfo *> *conversations;
+@property (nonatomic, strong)NSArray<WFCCUserInfo *>  *searchFriendList;
+@property (nonatomic, strong)NSArray<WFCCGroupSearchInfo *>  *searchGroupList;
+@end
+
+@implementation WFCUConferenceInviteViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    
+    CGRect frame = self.view.frame;
+    self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 54, frame.size.width, frame.size.height - 64)];
+    self.tableView.delegate = self;
+    self.tableView.dataSource = self;
+    self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
+    
+    self.tableView.tableHeaderView = nil;
+
+    self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:WFCString(@"Cancel") style:UIBarButtonItemStyleDone target:self action:@selector(onLeftBarBtn:)];
+
+    self.conversations = [[[WFCCIMService sharedWFCIMService] getConversationInfos:@[@(Single_Type), @(Group_Type)] lines:@[@(0)]] mutableCopy];
+
+    self.extendedLayoutIncludesOpaqueBars = YES;
+    
+    self.searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
+    self.searchController.searchResultsUpdater = self;
+    self.searchController.delegate = self;
+    self.searchController.dimsBackgroundDuringPresentation = NO;
+    
+    if (@available(iOS 13, *)) {
+        self.searchController.searchBar.searchBarStyle = UISearchBarStyleDefault;
+        self.searchController.searchBar.searchTextField.backgroundColor = [WFCUConfigManager globalManager].naviBackgroudColor;
+        UIImage* searchBarBg = [UIImage imageWithColor:[UIColor whiteColor] size:CGSizeMake(self.view.frame.size.width - 8 * 2, 36) cornerRadius:4];
+        [self.searchController.searchBar setSearchFieldBackgroundImage:searchBarBg forState:UIControlStateNormal];
+    } else {
+        [self.searchController.searchBar setValue:WFCString(@"Cancel") forKey:@"_cancelButtonText"];
+    }
+    
+    if (@available(iOS 9.1, *)) {
+        self.searchController.obscuresBackgroundDuringPresentation = NO;
+    }
+    
+    [self.searchController.searchBar setPlaceholder:WFCString(@"Search")];
+    
+    if (@available(iOS 11.0, *)) {
+        self.navigationItem.searchController = _searchController;
+        _searchController.hidesNavigationBarDuringPresentation = YES;
+    } else {
+        self.tableView.tableHeaderView = _searchController.searchBar;
+    }
+    self.definesPresentationContext = YES;
+    
+    self.tableView.sectionIndexColor = [UIColor grayColor];
+    [self.view addSubview:self.tableView];
+    [self.tableView reloadData];
+}
+
+- (void)onLeftBarBtn:(UIBarButtonItem *)sender {
+    [self.navigationController dismissViewControllerAnimated:YES completion:nil];
+}
+
+- (void)altertSend:(WFCCConversation *)conversation {
+    WFCUShareMessageView *shareView = [WFCUShareMessageView createViewFromNib];
+    
+    shareView.conversation = conversation;
+    WFCCMessage *message = [[WFCCMessage alloc] init];
+    message.conversation = conversation;
+    message.content = self.invite;
+    shareView.message = message;
+    __weak typeof(self)ws = self;
+    shareView.forwardDone = ^(BOOL success) {
+        if (success) {
+            [ws.view makeToast:WFCString(@"ForwardSuccess") duration:1 position:CSToastPositionCenter];
+            [ws.navigationController dismissViewControllerAnimated:YES completion:nil];
+        } else {
+            [ws.view makeToast:WFCString(@"ForwardFailure") duration:1 position:CSToastPositionCenter];
+        }
+    };
+    TYAlertController *alertController = [TYAlertController alertControllerWithAlertView:shareView preferredStyle:TYAlertControllerStyleAlert];
+    
+//    // blur effect
+//    [alertController setBlurEffectWithView:self.view];
+//
+    //alertController.alertViewOriginY = 60;
+    [self.navigationController presentViewController:alertController animated:YES completion:nil];
+//    [shareView showInWindow];
+}
+#pragma mark - UITableViewDataSource
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+    if (self.searchController.active) {
+        int sec = 0;
+        if (self.searchFriendList.count) {
+            sec++;
+            if (section == sec-1) {
+                return self.searchFriendList.count;
+            }
+        }
+        
+        if (self.searchGroupList.count) {
+            sec++;
+            if (section == sec-1) {
+                return self.searchGroupList.count;
+            }
+        }
+        
+        return 0;
+    } else {
+        if (section == 0) {
+            return 1;
+        }
+        return self.conversations.count;
+    }
+}
+
+// Row display. Implementers should *always* try to reuse cells by setting each cell's reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier:
+// Cell gets various attributes set automatically based on table (separators) and data source (accessory views, editing controls)
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+#define REUSECONVIDENTIFY @"resueConvCell"
+#define REUSENEWCONVIDENTIFY @"resueNewConvCell"
+    if (self.searchController.active) {
+        int sec = 0;
+        if (self.searchFriendList.count) {
+            sec++;
+            if (indexPath.section == sec-1) {
+                WFCUContactTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"friendCell"];
+                if (cell == nil) {
+                    cell = [[WFCUContactTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"friendCell"];
+                }
+                cell.userId = self.searchFriendList[indexPath.row].userId;
+                return cell;
+            }
+        }
+        if (self.searchGroupList.count) {
+            sec++;
+            if (indexPath.section == sec-1) {
+                WFCUSearchGroupTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"groupCell"];
+                if (cell == nil) {
+                    cell = [[WFCUSearchGroupTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"groupCell"];
+                }
+                cell.groupSearchInfo = self.searchGroupList[indexPath.row];
+                return cell;
+            }
+        }
+        return nil;
+    } else {
+        if (indexPath.section == 0) {
+            UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:REUSENEWCONVIDENTIFY];
+            if(!cell) {
+                cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:REUSENEWCONVIDENTIFY];
+                cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
+            }
+            cell.textLabel.text = WFCString(@"CreateNewChat");
+            return cell;
+        } else {
+            WFCUForwardMessageCell *cell = [tableView dequeueReusableCellWithIdentifier:REUSECONVIDENTIFY];
+            if(!cell) {
+                cell = [[WFCUForwardMessageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:REUSECONVIDENTIFY];
+            }
+            WFCCConversationInfo *info = [self.conversations objectAtIndex:indexPath.row];
+            cell.conversation = info.conversation;
+            return cell;
+        }
+        
+    }
+    
+    return nil;
+}
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
+    if (self.searchController.active) {
+        int sec = 0;
+        if (self.searchFriendList.count) {
+            sec++;
+        }
+        
+        if (self.searchGroupList.count) {
+            sec++;
+        }
+        
+        if (sec == 0) {
+            sec = 1;
+        }
+        return sec;
+    }
+    return 2;
+}
+
+- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
+    if (section == 0) {
+        if (self.searchController.isActive) {
+            return 44;
+        } else {
+            return 0;
+        }
+    }
+    return 21;
+}
+
+- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
+    if (self.searchController.isActive) {
+        if (self.searchGroupList.count + self.searchFriendList.count > 0) {
+            UIView *header = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.tableView.frame.size.width, section == 0 ? 44 : 20)];
+            UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, section == 0 ? 24 : 0, self.tableView.frame.size.width, 20)];
+            
+            label.font = [UIFont systemFontOfSize:13];
+            label.textColor = [UIColor grayColor];
+            label.textAlignment = NSTextAlignmentLeft;
+            label.backgroundColor = [WFCUConfigManager globalManager].backgroudColor;
+            
+            int sec = 0;
+            if (self.searchFriendList.count) {
+                sec++;
+                if (section == sec-1) {
+                    label.text = WFCString(@"Contact");
+                }
+            }
+            
+            if (self.searchGroupList.count) {
+                sec++;
+                if (section == sec-1) {
+                    label.text = WFCString(@"Group");
+                }
+            }
+
+            [header addSubview:label];
+            return header;
+        } else {
+            UIView *header = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.tableView.frame.size.width, 50)];
+            return header;
+        }
+    } else {
+        NSString *title = WFCString(@"RecentChat");
+        UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 21)];
+        label.font = [UIFont systemFontOfSize:13];
+        label.textColor = [UIColor grayColor];
+        label.textAlignment = NSTextAlignmentLeft;
+        label.text = [NSString stringWithFormat:@"  %@", title];
+        label.backgroundColor = [WFCUConfigManager globalManager].backgroudColor;
+        return label;
+    }
+}
+
+- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
+    return 56;
+}
+
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+    WFCCConversation *selectedConv;
+    if (self.searchController.isActive) {
+        if (indexPath.section == 0 && self.searchFriendList.count > 0) {
+            WFCCUserInfo *userInfo = self.searchFriendList[indexPath.row];
+            selectedConv = [[WFCCConversation alloc] init];
+            selectedConv.type = Single_Type;
+            selectedConv.target = userInfo.userId;
+            selectedConv.line = 0;
+        } else {
+            WFCCGroupInfo *groupInfo = self.searchGroupList[indexPath.row].groupInfo;
+            selectedConv = [[WFCCConversation alloc] init];
+            selectedConv.type = Group_Type;
+            selectedConv.target = groupInfo.target;
+            selectedConv.line = 0;
+        }
+    } else {
+        if (indexPath.section == 0) { //new conversation
+            WFCUContactListViewController *pvc = [[WFCUContactListViewController alloc] init];
+            pvc.selectContact = YES;
+            pvc.multiSelect = NO;
+            pvc.isPushed = YES;
+            __weak typeof(self)ws = self;
+            pvc.selectResult = ^(NSArray<NSString *> *contacts) {
+                dispatch_async(dispatch_get_main_queue(), ^{
+                    if (contacts.count == 1) {
+                        WFCCConversation *conversation = [[WFCCConversation alloc] init];
+                        conversation.type = Single_Type;
+                        conversation.target = contacts[0];
+                        conversation.line = 0;
+                        [ws altertSend:conversation];
+                    }
+                });
+            };
+            
+            [self.navigationController pushViewController:pvc animated:YES];
+            return;
+        } else {
+            selectedConv = self.conversations[indexPath.row].conversation;
+        }
+    }
+    if (selectedConv) {
+        [self altertSend:selectedConv];
+    }
+}
+
+- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
+    if (self.searchController.active) {
+        [self.searchController.searchBar resignFirstResponder];
+    }
+}
+#pragma mark - UISearchControllerDelegate
+-(void)updateSearchResultsForSearchController:(UISearchController *)searchController {
+    NSString *searchString = [self.searchController.searchBar text];
+    
+    if (searchString.length) {
+        self.searchFriendList = [[WFCCIMService sharedWFCIMService] searchFriends:searchString];
+        self.searchGroupList = [[WFCCIMService sharedWFCIMService] searchGroups:searchString];
+    } else {
+        self.searchFriendList = nil;
+        self.searchGroupList = nil;
+    }
+    
+    
+    [self.tableView reloadData];
+}
+@end

+ 27 - 0
wfuikit/WFChatUIKit/Voip/Conference/WFCUConferenceViewController.h

@@ -0,0 +1,27 @@
+//
+//  ViewController.h
+//  WFDemo
+//
+//  Created by heavyrain on 17/9/27.
+//  Copyright © 2017年 WildFireChat. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@class WFAVCallSession;
+@class WFCCConversation;
+@class WFCCConferenceInviteMessageContent;
+@interface WFCUConferenceViewController : UIViewController
+- (instancetype)initWithSession:(WFAVCallSession *)session;
+- (instancetype)initWithInvite:(WFCCConferenceInviteMessageContent *)invite;
+
+- (instancetype)initWithCallId:(NSString *_Nonnull)callId
+                     audioOnly:(BOOL)audioOnly
+                           pin:(NSString *_Nonnull)pin
+                          host:(NSString *_Nullable)host
+                         title:(NSString *_Nullable)title
+                          desc:(NSString *_Nullable)desc
+                      audience:(BOOL)audience
+                        moCall:(BOOL)moCall;
+@end
+

+ 1204 - 0
wfuikit/WFChatUIKit/Voip/Conference/WFCUConferenceViewController.m

@@ -0,0 +1,1204 @@
+//
+//  ViewController.m
+//  WFDemo
+//
+//  Created by heavyrain on 17/9/27.
+//  Copyright © 2017年 WildFireChat. All rights reserved.
+//
+
+
+#import "WFCUConferenceViewController.h"
+#import <AVFoundation/AVFoundation.h>
+#import <AVKit/AVKit.h>
+#if WFCU_SUPPORT_VOIP
+#import <WebRTC/WebRTC.h>
+#import <WFAVEngineKit/WFAVEngineKit.h>
+#import "WFCUFloatingWindow.h"
+#import "WFCUParticipantCollectionViewCell.h"
+#endif
+#import "SDWebImage.h"
+#import <WFChatClient/WFCCConversation.h>
+#import "WFCUPortraitCollectionViewCell.h"
+#import "WFCUParticipantCollectionViewLayout.h"
+#import "WFCUSeletedUserViewController.h"
+#import "UIView+Toast.h"
+#import "WFCUConferenceInviteViewController.h"
+
+
+@interface WFCUConferenceViewController () <UITextFieldDelegate
+#if WFCU_SUPPORT_VOIP
+    ,WFAVCallSessionDelegate
+#endif
+    ,UICollectionViewDataSource
+    ,UICollectionViewDelegate
+>
+#if WFCU_SUPPORT_VOIP
+@property (nonatomic, strong) UIView *bigVideoView;
+@property (nonatomic, strong) UICollectionView *smallCollectionView;
+
+@property (nonatomic, strong) UICollectionView *portraitCollectionView;
+@property (nonatomic, strong) UIButton *hangupButton;
+@property (nonatomic, strong) UIButton *answerButton;
+@property (nonatomic, strong) UIButton *switchCameraButton;
+@property (nonatomic, strong) UIButton *audioButton;
+@property (nonatomic, strong) UIButton *speakerButton;
+@property (nonatomic, strong) UIButton *videoButton;
+@property (nonatomic, strong) UIButton *scalingButton;
+
+@property (nonatomic, strong) UIButton *minimizeButton;
+@property (nonatomic, strong) UIButton *addParticipantButton;
+
+@property (nonatomic, strong) UIImageView *portraitView;
+@property (nonatomic, strong) UILabel *userNameLabel;
+@property (nonatomic, strong) UILabel *stateLabel;
+@property (nonatomic, strong) UILabel *connectTimeLabel;
+
+@property (nonatomic, strong) WFAVCallSession *currentSession;
+
+@property (nonatomic, assign) WFAVVideoScalingType smallScalingType;
+@property (nonatomic, assign) WFAVVideoScalingType bigScalingType;
+
+@property (nonatomic, assign) CGPoint panStartPoint;
+@property (nonatomic, assign) CGRect panStartVideoFrame;
+@property (nonatomic, strong) NSTimer *connectedTimer;
+
+@property (nonatomic, strong) NSMutableArray<NSString *> *participants;
+
+//视频时,大屏用户正在说话
+@property (nonatomic, strong)UIImageView *speakingView;
+#endif
+@end
+
+#define ButtonSize 60
+#define BottomPadding 36
+#define SmallVideoView 120
+#define OperationTitleFont 10
+#define OperationButtonSize 50
+
+#define PortraitItemSize 48
+#define PortraitLabelSize 16
+
+@implementation WFCUConferenceViewController
+#if !WFCU_SUPPORT_VOIP
+- (instancetype)initWithSession:(WFAVCallSession *)session {
+    self = [super init];
+    return self;
+}
+
+- (instancetype)initWithTargets:(NSArray<NSString *> *)targetIds conversation:(WFCCConversation *)conversation audioOnly:(BOOL)audioOnly {
+    self = [super init];
+    return self;
+}
+#else
+- (instancetype)initWithSession:(WFAVCallSession *)session {
+    self = [super init];
+    if (self) {
+        self.currentSession = session;
+        self.currentSession.delegate = self;
+        [self rearrangeParticipants];
+    }
+    return self;
+}
+
+- (instancetype)initWithInvite:(WFCCConferenceInviteMessageContent *)invite {
+    self = [super init];
+    if (self) {
+        self.currentSession = [[WFAVEngineKit sharedEngineKit]
+                               joinConference:invite.callId
+                                    audioOnly:invite.audioOnly
+                                        pin:invite.pin
+                               host:invite.host
+                               title:invite.title
+                               desc:invite.desc
+                               audience:invite.audience
+                               sessionDelegate:self];
+        
+        
+        
+        
+        [self didChangeState:kWFAVEngineStateIncomming];
+        [self rearrangeParticipants];
+    }
+    return self;
+}
+- (instancetype)initWithCallId:(NSString *_Nonnull)callId
+                     audioOnly:(BOOL)audioOnly
+                           pin:(NSString *_Nonnull)pin
+                          host:(NSString *_Nullable)host
+                         title:(NSString *_Nullable)title
+                          desc:(NSString *_Nullable)desc
+                      audience:(BOOL)audience
+                        moCall:(BOOL)moCall {
+    self = [super init];
+    if (self) {
+        if (moCall) {
+            self.currentSession = [[WFAVEngineKit sharedEngineKit] startConference:nil audioOnly:audioOnly pin:pin host:host title:title desc:desc audience:audience sessionDelegate:self];
+            
+            [self didChangeState:kWFAVEngineStateOutgoing];
+        } else {
+            self.currentSession = [[WFAVEngineKit sharedEngineKit]
+                                   joinConference:callId
+                                        audioOnly:audioOnly
+                                            pin:pin
+                               host:host
+                               title:title
+                               desc:desc
+                               audience:audience
+                               sessionDelegate:self];
+            [self didChangeState:kWFAVEngineStateIncomming];
+        
+        }
+        
+        
+        
+        [self rearrangeParticipants];
+    }
+    return self;
+}
+/*
+ session的participantIds是除了自己外的所有成员。这里把自己也加入列表,然后把发起者放到最后面。
+ */
+- (void)rearrangeParticipants {
+    self.participants = [[NSMutableArray alloc] init];
+    [self.participants addObjectsFromArray:self.currentSession.participantIds];
+    if ([self.currentSession.initiator isEqualToString:[WFCCNetworkService sharedInstance].userId]) {
+        [self.participants addObject:[WFCCNetworkService sharedInstance].userId];
+    } else {
+        [self.participants insertObject:[WFCCNetworkService sharedInstance].userId atIndex:[self.participants indexOfObject:self.currentSession.initiator]];
+        [self.participants removeObject:self.currentSession.initiator];
+        [self.participants addObject:self.currentSession.initiator];
+    }
+}
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    [self.view setBackgroundColor:[UIColor blackColor]];
+    
+    self.smallScalingType = kWFAVVideoScalingTypeAspectFill;
+    self.bigScalingType = kWFAVVideoScalingTypeAspectBalanced;
+    self.bigVideoView = [[UIView alloc] initWithFrame:self.view.bounds];
+    UITapGestureRecognizer *tapBigVideo = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onClickedBigVideoView:)];
+    [self.bigVideoView addGestureRecognizer:tapBigVideo];
+    [self.view addSubview:self.bigVideoView];
+    
+    UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
+    CGFloat itemWidth = (self.view.frame.size.width + layout.minimumLineSpacing)/3 - layout.minimumLineSpacing;
+    layout.itemSize = CGSizeMake(itemWidth, itemWidth);
+    layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
+    int lines = (int)([self.currentSession participantIds].count + 2) /3;
+    self.smallCollectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, kStatusBarAndNavigationBarHeight, self.view.frame.size.width, (itemWidth + layout.minimumLineSpacing)*lines-layout.minimumLineSpacing) collectionViewLayout:layout];
+    
+    self.smallCollectionView.dataSource = self;
+    self.smallCollectionView.delegate = self;
+    [self.smallCollectionView registerClass:[WFCUParticipantCollectionViewCell class] forCellWithReuseIdentifier:@"cell"];
+    self.smallCollectionView.backgroundColor = [UIColor clearColor];
+    
+    [self.smallCollectionView addGestureRecognizer:[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(onSmallVideoPan:)]];
+    if (self.currentSession.audioOnly) {
+        self.smallCollectionView.hidden = YES;
+    }
+    [self.view addSubview:self.smallCollectionView];
+    
+    
+    WFCUParticipantCollectionViewLayout *layout2 = [[WFCUParticipantCollectionViewLayout alloc] init];
+    layout2.itemHeight = PortraitItemSize + PortraitLabelSize;
+    layout2.itemWidth = PortraitItemSize;
+    layout2.lineSpace = 6;
+    layout2.itemSpace = 6;
+
+    self.portraitCollectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(16, self.view.frame.size.height - BottomPadding - ButtonSize - (PortraitItemSize + PortraitLabelSize)*3 - PortraitLabelSize, self.view.frame.size.width - 32, (PortraitItemSize + PortraitLabelSize)*3 + PortraitLabelSize) collectionViewLayout:layout2];
+    self.portraitCollectionView.dataSource = self;
+    self.portraitCollectionView.delegate = self;
+    [self.portraitCollectionView registerClass:[WFCUPortraitCollectionViewCell class] forCellWithReuseIdentifier:@"cell2"];
+    self.portraitCollectionView.backgroundColor = [UIColor clearColor];
+    [self.view addSubview:self.portraitCollectionView];
+    
+    
+    [self checkAVPermission];
+    
+    if(self.currentSession.state == kWFAVEngineStateOutgoing && !self.currentSession.isAudioOnly) {
+        [[WFAVEngineKit sharedEngineKit] startPreview];
+    }
+    
+    WFCCUserInfo *user = [[WFCCIMService sharedWFCIMService] getUserInfo:self.currentSession.initiator inGroup:self.currentSession.conversation.type == Group_Type ? self.currentSession.conversation.target : nil refresh:NO];
+    
+    self.portraitView = [[UIImageView alloc] init];
+    [self.portraitView sd_setImageWithURL:[NSURL URLWithString:[user.portrait stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]] placeholderImage:[UIImage imageNamed:@"PersonalChat"]];
+    self.portraitView.layer.masksToBounds = YES;
+    self.portraitView.layer.cornerRadius = 8.f;
+    [self.view addSubview:self.portraitView];
+    
+    
+    self.userNameLabel = [[UILabel alloc] init];
+    self.userNameLabel.font = [UIFont systemFontOfSize:26];
+    self.userNameLabel.text = user.displayName;
+    self.userNameLabel.textColor = [UIColor whiteColor];
+    [self.view addSubview:self.userNameLabel];
+    
+    self.stateLabel = [[UILabel alloc] init];
+    self.stateLabel.font = [UIFont systemFontOfSize:16];
+    self.stateLabel.textColor = [UIColor whiteColor];
+    [self.view addSubview:self.stateLabel];
+    
+    self.connectTimeLabel = [[UILabel alloc] init];
+    self.connectTimeLabel.font = [UIFont systemFontOfSize:16];
+    self.connectTimeLabel.textColor = [UIColor whiteColor];
+    [self.view addSubview:self.connectTimeLabel];
+    
+    
+    
+    [self updateTopViewFrame];
+    
+    [self didChangeState:self.currentSession.state];//update ui
+    
+    
+    [[NSNotificationCenter defaultCenter] addObserver:self
+                                             selector:@selector(onDeviceOrientationDidChange)
+                                                 name:UIDeviceOrientationDidChangeNotification
+                                               object:nil];
+    [self onDeviceOrientationDidChange];
+
+}
+
+- (UIButton *)hangupButton {
+    if (!_hangupButton) {
+        _hangupButton = [[UIButton alloc] init];
+        [_hangupButton setImage:[UIImage imageNamed:@"hangup"] forState:UIControlStateNormal];
+        [_hangupButton setImage:[UIImage imageNamed:@"hangup_hover"] forState:UIControlStateHighlighted];
+        [_hangupButton setImage:[UIImage imageNamed:@"hangup_hover"] forState:UIControlStateSelected];
+        _hangupButton.backgroundColor = [UIColor clearColor];
+        [_hangupButton addTarget:self action:@selector(hanupButtonDidTap:) forControlEvents:UIControlEventTouchDown];
+        _hangupButton.hidden = YES;
+        [self.view addSubview:_hangupButton];
+    }
+    return _hangupButton;
+}
+
+- (UIButton *)answerButton {
+    if (!_answerButton) {
+        _answerButton = [[UIButton alloc] init];
+        
+        [_answerButton setImage:[UIImage imageNamed:@"answer"] forState:UIControlStateNormal];
+        [_answerButton setImage:[UIImage imageNamed:@"answer_hover"] forState:UIControlStateHighlighted];
+        [_answerButton setImage:[UIImage imageNamed:@"answer_hover"] forState:UIControlStateSelected];
+        
+        _answerButton.backgroundColor = [UIColor clearColor];
+        [_answerButton addTarget:self action:@selector(answerButtonDidTap:) forControlEvents:UIControlEventTouchDown];
+        _answerButton.hidden = YES;
+        [self.view addSubview:_answerButton];
+    }
+    return _answerButton;
+}
+
+- (UIButton *)minimizeButton {
+    if (!_minimizeButton) {
+        _minimizeButton = [[UIButton alloc] initWithFrame:CGRectMake(16, 26, 30, 30)];
+        
+        [_minimizeButton setImage:[UIImage imageNamed:@"minimize"] forState:UIControlStateNormal];
+        [_minimizeButton setImage:[UIImage imageNamed:@"minimize_hover"] forState:UIControlStateHighlighted];
+        [_minimizeButton setImage:[UIImage imageNamed:@"minimize_hover"] forState:UIControlStateSelected];
+        
+        _minimizeButton.backgroundColor = [UIColor clearColor];
+        [_minimizeButton addTarget:self action:@selector(minimizeButtonDidTap:) forControlEvents:UIControlEventTouchDown];
+        _minimizeButton.hidden = YES;
+        [self.view addSubview:_minimizeButton];
+    }
+    return _minimizeButton;
+}
+
+- (UIButton *)addParticipantButton {
+    if (!_addParticipantButton) {
+        _addParticipantButton = [[UIButton alloc] initWithFrame:CGRectMake(self.view.frame.size.width - 16 - 30, 26, 30, 30)];
+        
+        [_addParticipantButton setImage:[UIImage imageNamed:@"plus-circle"] forState:UIControlStateNormal];
+        [_addParticipantButton setImage:[UIImage imageNamed:@"plus-circle"] forState:UIControlStateHighlighted];
+        [_addParticipantButton setImage:[UIImage imageNamed:@"plus-circle"] forState:UIControlStateSelected];
+        
+        _addParticipantButton.backgroundColor = [UIColor clearColor];
+        [_addParticipantButton addTarget:self action:@selector(addParticipantButtonDidTap:) forControlEvents:UIControlEventTouchDown];
+        _addParticipantButton.hidden = YES;
+        [self.view addSubview:_addParticipantButton];
+    }
+    return _addParticipantButton;
+}
+
+- (UIButton *)switchCameraButton {
+    if (!_switchCameraButton) {
+        _switchCameraButton = [[UIButton alloc] init];
+        [_switchCameraButton setImage:[UIImage imageNamed:@"switchcamera"] forState:UIControlStateNormal];
+        [_switchCameraButton setImage:[UIImage imageNamed:@"switchcamera_hover"] forState:UIControlStateHighlighted];
+        [_switchCameraButton setImage:[UIImage imageNamed:@"switchcamera_hover"] forState:UIControlStateSelected];
+        _switchCameraButton.backgroundColor = [UIColor clearColor];
+        [_switchCameraButton addTarget:self action:@selector(switchCameraButtonDidTap:) forControlEvents:UIControlEventTouchDown];
+        _switchCameraButton.hidden = YES;
+        [self.view addSubview:_switchCameraButton];
+    }
+    return _switchCameraButton;
+}
+
+- (UIButton *)audioButton {
+    if (!_audioButton) {
+        _audioButton = [[UIButton alloc] initWithFrame:CGRectMake(self.view.frame.size.width/2-ButtonSize/2, self.view.frame.size.height-10-ButtonSize, ButtonSize, ButtonSize)];
+        [_audioButton setImage:[UIImage imageNamed:@"mute"] forState:UIControlStateNormal];
+        [_audioButton setImage:[UIImage imageNamed:@"mute_hover"] forState:UIControlStateHighlighted];
+        [_audioButton setImage:[UIImage imageNamed:@"mute_hover"] forState:UIControlStateSelected];
+        _audioButton.backgroundColor = [UIColor clearColor];
+        [_audioButton addTarget:self action:@selector(audioButtonDidTap:) forControlEvents:UIControlEventTouchDown];
+        _audioButton.hidden = YES;
+        [self updateAudioButton];
+        [self.view addSubview:_audioButton];
+    }
+    return _audioButton;
+}
+- (UIButton *)speakerButton {
+    if (!_speakerButton) {
+        _speakerButton = [[UIButton alloc] initWithFrame:CGRectMake(self.view.frame.size.width/2-ButtonSize/2, self.view.frame.size.height-10-ButtonSize, ButtonSize, ButtonSize)];
+        [_speakerButton setImage:[UIImage imageNamed:@"speaker"] forState:UIControlStateNormal];
+        [_speakerButton setImage:[UIImage imageNamed:@"speaker_hover"] forState:UIControlStateHighlighted];
+        [_speakerButton setImage:[UIImage imageNamed:@"speaker_hover"] forState:UIControlStateSelected];
+        _speakerButton.backgroundColor = [UIColor clearColor];
+        [_speakerButton addTarget:self action:@selector(speakerButtonDidTap:) forControlEvents:UIControlEventTouchDown];
+        _speakerButton.hidden = YES;
+        [self.view addSubview:_speakerButton];
+    }
+    return _speakerButton;
+}
+
+- (UIButton *)videoButton {
+    if (!_videoButton) {
+        _videoButton = [[UIButton alloc] initWithFrame:CGRectMake(self.view.frame.size.width*3/4-ButtonSize/4, self.view.frame.size.height-45-ButtonSize-ButtonSize/2-2, ButtonSize/2, ButtonSize/2)];
+        
+        [_videoButton setImage:[UIImage imageNamed:@"enable_video"] forState:UIControlStateNormal];
+        _videoButton.backgroundColor = [UIColor clearColor];
+        [_videoButton addTarget:self action:@selector(videoButtonDidTap:) forControlEvents:UIControlEventTouchDown];
+        _videoButton.hidden = YES;
+        [self updateVideoButton];
+        [self.view addSubview:_videoButton];
+    }
+    return _videoButton;
+}
+
+- (UIButton *)scalingButton {
+    if (!_scalingButton) {
+        _scalingButton = [[UIButton alloc] initWithFrame:CGRectMake(self.view.frame.size.width/2-ButtonSize/2, self.view.frame.size.height-10-ButtonSize, ButtonSize, ButtonSize)];
+        [_scalingButton setTitle:WFCString(@"Scale") forState:UIControlStateNormal];
+        _scalingButton.backgroundColor = [UIColor greenColor];
+        [_scalingButton addTarget:self action:@selector(scalingButtonDidTap:) forControlEvents:UIControlEventTouchDown];
+        _scalingButton.hidden = YES;
+        [self.view addSubview:_scalingButton];
+    }
+    return _scalingButton;
+}
+
+- (UIImageView *)speakingView {
+    if (!_speakingView) {
+        _speakingView = [[UIImageView alloc] initWithFrame:CGRectMake(0, self.bigVideoView.bounds.size.height - 20, 20, 20)];
+
+        _speakingView.layer.masksToBounds = YES;
+        _speakingView.layer.cornerRadius = 2.f;
+        _speakingView.image = [UIImage imageNamed:@"speaking"];
+        _speakingView.hidden = YES;
+        [self.bigVideoView addSubview:_speakingView];
+    }
+    return _speakingView;
+}
+
+- (void)startConnectedTimer {
+    [self stopConnectedTimer];
+    self.connectedTimer = [NSTimer scheduledTimerWithTimeInterval:1
+                                                        target:self
+                                                      selector:@selector(updateConnectedTimeLabel)
+                                                      userInfo:nil
+                                                       repeats:YES];
+    [self.connectedTimer fire];
+}
+
+- (void)stopConnectedTimer {
+    if (self.connectedTimer) {
+        [self.connectedTimer invalidate];
+        self.connectedTimer = nil;
+    }
+}
+
+- (void)setFocusUser:(NSString *)userId {
+    if (userId) {
+        [self.participants removeObject:userId];
+        [self.participants addObject:userId];
+        [self reloadVideoUI];
+    }
+}
+
+- (void)updateConnectedTimeLabel {
+    long sec = [[NSDate date] timeIntervalSince1970] - self.currentSession.connectedTime / 1000;
+    if (sec < 60 * 60) {
+        self.connectTimeLabel.text = [NSString stringWithFormat:@"%02ld:%02ld", sec / 60, sec % 60];
+    } else {
+        self.connectTimeLabel.text = [NSString stringWithFormat:@"%02ld:%02ld:%02ld", sec / 60 / 60, (sec / 60) % 60, sec % 60];
+    }
+}
+
+- (void)hanupButtonDidTap:(UIButton *)button {
+    if(self.currentSession.state != kWFAVEngineStateIdle) {
+        [self.currentSession endCall];
+    }
+}
+
+- (void)answerButtonDidTap:(UIButton *)button {
+    if (self.currentSession.state == kWFAVEngineStateIncomming) {
+        [self.currentSession answerCall:NO];
+    }
+}
+
+- (void)minimizeButtonDidTap:(UIButton *)button {
+    __block NSString *focusUser = [self.participants lastObject];
+    [WFCUFloatingWindow startCallFloatingWindow:self.currentSession focusUser:focusUser withTouchedBlock:^(WFAVCallSession *callSession) {
+        WFCUConferenceViewController *vc = [[WFCUConferenceViewController alloc] initWithSession:callSession];
+        [vc setFocusUser:focusUser];
+         [[WFAVEngineKit sharedEngineKit] presentViewController:vc];
+     }];
+    
+    [[WFAVEngineKit sharedEngineKit] dismissViewController:self];
+}
+
+- (void)addParticipantButtonDidTap:(UIButton *)button {
+    WFCUConferenceInviteViewController *pvc = [[WFCUConferenceInviteViewController alloc] init];
+    
+    WFCCConferenceInviteMessageContent *invite = [[WFCCConferenceInviteMessageContent alloc] init];
+    invite.callId = self.currentSession.callId;
+    invite.pin = self.currentSession.pin;
+    invite.audioOnly = self.currentSession.audioOnly;
+    invite.host = self.currentSession.host;
+    invite.title = self.currentSession.title;
+    invite.desc = self.currentSession.desc;
+    invite.audience = self.currentSession.audience;
+    
+    pvc.invite = invite;
+    
+    UINavigationController *navi = [[UINavigationController alloc] initWithRootViewController:pvc];
+    navi.modalPresentationStyle = UIModalPresentationFullScreen;
+
+    [self presentViewController:navi animated:YES completion:nil];
+}
+
+- (void)switchCameraButtonDidTap:(UIButton *)button {
+    if (self.currentSession.state != kWFAVEngineStateIdle) {
+        [self.currentSession switchCamera];
+    }
+}
+
+- (void)audioButtonDidTap:(UIButton *)button {
+    if (self.currentSession.state != kWFAVEngineStateIdle) {
+        [self.currentSession muteAudio:!self.currentSession.audioMuted];
+        [self updateAudioButton];
+    }
+}
+
+- (void)updateAudioButton {
+    if (self.currentSession.audioMuted) {
+        [self.audioButton setImage:[UIImage imageNamed:@"mute_hover"] forState:UIControlStateNormal];
+    } else {
+        [self.audioButton setImage:[UIImage imageNamed:@"mute"] forState:UIControlStateNormal];
+    }
+}
+- (void)speakerButtonDidTap:(UIButton *)button {
+    if (self.currentSession.state != kWFAVEngineStateIdle) {
+        [self.currentSession enableSpeaker:!self.currentSession.isSpeaker];
+        [self updateSpeakerButton];
+    }
+}
+
+- (void)updateSpeakerButton {
+    if (!self.currentSession.isSpeaker) {
+        [self.speakerButton setImage:[UIImage imageNamed:@"speaker"] forState:UIControlStateNormal];
+    } else {
+        [self.speakerButton setImage:[UIImage imageNamed:@"speaker_hover"] forState:UIControlStateNormal];
+    }
+}
+
+- (void)updateVideoButton {
+    if (self.currentSession.videoMuted) {
+        [self.videoButton setImage:[UIImage imageNamed:@"disable_video"] forState:UIControlStateNormal];
+    } else {
+        [self.videoButton setImage:[UIImage imageNamed:@"enable_video"] forState:UIControlStateNormal];
+    }
+}
+
+//1.决定当前界面是否开启自动转屏,如果返回NO,后面两个方法也不会被调用,只是会支持默认的方向
+- (BOOL)shouldAutorotate {
+      return YES;
+}
+
+//2.返回支持的旋转方向
+//iPad设备上,默认返回值UIInterfaceOrientationMaskAllButUpSideDwon
+//iPad设备上,默认返回值是UIInterfaceOrientationMaskAll
+- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
+     return UIDeviceOrientationLandscapeLeft | UIDeviceOrientationLandscapeRight | UIDeviceOrientationPortrait;
+}
+
+//3.返回进入界面默认显示方向
+- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
+     return UIInterfaceOrientationPortrait;
+}
+
+- (BOOL)onDeviceOrientationDidChange{
+    //获取当前设备Device
+    UIDevice *device = [UIDevice currentDevice] ;
+
+    switch (device.orientation) {
+        case UIDeviceOrientationFaceUp:
+            NSLog(@"屏幕幕朝上平躺");
+            break;
+
+        case UIDeviceOrientationFaceDown:
+            NSLog(@"屏幕朝下平躺");
+            break;
+
+        case UIDeviceOrientationUnknown:
+            //系统当前无法识别设备朝向,可能是倾斜
+            NSLog(@"未知方向");
+            break;
+
+        case UIDeviceOrientationLandscapeLeft:
+            self.bigVideoView.transform = CGAffineTransformMakeRotation(M_PI_2);
+            NSLog(@"屏幕向左橫置");
+            break;
+
+        case UIDeviceOrientationLandscapeRight:
+            self.bigVideoView.transform = CGAffineTransformMakeRotation(-M_PI_2);
+            NSLog(@"屏幕向右橫置");
+            break;
+
+        case UIDeviceOrientationPortrait:
+            self.bigVideoView.transform = CGAffineTransformMakeRotation(0);
+            NSLog(@"屏幕直立");
+            break;
+
+        case UIDeviceOrientationPortraitUpsideDown:
+            NSLog(@"屏幕直立,上下顛倒");
+            break;
+
+        default:
+            NSLog(@"無法识别");
+            break;
+    }
+    
+    if (!self.smallCollectionView.hidden) {
+        [self.smallCollectionView reloadData];
+    }
+    return YES;
+}
+
+
+- (void)viewWillAppear:(BOOL)animated {
+    [super viewWillAppear:animated];
+    
+    if (_currentSession.state == kWFAVEngineStateConnected) {
+        [self updateConnectedTimeLabel];
+        [self startConnectedTimer];
+    }
+}
+
+- (void)viewWillDisappear:(BOOL)animated {
+    [super viewWillDisappear:animated];
+    
+    [self stopConnectedTimer];
+}
+
+- (void)setPanStartPoint:(CGPoint)panStartPoint {
+    _panStartPoint = panStartPoint;
+    _panStartVideoFrame = self.smallCollectionView.frame;
+}
+
+- (void)moveToPanPoint:(CGPoint)panPoint {
+    CGRect frame = self.panStartVideoFrame;
+    CGSize moveSize = CGSizeMake(panPoint.x - self.panStartPoint.x, panPoint.y - self.panStartPoint.y);
+    
+    frame.origin.x += moveSize.width;
+    frame.origin.y += moveSize.height;
+    self.smallCollectionView.frame = frame;
+}
+
+- (void)onSmallVideoPan:(UIPanGestureRecognizer *)recognize {
+    switch (recognize.state) {
+        case UIGestureRecognizerStateBegan:
+            self.panStartPoint = [recognize translationInView:self.view];
+            break;
+        case UIGestureRecognizerStateChanged: {
+            CGPoint currentPoint = [recognize translationInView:self.view];
+            [self moveToPanPoint:currentPoint];
+            break;
+        }
+        case UIGestureRecognizerStateEnded: {
+            CGPoint endPoint = [recognize translationInView:self.view];
+            [self moveToPanPoint:endPoint];
+            break;
+        }
+        case UIGestureRecognizerStateCancelled:
+        case UIGestureRecognizerStateFailed:
+        default:
+            break;
+        }
+}
+
+- (void)videoButtonDidTap:(UIButton *)button {
+    if (self.currentSession.state != kWFAVEngineStateIdle) {
+        [self.currentSession muteVideo:!self.currentSession.isVideoMuted];
+        [self updateVideoButton];
+    }
+}
+
+- (void)scalingButtonDidTap:(UIButton *)button {
+//    if (self.currentSession.state != kWFAVEngineStateIdle) {
+//        if (self.scalingType < kWFAVVideoScalingTypeAspectBalanced) {
+//            self.scalingType++;
+//        } else {
+//            self.scalingType = kWFAVVideoScalingTypeAspectFit;
+//        }
+//
+////        [self.currentSession setupLocalVideoView:self.smallVideoView scalingType:self.scalingType];
+////        [self.currentSession setupRemoteVideoView:self.bigVideoView scalingType:self.scalingType forUser:self.currentSession.participants[0]];
+//    }
+}
+
+- (CGRect)getButtomCenterButtonFrame {
+    return CGRectMake(self.view.frame.size.width/2-ButtonSize/2, self.view.frame.size.height-BottomPadding-ButtonSize, ButtonSize, ButtonSize);
+}
+
+- (CGRect)getButtomLeftButtonFrame {
+    return CGRectMake(self.view.frame.size.width/4-ButtonSize/2, self.view.frame.size.height-BottomPadding-ButtonSize, ButtonSize, ButtonSize);
+}
+
+- (CGRect)getButtomRightButtonFrame {
+    return CGRectMake(self.view.frame.size.width*3/4-ButtonSize/2, self.view.frame.size.height-BottomPadding-ButtonSize, ButtonSize, ButtonSize);
+}
+
+- (CGRect)getToAudioButtonFrame {
+    return CGRectMake(self.view.frame.size.width*3/4-ButtonSize/2, self.view.frame.size.height-BottomPadding-ButtonSize-ButtonSize-2, ButtonSize, ButtonSize);
+}
+
+- (void)updateTopViewFrame {
+//    if (self.currentSession.isAudioOnly) {
+        CGFloat containerWidth = self.view.bounds.size.width;
+        
+        self.portraitView.frame = CGRectMake((containerWidth-64)/2, kStatusBarAndNavigationBarHeight, 64, 64);;
+        
+        self.userNameLabel.frame = CGRectMake((containerWidth - 240)/2, kStatusBarAndNavigationBarHeight + 64 + 8, 240, 26);
+        self.userNameLabel.textAlignment = NSTextAlignmentCenter;
+        
+        self.connectTimeLabel.frame = CGRectMake((containerWidth - 240)/2, self.smallCollectionView.frame.origin.y + self.smallCollectionView.frame.size.height + 8, 240, 16);
+        self.connectTimeLabel.textAlignment = NSTextAlignmentCenter;
+    
+        self.stateLabel.frame = CGRectMake((containerWidth - 240)/2, self.smallCollectionView.frame.origin.y + self.smallCollectionView.frame.size.height + 30, 240, 16);
+        self.stateLabel.textAlignment = NSTextAlignmentCenter;
+//    } else {
+//        self.portraitView.frame = CGRectMake(16, kStatusBarAndNavigationBarHeight, 64, 64);
+//        self.userNameLabel.frame = CGRectMake(96, kStatusBarAndNavigationBarHeight + 8, 240, 26);
+//        if(![NSThread isMainThread]) {
+//            NSLog(@"error not main thread");
+//        }
+//        self.userNameLabel.textAlignment = NSTextAlignmentLeft;
+//        if(self.currentSession.state == kWFAVEngineStateConnected) {
+//            self.stateLabel.frame = CGRectMake(54, 30, 240, 20);
+//        } else {
+//            self.stateLabel.frame = CGRectMake(96, kStatusBarAndNavigationBarHeight + 26 + 14, 240, 16);
+//        }
+//        self.stateLabel.textAlignment = NSTextAlignmentLeft;
+//    }
+}
+
+- (void)onClickedBigVideoView:(id)sender {
+    if (self.currentSession.state != kWFAVEngineStateConnected) {
+        return;
+    }
+    
+    if (self.currentSession.audioOnly) {
+        return;
+    }
+    
+    if (self.smallCollectionView.hidden) {
+        if (self.hangupButton.hidden) {
+            self.hangupButton.hidden = NO;
+            self.audioButton.hidden = NO;
+            if (self.currentSession.audioOnly) {
+                self.videoButton.hidden = YES;
+            } else {
+                self.videoButton.hidden = NO;
+            }
+            self.switchCameraButton.hidden = NO;
+            self.smallCollectionView.hidden = NO;
+            self.minimizeButton.hidden = NO;
+            self.addParticipantButton.hidden = NO;
+        } else {
+            self.hangupButton.hidden = YES;
+            self.audioButton.hidden = YES;
+            self.videoButton.hidden = YES;
+            self.switchCameraButton.hidden = YES;
+            self.minimizeButton.hidden = YES;
+            self.addParticipantButton.hidden = NO;
+        }
+    } else {
+        self.smallCollectionView.hidden = YES;
+    }
+}
+
+#pragma mark - WFAVEngineDelegate
+- (void)didChangeState:(WFAVEngineState)state {
+    if (!self.viewLoaded) {
+        return;
+    }
+    switch (state) {
+        case kWFAVEngineStateIdle:
+            self.answerButton.hidden = YES;
+            self.hangupButton.hidden = YES;
+            self.switchCameraButton.hidden = YES;
+            self.audioButton.hidden = YES;
+            self.videoButton.hidden = YES;
+            self.scalingButton.hidden = YES;
+            [self stopConnectedTimer];
+            self.userNameLabel.hidden = YES;
+            self.portraitView.hidden = YES;
+            self.stateLabel.text = WFCString(@"CallEnded");
+            self.smallCollectionView.hidden = YES;
+            self.portraitCollectionView.hidden = YES;
+            self.bigVideoView.hidden = YES;
+            self.minimizeButton.hidden = YES;
+            self.speakerButton.hidden = YES;
+            self.addParticipantButton.hidden = NO;
+            [self updateTopViewFrame];
+            break;
+        case kWFAVEngineStateOutgoing:
+            self.answerButton.hidden = YES;
+            self.connectTimeLabel.hidden = YES;
+            self.hangupButton.hidden = NO;
+            self.hangupButton.frame = [self getButtomCenterButtonFrame];
+            self.switchCameraButton.hidden = YES;
+            if (self.currentSession.isAudioOnly) {
+                self.speakerButton.hidden = YES;
+                [self updateSpeakerButton];
+                self.speakerButton.frame = [self getButtomRightButtonFrame];
+                self.audioButton.hidden = YES;
+                self.audioButton.frame = [self getButtomLeftButtonFrame];
+            } else {
+                self.speakerButton.hidden = YES;
+                self.audioButton.hidden = YES;
+            }
+            self.videoButton.hidden = YES;
+            self.scalingButton.hidden = YES;
+            [self.currentSession setupLocalVideoView:self.bigVideoView scalingType:self.bigScalingType];
+            self.stateLabel.text = WFCString(@"WaitingAccept");
+            self.smallCollectionView.hidden = YES;
+            self.portraitCollectionView.hidden = NO;
+            [self.portraitCollectionView reloadData];
+            
+            self.userNameLabel.hidden = YES;
+            self.portraitView.hidden = YES;
+            [self updateTopViewFrame];
+            self.addParticipantButton.hidden = NO;
+            
+            break;
+        case kWFAVEngineStateConnecting:
+            self.answerButton.hidden = YES;
+            self.hangupButton.hidden = NO;
+            self.speakerButton.hidden = YES;
+            self.hangupButton.frame = [self getButtomCenterButtonFrame];
+            self.switchCameraButton.hidden = YES;
+            self.audioButton.hidden = YES;
+            self.videoButton.hidden = YES;
+            self.scalingButton.hidden = YES;
+            [self.currentSession setupLocalVideoView:self.bigVideoView scalingType:self.bigScalingType];
+            if (self.currentSession.audioOnly) {
+                self.smallCollectionView.hidden = YES;
+                self.portraitCollectionView.hidden = NO;
+                [self.portraitCollectionView reloadData];
+                
+                self.portraitCollectionView.center = self.view.center;
+            } else {
+                self.smallCollectionView.hidden = NO;
+                [self.smallCollectionView reloadData];
+                self.portraitCollectionView.hidden = YES;
+            }
+            
+            
+            self.stateLabel.text = WFCString(@"CallConnecting");
+            self.portraitView.hidden = YES;
+            self.userNameLabel.hidden = YES;
+            break;
+        case kWFAVEngineStateConnected:
+            self.answerButton.hidden = YES;
+            self.hangupButton.hidden = NO;
+            self.connectTimeLabel.hidden = NO;
+            self.stateLabel.hidden = YES;
+            self.hangupButton.frame = [self getButtomCenterButtonFrame];
+            if (self.currentSession.isAudioOnly) {
+                self.speakerButton.hidden = NO;
+                self.speakerButton.frame = [self getButtomRightButtonFrame];
+                [self updateSpeakerButton];
+                self.audioButton.hidden = NO;
+                self.audioButton.frame = [self getButtomLeftButtonFrame];
+                self.switchCameraButton.hidden = YES;
+                self.videoButton.hidden = YES;
+            } else {
+                self.speakerButton.hidden = YES;
+                [self.currentSession enableSpeaker:YES];
+                self.audioButton.hidden = NO;
+                self.audioButton.frame = [self getButtomLeftButtonFrame];
+                self.switchCameraButton.hidden = NO;
+                self.switchCameraButton.frame = [self getButtomRightButtonFrame];
+                self.videoButton.hidden = NO;
+            }
+            
+            self.scalingButton.hidden = YES;
+            self.minimizeButton.hidden = NO;
+            self.addParticipantButton.hidden = NO;
+            
+            if (self.currentSession.isAudioOnly) {
+                [self.currentSession setupLocalVideoView:nil scalingType:self.bigScalingType];
+                self.smallCollectionView.hidden = YES;
+                self.bigVideoView.hidden = YES;
+                
+                self.portraitCollectionView.hidden = NO;
+                [self.portraitCollectionView reloadData];
+            } else {
+                NSString *lastUser = [self.participants lastObject];
+                if ([lastUser isEqualToString:[WFCCNetworkService sharedInstance].userId]) {
+                    [self.currentSession setupLocalVideoView:self.bigVideoView scalingType:self.bigScalingType];
+                } else {
+                    [self.currentSession setupRemoteVideoView:self.bigVideoView scalingType:self.bigScalingType forUser:lastUser];
+                }
+                
+                self.smallCollectionView.hidden = NO;
+                [self.smallCollectionView reloadData];
+                self.bigVideoView.hidden = NO;
+                
+                self.portraitCollectionView.hidden = YES;
+            }
+            
+            
+//            if (!_currentSession.isAudioOnly) {
+                self.userNameLabel.hidden = YES;
+                self.portraitView.hidden = YES;
+//            } else {
+//                self.userNameLabel.hidden = NO;
+//                self.portraitView.hidden = NO;
+//            }
+            [self updateConnectedTimeLabel];
+            [self startConnectedTimer];
+            [self updateTopViewFrame];
+            break;
+        case kWFAVEngineStateIncomming:
+            self.connectTimeLabel.hidden = YES;
+            self.answerButton.hidden = NO;
+            self.answerButton.frame = [self getButtomRightButtonFrame];
+            self.hangupButton.hidden = NO;
+            self.hangupButton.frame = [self getButtomLeftButtonFrame];
+            self.switchCameraButton.hidden = YES;
+            self.audioButton.hidden = YES;
+            self.videoButton.hidden = YES;
+            self.scalingButton.hidden = YES;
+            
+            [self.currentSession setupLocalVideoView:self.bigVideoView scalingType:self.bigScalingType];
+            self.stateLabel.text = WFCString(@"InvitingYou");
+            self.smallCollectionView.hidden = YES;
+            self.portraitCollectionView.hidden = NO;
+            [self.portraitCollectionView reloadData];
+            break;
+        default:
+            break;
+    }
+}
+
+- (void)didCreateLocalVideoTrack:(RTCVideoTrack *)localVideoTrack {
+}
+
+- (void)didReceiveRemoteVideoTrack:(RTCVideoTrack *)remoteVideoTrack fromUser:(NSString *)userId {
+}
+
+- (void)didVideoMuted:(BOOL)videoMuted fromUser:(NSString *)userId {
+    if ([self.participants.lastObject isEqualToString:userId]) {
+        for (int i = 0; i < self.participants.count-1; i++) {
+            NSString *pid = [self.participants objectAtIndex:i];
+            if ([pid isEqualToString:[WFCCNetworkService sharedInstance].userId]) {
+                if (!self.currentSession.myProfile.videoMuted) {
+                    [self switchVideoView:i];
+                    return;
+                }
+                continue;
+            }
+            for (WFAVParticipantProfile *p in self.currentSession.participants) {
+                if ([p.userId isEqualToString:pid]) {
+                    if (!p.videoMuted && p.state == kWFAVEngineStateConnected) {
+                        [self switchVideoView:i];
+                        return;
+                    }
+                    break;
+                }
+            }
+        }
+        [self reloadVideoUI];
+    } else {
+        [self reloadVideoUI];
+    }
+}
+- (void)didReportAudioVolume:(NSInteger)volume ofUser:(NSString *)userId {
+    NSLog(@"user %@ report volume %ld", userId, volume);
+    [[NSNotificationCenter defaultCenter] postNotificationName:@"wfavVolumeUpdated" object:userId userInfo:@{@"volume":@(volume)}];
+    if (!self.currentSession.audioOnly && [userId isEqualToString:self.participants.lastObject]) {
+        if (volume > 1000) {
+            [self.bigVideoView bringSubviewToFront:self.speakingView];
+            self.speakingView.hidden = NO;
+        } else {
+            self.speakingView.hidden = YES;
+        }
+    }
+}
+- (void)didCallEndWithReason:(WFAVCallEndReason)reason {
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+            [[WFAVEngineKit sharedEngineKit] dismissViewController:self];
+    });
+}
+
+- (void)didParticipantJoined:(NSString *)userId {
+    if ([self.participants containsObject:userId] || [userId isEqualToString:[WFCCNetworkService sharedInstance].userId]) {
+        return;
+    }
+    [self.participants insertObject:userId atIndex:0];
+    [self reloadVideoUI];
+}
+
+- (void)didParticipantConnected:(NSString *)userId {
+    [self reloadVideoUI];
+}
+
+- (void)didParticipantLeft:(NSString *)userId withReason:(WFAVCallEndReason)reason {
+    [self.participants removeObject:userId];
+    [self reloadVideoUI];
+    
+    
+    WFCCUserInfo *userInfo = [[WFCCIMService sharedWFCIMService] getUserInfo:userId inGroup:self.currentSession.conversation.type == Group_Type ? self.currentSession.conversation.target : nil refresh:NO];
+    
+    NSString *reasonStr;
+    if (reason == kWFAVCallEndReasonTimeout) {
+        reasonStr = @"未接听";
+    } else if(reason == kWFAVCallEndReasonBusy) {
+        reasonStr = @"网络忙";
+    } else if(reason == kWFAVCallEndReasonRemoteHangup) {
+        reasonStr = @"离开会议";
+    } else {
+        reasonStr = @"离开会议"; //"网络错误";
+    }
+    
+    [self.view makeToast:[NSString stringWithFormat:@"%@ %@", userInfo.displayName, reasonStr] duration:1 position:CSToastPositionCenter];
+}
+
+- (void)didChangeMode:(BOOL)isAudioOnly {
+    [self didChangeState:self.currentSession.state];
+}
+
+- (void)didError:(NSError *)error {
+    
+}
+
+- (void)didGetStats:(NSArray *)stats {
+    
+}
+
+- (void)checkAVPermission {
+    [self checkCapturePermission:nil];
+    [self checkRecordPermission:nil];
+}
+
+- (void)checkCapturePermission:(void (^)(BOOL granted))complete {
+    AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
+    if (authStatus == AVAuthorizationStatusDenied || authStatus == AVAuthorizationStatusRestricted) {
+        if (complete) {
+            complete(NO);
+        }
+    } else if (authStatus == AVAuthorizationStatusNotDetermined) {
+        [AVCaptureDevice
+         requestAccessForMediaType:AVMediaTypeVideo
+         completionHandler:^(BOOL granted) {
+             if (complete) {
+                 complete(granted);
+             }
+         }];
+    } else {
+        if (complete) {
+            complete(YES);
+        }
+    }
+}
+
+- (void)checkRecordPermission:(void (^)(BOOL granted))complete {
+    if ([[AVAudioSession sharedInstance] respondsToSelector:@selector(requestRecordPermission:)]) {
+        [[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) {
+            if (complete) {
+                complete(granted);
+            }
+        }];
+    }
+}
+- (void)reloadVideoUI {
+    if (!self.currentSession.audioOnly) {
+        if (self.currentSession.state == kWFAVEngineStateConnecting || self.currentSession.state == kWFAVEngineStateConnected) {
+            UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
+            CGFloat itemWidth = (self.view.frame.size.width + layout.minimumLineSpacing)/3 - layout.minimumLineSpacing;
+            
+            int lines = (int)([self.currentSession participantIds].count + 2) /3;
+            
+            self.smallCollectionView.frame = CGRectMake(0, kStatusBarAndNavigationBarHeight, self.view.frame.size.width, (itemWidth + layout.minimumLineSpacing)*lines - layout.minimumLineSpacing);
+            
+            _speakingView.hidden = YES;
+            NSString *userId = [self.participants lastObject];
+            if ([userId isEqualToString:[WFCCNetworkService sharedInstance].userId]) {
+                if (self.currentSession.myProfile.videoMuted) {
+                    [self.currentSession setupLocalVideoView:nil scalingType:self.bigScalingType];
+                    self.stateLabel.text = WFCString(@"VideoClosed");
+                    self.stateLabel.hidden = NO;
+                } else {
+                    [self.currentSession setupLocalVideoView:self.bigVideoView scalingType:self.bigScalingType];
+                    self.stateLabel.text = nil;
+                    self.stateLabel.hidden = YES;
+                }
+            } else {
+                for (WFAVParticipantProfile *profile in self.currentSession.participants) {
+                    if ([profile.userId isEqualToString:userId]) {
+                        if (profile.videoMuted) {
+                            [self.currentSession setupRemoteVideoView:nil scalingType:self.bigScalingType forUser:userId];
+                            self.stateLabel.text = WFCString(@"VideoClosed");
+                            self.stateLabel.hidden = NO;
+                        } else {
+                            [self.currentSession setupRemoteVideoView:self.bigVideoView scalingType:self.bigScalingType forUser:userId];
+                            self.stateLabel.text = nil;
+                            self.stateLabel.hidden = YES;
+                        }
+                        break;
+                    }
+                }
+                
+            }
+            [self.smallCollectionView reloadData];
+        } else {
+            [self.portraitCollectionView reloadData];
+        }
+    } else {
+        [self.portraitCollectionView reloadData];
+    }
+}
+
+- (BOOL)switchVideoView:(NSUInteger)index {
+    NSString *userId = self.participants[index];
+    
+    BOOL canSwitch = NO;
+    for (WFAVParticipantProfile *profile in self.currentSession.participants) {
+        if ([profile.userId isEqualToString:userId]) {
+            if (profile.state == kWFAVEngineStateConnected) {
+                canSwitch = YES;
+            }
+            break;
+        }
+    }
+    
+    if ([userId isEqualToString:[WFCCNetworkService sharedInstance].userId]) {
+        if (self.currentSession.state == kWFAVEngineStateConnected) {
+            canSwitch = YES;
+        }
+    }
+    
+    if (canSwitch) {
+        NSString *lastId = [self.participants lastObject];
+        [self.participants removeLastObject];
+        [self.participants insertObject:lastId atIndex:index];
+        [self.participants removeObject:userId];
+        [self.participants addObject:userId];
+    }
+    [self reloadVideoUI];
+    
+    return canSwitch;
+}
+
+#pragma mark - UICollectionViewDataSource
+- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
+    if (collectionView == self.portraitCollectionView) {
+        if (self.currentSession.audioOnly && (self.currentSession.state == kWFAVEngineStateConnecting || self.currentSession.state == kWFAVEngineStateConnected)) {
+            return self.participants.count;
+        }
+    }
+    return self.participants.count - 1;
+}
+
+// The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath:
+- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
+    NSString *userId = self.participants[indexPath.row];
+    if (collectionView == self.smallCollectionView) {
+        WFCUParticipantCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
+
+        WFCCUserInfo *userInfo = [[WFCCIMService sharedWFCIMService] getUserInfo:userId inGroup:self.currentSession.conversation.type == Group_Type ? self.currentSession.conversation.target : nil refresh:NO];
+        
+        
+        UIDevice *device = [UIDevice currentDevice] ;
+        if (device.orientation == UIDeviceOrientationLandscapeLeft) {
+            cell.transform = CGAffineTransformMakeRotation(M_PI_2);
+        } else if (device.orientation == UIDeviceOrientationLandscapeRight) {
+            cell.transform = CGAffineTransformMakeRotation(-M_PI_2);
+        } else {
+            cell.transform = CGAffineTransformMakeRotation(0);
+        }
+        
+        
+        if ([userId isEqualToString:[WFCCNetworkService sharedInstance].userId]) {
+            WFAVParticipantProfile *profile = self.currentSession.myProfile;
+            [cell setUserInfo:userInfo callProfile:profile];
+            if (profile.videoMuted) {
+                [self.currentSession setupLocalVideoView:nil scalingType:self.smallScalingType];
+            } else {
+                [self.currentSession setupLocalVideoView:cell scalingType:self.smallScalingType];
+            }
+        } else {
+            for (WFAVParticipantProfile *profile in self.currentSession.participants) {
+                if ([profile.userId isEqualToString:userId]) {
+                    [cell setUserInfo:userInfo callProfile:profile];
+                    if (profile.videoMuted) {
+                        [self.currentSession setupRemoteVideoView:nil scalingType:self.smallScalingType forUser:userId];
+                    } else {
+                        [self.currentSession setupRemoteVideoView:cell scalingType:self.smallScalingType forUser:userId];
+                    }
+                    break;
+                }
+            }
+        }
+
+        return cell;
+    } else {
+        WFCUPortraitCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell2" forIndexPath:indexPath];
+        
+        cell.itemSize = PortraitItemSize;
+        cell.labelSize = PortraitLabelSize;
+        
+        WFCCUserInfo *userInfo = [[WFCCIMService sharedWFCIMService] getUserInfo:userId inGroup:self.currentSession.conversation.type == Group_Type ? self.currentSession.conversation.target : nil refresh:NO];
+        cell.userInfo = userInfo;
+        
+        if ([userId isEqualToString:[WFCCNetworkService sharedInstance].userId]) {
+            cell.profile = self.currentSession.myProfile;
+        } else {
+            for (WFAVParticipantProfile *profile in self.currentSession.participants) {
+                if ([profile.userId isEqualToString:userId]) {
+                    cell.profile = profile;
+                    break;
+                }
+            }
+        }
+        
+        return cell;
+    }
+    
+}
+
+#pragma mark - UICollectionViewDelegate
+- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
+    if (collectionView == self.smallCollectionView) {
+        [self switchVideoView:indexPath.row];
+    }
+}
+
+#endif
+@end

+ 17 - 0
wfuikit/WFChatUIKit/Voip/Conference/WFCUCreateConferenceViewController.h

@@ -0,0 +1,17 @@
+//
+//  WFCUCreateConferenceViewController.h
+//  WFChatUIKit
+//
+//  Created by Tom Lee on 2020/6/17.
+//  Copyright © 2020 Tom Lee. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface WFCUCreateConferenceViewController : UIViewController
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 73 - 0
wfuikit/WFChatUIKit/Voip/Conference/WFCUCreateConferenceViewController.m

@@ -0,0 +1,73 @@
+//
+//  WFCUCreateConferenceViewController.m
+//  WFChatUIKit
+//
+//  Created by Tom Lee on 2020/6/17.
+//  Copyright © 2020 Tom Lee. All rights reserved.
+//
+
+#import "WFCUCreateConferenceViewController.h"
+#import <WebRTC/WebRTC.h>
+#import <WFAVEngineKit/WFAVEngineKit.h>
+#import "WFCUConferenceViewController.h"
+
+@interface WFCUCreateConferenceViewController ()
+@property(nonatomic, strong)UITextField *conferenceTitle;
+@property(nonatomic, strong)UITextField *conferenceDesc;
+@property(nonatomic, strong)UISwitch *audioOnlySwitch;
+@property(nonatomic, strong)UISwitch *audienceSwitch;
+@property(nonatomic, strong)UIButton *startBtn;
+@end
+
+@implementation WFCUCreateConferenceViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    self.view.backgroundColor = [UIColor whiteColor];
+    
+    WFCCUserInfo *userInfo = [[WFCCIMService sharedWFCIMService] getUserInfo:[WFCCNetworkService sharedInstance].userId refresh:NO];
+    
+    
+    CGRect bounds = self.view.bounds;
+    
+    CGFloat padding = 40;
+    CGFloat h = kStatusBarAndNavigationBarHeight+padding;
+    
+    self.conferenceTitle = [[UITextField alloc] initWithFrame:CGRectMake(16, h, bounds.size.width - 32, 32)];
+    self.conferenceTitle.placeholder = @"请输入会议标题";
+    self.conferenceTitle.text = [NSString stringWithFormat:@"%@的会议", userInfo.displayName];
+    [self.view addSubview:self.conferenceTitle];
+    
+    h+=padding;
+    self.conferenceDesc = [[UITextField alloc] initWithFrame:CGRectMake(16, h, bounds.size.width - 32, 32)];
+    self.conferenceDesc.placeholder = @"请输入会议描述";
+    [self.view addSubview:self.conferenceDesc];
+    
+    h+=padding;
+    UILabel *audioLable = [[UILabel alloc] initWithFrame:CGRectMake(bounds.size.width - 140 - 16, h, 80, 24)];
+    audioLable.text = @"开启视频";
+    [self.view addSubview:audioLable];
+    self.audioOnlySwitch = [[UISwitch alloc] initWithFrame:CGRectMake(bounds.size.width - 60 - 16, h, 60, 24)];
+    [self.view addSubview:self.audioOnlySwitch];
+    
+    h+=padding;
+    UILabel *audienceLable = [[UILabel alloc] initWithFrame:CGRectMake(bounds.size.width - 140 - 16, h, 80, 24)];
+    audienceLable.text = @"观众模式";
+    [self.view addSubview:audienceLable];
+    self.audienceSwitch = [[UISwitch alloc] initWithFrame:CGRectMake(bounds.size.width - 60 - 16, h, 60, 24)];
+    [self.view addSubview:self.audienceSwitch];
+    
+    self.startBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 120, 40)];
+    self.startBtn.center = self.view.center;
+    [self.startBtn setTitle:@"开始" forState:UIControlStateNormal];
+    [self.startBtn setBackgroundColor:[UIColor greenColor]];
+    [self.startBtn addTarget:self action:@selector(onStart:) forControlEvents:UIControlEventTouchDown];
+    [self.view addSubview:self.startBtn];
+    
+}
+
+- (void)onStart:(id)sender {
+    WFCUConferenceViewController *vc = [[WFCUConferenceViewController alloc] initWithCallId:nil audioOnly:self.audienceSwitch.on pin:nil host:[WFCCNetworkService sharedInstance].userId title:self.conferenceTitle.text desc:self.conferenceDesc.text audience:self.audienceSwitch.on moCall:YES];
+    [[WFAVEngineKit sharedEngineKit] presentViewController:vc];
+}
+@end

+ 2 - 1
wfuikit/WFChatUIKit/Voip/WFCUMultiVideoViewController.m

@@ -146,7 +146,8 @@
     CGFloat itemWidth = (self.view.frame.size.width + layout.minimumLineSpacing)/3 - layout.minimumLineSpacing;
     layout.itemSize = CGSizeMake(itemWidth, itemWidth);
     layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
-    self.smallCollectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, kStatusBarAndNavigationBarHeight, self.view.frame.size.width, itemWidth) collectionViewLayout:layout];
+    int lines = ([self.currentSession participantIds].count + 2) /3;
+    self.smallCollectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, kStatusBarAndNavigationBarHeight, self.view.frame.size.width, itemWidth*lines) collectionViewLayout:layout];
     
     self.smallCollectionView.dataSource = self;
     self.smallCollectionView.delegate = self;

+ 1 - 0
wfuikit/WFChatUIKit/WFChatUIKit.h

@@ -22,6 +22,7 @@ FOUNDATION_EXPORT const unsigned char WFChatUIKitVersionString[];
 #import <WFChatUIKit/WFCUMessageListViewController.h>
 #import <WFChatUIKit/WFCUVideoViewController.h>
 #import <WFChatUIKit/WFCUMultiVideoViewController.h>
+#import <WFChatUIKit/WFCUCreateConferenceViewController.h>
 #import <WFChatUIKit/WFCUMyPortraitViewController.h>
 #import <WFChatUIKit/WFCUMessageNotificationViewController.h>
 #import <WFChatUIKit/WFCUMyProfileTableViewController.h>