|
|
@@ -0,0 +1,182 @@
|
|
|
+#import "ZoomMeetingSDKBridge.h"
|
|
|
+
|
|
|
+#if __has_include(<ZoomSDK/ZoomSDK.h>)
|
|
|
+#import <ZoomSDK/ZoomSDK.h>
|
|
|
+#define ZOOM_MEETING_SDK 1
|
|
|
+#else
|
|
|
+#define ZOOM_MEETING_SDK 0
|
|
|
+#endif
|
|
|
+
|
|
|
+NSNotificationName const ZoomMeetingSDKNativeSessionEndedNotification = @"ZoomMeetingSDK.NativeSessionEnded";
|
|
|
+
|
|
|
+@interface ZoomMeetingSDKBridge ()
|
|
|
+#if ZOOM_MEETING_SDK
|
|
|
+<ZoomSDKAuthDelegate, ZoomSDKMeetingServiceDelegate>
|
|
|
+#endif
|
|
|
+@property (nonatomic, copy, nullable) void (^authJoinCompletion)(BOOL success, NSString * _Nullable message);
|
|
|
+@property (nonatomic, assign) BOOL wasInMeeting;
|
|
|
+@property (nonatomic, assign) BOOL sdkInitialized;
|
|
|
+@property (nonatomic, copy, nullable) NSString *pendingJWT;
|
|
|
+@property (nonatomic, assign) long long pendingMeetingNumber;
|
|
|
+@property (nonatomic, copy, nullable) NSString *pendingPassword;
|
|
|
+@property (nonatomic, copy, nullable) NSString *pendingDisplayName;
|
|
|
+@property (nonatomic, copy, nullable) NSString *pendingZAK;
|
|
|
+@end
|
|
|
+
|
|
|
+@implementation ZoomMeetingSDKBridge
|
|
|
+
|
|
|
++ (instancetype)shared {
|
|
|
+ static ZoomMeetingSDKBridge *instance;
|
|
|
+ static dispatch_once_t onceToken;
|
|
|
+ dispatch_once(&onceToken, ^{
|
|
|
+ instance = [ZoomMeetingSDKBridge new];
|
|
|
+ });
|
|
|
+ return instance;
|
|
|
+}
|
|
|
+
|
|
|
++ (BOOL)isCompiledWithNativeMeetingSDK {
|
|
|
+#if ZOOM_MEETING_SDK
|
|
|
+ return YES;
|
|
|
+#else
|
|
|
+ return NO;
|
|
|
+#endif
|
|
|
+}
|
|
|
+
|
|
|
+- (void)initSDKIfNeeded {
|
|
|
+#if ZOOM_MEETING_SDK
|
|
|
+ if (self.sdkInitialized) { return; }
|
|
|
+ ZoomSDKInitParams *params = [[ZoomSDKInitParams alloc] init];
|
|
|
+ params.zoomDomain = @"https://zoom.us";
|
|
|
+ params.needCustomizedUI = NO;
|
|
|
+ params.enableLog = YES;
|
|
|
+ ZoomSDKError initError = [[ZoomSDK sharedSDK] initSDKWithParams:params];
|
|
|
+ if (initError != ZoomSDKError_Success) {
|
|
|
+ NSLog(@"ZoomMeetingSDKBridge: initSDKWithParams failed (%d)", (int)initError);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ [[ZoomSDK sharedSDK] getMeetingService].delegate = self;
|
|
|
+ [[ZoomSDK sharedSDK] getAuthService].delegate = self;
|
|
|
+ self.sdkInitialized = YES;
|
|
|
+#endif
|
|
|
+}
|
|
|
+
|
|
|
+- (void)authenticateWithJWT:(NSString *)jwt
|
|
|
+ meetingNumber:(long long)meetingNumber
|
|
|
+ password:(NSString *)password
|
|
|
+ displayName:(NSString *)displayName
|
|
|
+ zak:(NSString *)zak
|
|
|
+ completion:(void (^)(BOOL, NSString * _Nullable))completion {
|
|
|
+#if !ZOOM_MEETING_SDK
|
|
|
+ if (completion) {
|
|
|
+ completion(NO, @"Zoom Meeting SDK is not linked. Add Vendor/ZoomSDK from the Zoom Marketplace and set ZOOM_SDK_LDFLAGS in Config/Base.xcconfig. See Vendor/ZoomSDK/README.md.");
|
|
|
+ }
|
|
|
+ return;
|
|
|
+#else
|
|
|
+ NSAssert([NSThread isMainThread], @"ZoomMeetingSDKBridge must be used on the main thread");
|
|
|
+ [self initSDKIfNeeded];
|
|
|
+ if (self.sdkInitialized == NO) {
|
|
|
+ if (completion) { completion(NO, @"Zoom SDK failed to initialize."); }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ self.authJoinCompletion = completion;
|
|
|
+ self.pendingJWT = jwt;
|
|
|
+ self.pendingMeetingNumber = meetingNumber;
|
|
|
+ self.pendingPassword = (password != nil && password.length > 0) ? password : @"";
|
|
|
+ self.pendingDisplayName = displayName.length ? displayName : @"Guest";
|
|
|
+ self.pendingZAK = zak;
|
|
|
+
|
|
|
+ ZoomSDKAuthContext *ctx = [[ZoomSDKAuthContext alloc] init];
|
|
|
+ ctx.jwtToken = jwt;
|
|
|
+
|
|
|
+ ZoomSDKError authErr = [[[ZoomSDK sharedSDK] getAuthService] sdkAuth:ctx];
|
|
|
+ if (authErr != ZoomSDKError_Success) {
|
|
|
+ [self finishAuthJoin:NO message:[NSString stringWithFormat:@"sdkAuth returned %d", (int)authErr]];
|
|
|
+ }
|
|
|
+#endif
|
|
|
+}
|
|
|
+
|
|
|
+#if ZOOM_MEETING_SDK
|
|
|
+
|
|
|
+- (void)finishAuthJoin:(BOOL)ok message:(NSString *)message {
|
|
|
+ void (^block)(BOOL, NSString *) = self.authJoinCompletion;
|
|
|
+ self.authJoinCompletion = nil;
|
|
|
+ self.pendingJWT = nil;
|
|
|
+ self.pendingPassword = nil;
|
|
|
+ self.pendingDisplayName = nil;
|
|
|
+ self.pendingZAK = nil;
|
|
|
+ if (block) {
|
|
|
+ block(ok, ok ? nil : message);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)onZoomSDKAuthReturn:(ZoomSDKAuthError)returnValue {
|
|
|
+ if (returnValue != ZoomSDKAuthError_Success) {
|
|
|
+ [self finishAuthJoin:NO message:[NSString stringWithFormat:@"Zoom SDK auth failed (code %ld). Ensure Meeting SDK is enabled for this OAuth client and the JWT is valid.", (long)returnValue]];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ ZoomSDKJoinMeetingElements *params = [[ZoomSDKJoinMeetingElements alloc] init];
|
|
|
+ params.meetingNumber = self.pendingMeetingNumber;
|
|
|
+ params.password = self.pendingPassword ?: @"";
|
|
|
+ params.displayName = self.pendingDisplayName ?: @"Guest";
|
|
|
+ if (self.pendingZAK.length) {
|
|
|
+ params.zak = self.pendingZAK;
|
|
|
+ }
|
|
|
+
|
|
|
+ ZoomSDKMeetingService *meeting = [[ZoomSDK sharedSDK] getMeetingService];
|
|
|
+ ZoomSDKError joinErr = [meeting joinMeeting:params];
|
|
|
+ if (joinErr != ZoomSDKError_Success) {
|
|
|
+ [self finishAuthJoin:NO message:[NSString stringWithFormat:@"joinMeeting failed (code %d)", (int)joinErr]];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ [self finishAuthJoin:YES message:nil];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)onZoomAuthIdentityExpired {
|
|
|
+ [self finishAuthJoin:NO message:@"Zoom SDK JWT or identity expired. Try joining again."];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)onMeetingStatusChange:(ZoomSDKMeetingStatus)state
|
|
|
+ meetingError:(ZoomSDKMeetingError)error
|
|
|
+ EndReason:(EndMeetingReason)reason {
|
|
|
+ // Treat any in-join / in-meeting / disconnecting state as "session active" so we still fire
|
|
|
+ // NativeSessionEnded when the user leaves from the waiting room, host never starts, etc.
|
|
|
+ // Recording starts right after joinMeeting returns; callbacks may still be Connecting / InWaitingRoom.
|
|
|
+ switch (state) {
|
|
|
+ case ZoomSDKMeetingStatus_Connecting:
|
|
|
+ case ZoomSDKMeetingStatus_WaitingForHost:
|
|
|
+ case ZoomSDKMeetingStatus_InMeeting:
|
|
|
+ case ZoomSDKMeetingStatus_Reconnecting:
|
|
|
+ case ZoomSDKMeetingStatus_AudioReady:
|
|
|
+ case ZoomSDKMeetingStatus_InWaitingRoom:
|
|
|
+ case ZoomSDKMeetingStatus_OtherMeetingInProgress:
|
|
|
+ case ZoomSDKMeetingStatus_Webinar_Promote:
|
|
|
+ case ZoomSDKMeetingStatus_Webinar_Depromote:
|
|
|
+ case ZoomSDKMeetingStatus_Join_Breakout_Room:
|
|
|
+ case ZoomSDKMeetingStatus_Leave_Breakout_Room:
|
|
|
+ case ZoomSDKMeetingStatus_Disconnecting:
|
|
|
+ self.wasInMeeting = YES;
|
|
|
+ break;
|
|
|
+ case ZoomSDKMeetingStatus_Ended:
|
|
|
+ case ZoomSDKMeetingStatus_Failed:
|
|
|
+ if (self.wasInMeeting) {
|
|
|
+ self.wasInMeeting = NO;
|
|
|
+ [[NSNotificationCenter defaultCenter] postNotificationName:ZoomMeetingSDKNativeSessionEndedNotification object:nil];
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case ZoomSDKMeetingStatus_Idle:
|
|
|
+ if (self.wasInMeeting) {
|
|
|
+ self.wasInMeeting = NO;
|
|
|
+ [[NSNotificationCenter defaultCenter] postNotificationName:ZoomMeetingSDKNativeSessionEndedNotification object:nil];
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#endif
|
|
|
+
|
|
|
+@end
|