#import "RNStringeeCall.h" #import "RNStringeeInstanceManager.h" #import static NSString *didChangeSignalingState = @"didChangeSignalingState"; static NSString *didChangeMediaState = @"didChangeMediaState"; static NSString *didReceiveLocalStream = @"didReceiveLocalStream"; static NSString *didReceiveRemoteStream = @"didReceiveRemoteStream"; static NSString *didReceiveDtmfDigit = @"didReceiveDtmfDigit"; static NSString *didReceiveCallInfo = @"didReceiveCallInfo"; static NSString *didHandleOnAnotherDevice = @"didHandleOnAnotherDevice"; @implementation RNStringeeCall { // Events are supported NSMutableArray *jsEvents; } @synthesize bridge = _bridge; RCT_EXPORT_MODULE(); - (instancetype)init { self = [super init]; if (self) { [RNStringeeInstanceManager instance].rnCall = self; jsEvents = [[NSMutableArray alloc] init]; } return self; } - (NSArray *)supportedEvents { return @[didChangeSignalingState, didChangeMediaState, didReceiveLocalStream, didReceiveRemoteStream, didReceiveDtmfDigit, didReceiveCallInfo, didHandleOnAnotherDevice ]; } + (BOOL)requiresMainQueueSetup { return YES; } // TODO: - Publish Functions RCT_EXPORT_METHOD(setNativeEvent:(NSString *)event) { [jsEvents addObject:event]; } RCT_EXPORT_METHOD(removeNativeEvent:(NSString *)event) { int index = -1; index = (int)[jsEvents indexOfObject:event]; if (index >= 0) { [jsEvents removeObjectAtIndex:index]; } } RCT_EXPORT_METHOD(makeCall:(NSString *)uuid parameters:(NSString *)parameters callback:(RCTResponseSenderBlock)callback) { NSError *jsonError; NSData *objectData = [parameters dataUsingEncoding:NSUTF8StringEncoding]; NSDictionary *data = [NSJSONSerialization JSONObjectWithData:objectData options:NSJSONReadingMutableContainers error:&jsonError]; if (jsonError) { callback(@[@(NO), @(-4), @"The parameters format is invalid.", [NSNull null], [NSNull null]]); } else { NSString *from = data[@"from"]; NSString *to = data[@"to"]; NSNumber *isVideoCall = data[@"isVideoCall"]; NSString *customData = data[@"customData"]; NSString *videoResolution = data[@"videoResolution"]; RNClientWrapper *wrapper = [RNStringeeInstanceManager.instance.clientWrappers objectForKey:uuid]; if (wrapper == nil) { callback(@[@(NO), @(-1), @"Wrapper is not found", [NSNull null], [NSNull null]]); return; } if (!wrapper.client) { callback(@[@(NO), @(-1), @"StringeeClient is not initialized", [NSNull null], [NSNull null]]); return; } StringeeCall *outgoingCall = [[StringeeCall alloc] initWithStringeeClient:wrapper.client from:from to:to]; outgoingCall.delegate = self; outgoingCall.isVideoCall = [isVideoCall boolValue]; if (customData.length) { outgoingCall.customData = customData; } if ([videoResolution isEqualToString:@"NORMAL"]) { outgoingCall.videoResolution = VideoResolution_Normal; } else if ([videoResolution isEqualToString:@"HD"]) { outgoingCall.videoResolution = VideoResolution_HD; } __weak StringeeCall *weakCall = outgoingCall; __weak NSMutableDictionary *weakCalls = [RNStringeeInstanceManager instance].calls; [outgoingCall makeCallWithCompletionHandler:^(BOOL status, int code, NSString *message, NSString *data) { StringeeCall *strongCall = weakCall; NSMutableDictionary *strongCalls = weakCalls; if (status) { [strongCalls setObject:strongCall forKey:strongCall.callId]; } id returnCallId = strongCall.callId ? strongCall.callId : [NSNull null]; id returnData = data ? data : [NSNull null]; callback(@[@(status), @(code), message, returnCallId, returnData]); }]; } } RCT_EXPORT_METHOD(initAnswer:(NSString *)uuid callId:(NSString *)callId callback:(RCTResponseSenderBlock)callback) { RNClientWrapper *wrapper = [RNStringeeInstanceManager.instance.clientWrappers objectForKey:uuid]; if (wrapper == nil) { callback(@[@(NO), @(-1), @"Wrapper is not found", [NSNull null], [NSNull null]]); return; } if (wrapper.client && wrapper.client.hasConnected) { if (callId.length) { StringeeCall *call = [[RNStringeeInstanceManager instance].calls objectForKey:callId]; if (call) { call.delegate = self; [call initAnswerCall]; callback(@[@(YES), @(0), @"Init answer call successfully."]); } else { callback(@[@(NO), @(-3), @"Init answer call failed. The call is not found."]); } } else { callback(@[@(NO), @(-2), @"Init answer call failed. The callId is invalid."]); } } else { callback(@[@(NO), @(-1), @"StringeeClient is not initialzied or connected."]); } } RCT_EXPORT_METHOD(answer:(NSString *)callId callback:(RCTResponseSenderBlock)callback) { if (callId.length) { StringeeCall *call = [[RNStringeeInstanceManager instance].calls objectForKey:callId]; if (call) { [call answerCallWithCompletionHandler:^(BOOL status, int code, NSString *message) { callback(@[@(status), @(code), message]); }]; } else { callback(@[@(NO), @(-3), @"Answer call failed. The call is not found."]); } } else { callback(@[@(NO), @(-2), @"Answer call failed. The callId is invalid."]); } } RCT_EXPORT_METHOD(hangup:(NSString *)callId callback:(RCTResponseSenderBlock)callback) { if (callId.length) { StringeeCall *call = [[RNStringeeInstanceManager instance].calls objectForKey:callId]; if (call) { [call hangupWithCompletionHandler:^(BOOL status, int code, NSString *message) { callback(@[@(status), @(code), message]); }]; } else { callback(@[@(NO), @(-3), @"Hangup call failed. The call is not found."]); } } else { callback(@[@(NO), @(-2), @"Hangup call failed. The callId is invalid."]); } } RCT_EXPORT_METHOD(reject:(NSString *)callId callback:(RCTResponseSenderBlock)callback) { if (callId.length) { StringeeCall *call = [[RNStringeeInstanceManager instance].calls objectForKey:callId]; if (call) { [call rejectWithCompletionHandler:^(BOOL status, int code, NSString *message) { callback(@[@(status), @(code), message]); }]; } else { callback(@[@(NO), @(-3), @"Reject call failed. The call is not found."]); } } else { callback(@[@(NO), @(-2), @"Reject call failed. The callId is invalid."]); } } RCT_EXPORT_METHOD(sendDTMF:(NSString *)callId dtmf:(NSString *)dtmf callback:(RCTResponseSenderBlock)callback) { if (callId.length) { StringeeCall *call = [[RNStringeeInstanceManager instance].calls objectForKey:callId]; if (call) { NSArray *DTMF = @[@"0", @"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9", @"*", @"#"]; if ([DTMF containsObject:dtmf]) { CallDTMF dtmfParam; if ([dtmf isEqualToString:@"0"]) { dtmfParam = CallDTMFZero; } else if ([dtmf isEqualToString:@"1"]) { dtmfParam = CallDTMFOne; } else if ([dtmf isEqualToString:@"2"]) { dtmfParam = CallDTMFTwo; } else if ([dtmf isEqualToString:@"3"]) { dtmfParam = CallDTMFThree; } else if ([dtmf isEqualToString:@"4"]) { dtmfParam = CallDTMFFour; } else if ([dtmf isEqualToString:@"5"]) { dtmfParam = CallDTMFFive; } else if ([dtmf isEqualToString:@"6"]) { dtmfParam = CallDTMFSix; } else if ([dtmf isEqualToString:@"7"]) { dtmfParam = CallDTMFSeven; } else if ([dtmf isEqualToString:@"8"]) { dtmfParam = CallDTMFEight; } else if ([dtmf isEqualToString:@"9"]) { dtmfParam = CallDTMFNine; } else if ([dtmf isEqualToString:@"*"]) { dtmfParam = CallDTMFStar; } else { dtmfParam = CallDTMFPound; } [call sendDTMF:dtmfParam completionHandler:^(BOOL status, int code, NSString *message) { if (status) { callback(@[@(YES), @(0), @"Sends successfully"]); } else { callback(@[@(NO), @(-1), @"Failed to send. The client is not connected to Stringee Server."]); } }]; } else { callback(@[@(NO), @(-4), @"Failed to send. The dtmf is invalid."]); } } else { callback(@[@(NO), @(-3), @"Failed to send. The call is not found."]); } } else { callback(@[@(NO), @(-2), @"Failed to send. The callId is invalid."]); } } RCT_EXPORT_METHOD(sendCallInfo:(NSString *)callId callInfo:(NSString *)callInfo callback:(RCTResponseSenderBlock)callback) { if (callId.length) { StringeeCall *call = [[RNStringeeInstanceManager instance].calls objectForKey:callId]; if (call) { NSError *jsonError; NSData *objectData = [callInfo dataUsingEncoding:NSUTF8StringEncoding]; NSDictionary *data = [NSJSONSerialization JSONObjectWithData:objectData options:NSJSONReadingMutableContainers error:&jsonError]; if (jsonError) { callback(@[@(NO), @(-4), @"The call info format is invalid."]); } else { [call sendCallInfo:data completionHandler:^(BOOL status, int code, NSString *message) { if (status) { callback(@[@(YES), @(0), @"Sends successfully"]); } else { callback(@[@(NO), @(-1), @"Failed to send. The client is not connected to Stringee Server."]); } }]; } } else { callback(@[@(NO), @(-3), @"Failed to send. The call is not found"]); } } else { callback(@[@(NO), @(-2), @"Failed to send. The callId is invalid"]); } } RCT_EXPORT_METHOD(getCallStats:(NSString *)uuid callId:(NSString *)callId callback:(RCTResponseSenderBlock)callback) { RNClientWrapper *wrapper = [RNStringeeInstanceManager.instance.clientWrappers objectForKey:uuid]; if (wrapper == nil) { callback(@[@(NO), @(-1), @"Wrapper is not found", [NSNull null], [NSNull null]]); return; } if (!wrapper.client || !wrapper.client.hasConnected) { callback(@[@(NO), @(-1), @"StringeeClient is not initialzied or connected.", @""]); return; } if (!callId.length) { callback(@[@(NO), @(-2), @"The call id is invalid.", @""]); return; } StringeeCall *call = [[RNStringeeInstanceManager instance].calls objectForKey:callId]; if (!call) { callback(@[@(NO), @(-3), @"The call is not found.", @""]); return; } [call statsWithCompletionHandler:^(NSDictionary *values) { NSData *jsonData = [NSJSONSerialization dataWithJSONObject:values options:NSJSONWritingPrettyPrinted error:nil]; NSString *jsonString = [[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] stringByReplacingOccurrencesOfString:@" " withString:@""]; callback(@[@(YES), @(0), @"Success", jsonString]); }]; } RCT_EXPORT_METHOD(mute:(NSString *)callId mute:(BOOL)mute callback:(RCTResponseSenderBlock)callback) { if (!callId.length) { callback(@[@(NO), @(-2), @"The call id is invalid."]); return; } StringeeCall *call = [[RNStringeeInstanceManager instance].calls objectForKey:callId]; if (!call) { callback(@[@(NO), @(-3), @"The call is not found."]); return; } [call mute:mute]; callback(@[@(YES), @(0), @"Success"]); } RCT_EXPORT_METHOD(setSpeakerphoneOn:(NSString *)callId speaker:(BOOL)speaker callback:(RCTResponseSenderBlock)callback) { if (!callId.length) { callback(@[@(NO), @(-2), @"The call id is invalid."]); return; } StringeeCall *call = [[RNStringeeInstanceManager instance].calls objectForKey:callId]; if (!call) { callback(@[@(NO), @(-3), @"The call is not found."]); return; } [[StringeeAudioManager instance] setLoudspeaker:speaker]; callback(@[@(YES), @(0), @"Success"]); } RCT_EXPORT_METHOD(switchCamera:(NSString *)callId callback:(RCTResponseSenderBlock)callback) { if (!callId.length) { callback(@[@(NO), @(-2), @"The call id is invalid."]); return; } StringeeCall *call = [[RNStringeeInstanceManager instance].calls objectForKey:callId]; if (!call) { callback(@[@(NO), @(-3), @"The call is not found."]); return; } [call switchCamera]; callback(@[@(YES), @(0), @"Success"]); } RCT_EXPORT_METHOD(enableVideo:(NSString *)callId enableVideo:(BOOL)enableVideo callback:(RCTResponseSenderBlock)callback) { if (!callId.length) { callback(@[@(NO), @(-2), @"The call id is invalid."]); return; } StringeeCall *call = [[RNStringeeInstanceManager instance].calls objectForKey:callId]; if (!call) { callback(@[@(NO), @(-3), @"The call is not found."]); return; } [call enableLocalVideo:enableVideo]; callback(@[@(YES), @(0), @"Success"]); } - (void)didChangeSignalingState:(StringeeCall *)stringeeCall signalingState:(SignalingState)signalingState reason:(NSString *)reason sipCode:(int)sipCode sipReason:(NSString *)sipReason { if ([jsEvents containsObject:didChangeSignalingState]) { [self sendEventWithName:didChangeSignalingState body:@{ @"callId" : stringeeCall.callId, @"code" : @(signalingState), @"reason" : reason, @"sipCode" : @(sipCode), @"sipReason" : sipReason, @"serial": @(stringeeCall.serial) }]; } } - (void)didChangeMediaState:(StringeeCall *)stringeeCall mediaState:(MediaState)mediaState { if ([jsEvents containsObject:didChangeMediaState]) { switch (mediaState) { case MediaStateConnected: [self sendEventWithName:didChangeMediaState body:@{ @"callId" : stringeeCall.callId, @"code" : @(0), @"description" : @"Connected" }]; break; case MediaStateDisconnected: [self sendEventWithName:didChangeMediaState body:@{ @"callId" : stringeeCall.callId, @"code" : @(1), @"description" : @"Disconnected" }]; break; default: break; } } } - (void)didReceiveLocalStream:(StringeeCall *)stringeeCall { if ([jsEvents containsObject:didReceiveLocalStream]) { [self sendEventWithName:didReceiveLocalStream body:@{ @"callId" : stringeeCall.callId }]; } } - (void)didReceiveRemoteStream:(StringeeCall *)stringeeCall { if ([jsEvents containsObject:didReceiveRemoteStream]) { [self sendEventWithName:didReceiveRemoteStream body:@{ @"callId" : stringeeCall.callId }]; } } - (void)didReceiveDtmfDigit:(StringeeCall *)stringeeCall callDTMF:(CallDTMF)callDTMF { if ([jsEvents containsObject:didReceiveDtmfDigit]) { NSString * digit = @""; if ((long)callDTMF <= 9) { digit = [NSString stringWithFormat:@"%ld", (long)callDTMF]; } else if (callDTMF == 10) { digit = @"*"; } else if (callDTMF == 11) { digit = @"#"; } [self sendEventWithName:didReceiveDtmfDigit body:@{ @"callId" : stringeeCall.callId, @"dtmf" : digit }]; } } - (void)didReceiveCallInfo:(StringeeCall *)stringeeCall info:(NSDictionary *)info { if ([jsEvents containsObject:didReceiveCallInfo]) { NSData *jsonData = [NSJSONSerialization dataWithJSONObject:info options:NSJSONWritingPrettyPrinted error:nil]; NSString *jsonString = [[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] stringByReplacingOccurrencesOfString:@" " withString:@""]; [self sendEventWithName:didReceiveCallInfo body:@{ @"callId" : stringeeCall.callId, @"data" : jsonString }]; } } - (void)didHandleOnAnotherDevice:(StringeeCall *)stringeeCall signalingState:(SignalingState)signalingState reason:(NSString *)reason sipCode:(int)sipCode sipReason:(NSString *)sipReason { if ([jsEvents containsObject:didHandleOnAnotherDevice]) { [self sendEventWithName:didHandleOnAnotherDevice body:@{ @"callId" : stringeeCall.callId, @"code" : @(signalingState), @"description" : reason }]; } } - (void)addRenderToView:(UIView *)view callId:(NSString *)callId isLocal:(BOOL)isLocal contentMode:(StringeeVideoContentMode)contentMode { if (callId.length) { StringeeCall *call = [[RNStringeeInstanceManager instance].calls objectForKey:callId]; if (call) { if (isLocal) { call.localVideoView.frame = CGRectMake(0, 0, view.bounds.size.width, view.bounds.size.height); call.localVideoView.contentMode = contentMode; [view addSubview:call.localVideoView]; } else { call.remoteVideoView.frame = CGRectMake(0, 0, view.bounds.size.width, view.bounds.size.height); call.remoteVideoView.contentMode = contentMode; [view addSubview:call.remoteVideoView]; } } } } @end