/** * @copyright Copyright (c) 2020 Ivan Sein * * @author Ivan Sein * * @license GNU GPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #import "NCCallController.h" #import #import #import #import #import #import #import #import #import #import "NCAPIController.h" #import "NCAudioController.h" #import "NCSettingsController.h" #import "NCSignalingController.h" #import "NCExternalSignalingController.h" static NSString * const kNCMediaStreamId = @"NCMS"; static NSString * const kNCAudioTrackId = @"NCa0"; static NSString * const kNCVideoTrackId = @"NCv0"; static NSString * const kNCVideoTrackKind = @"video"; @interface NCCallController () @property (nonatomic, assign) BOOL isAudioOnly; @property (nonatomic, assign) BOOL inCall; @property (nonatomic, assign) BOOL leavingCall; @property (nonatomic, assign) BOOL preparedForRejoin; @property (nonatomic, assign) BOOL joinedCallOnce; @property (nonatomic, assign) NSInteger joinCallAttempts; @property (nonatomic, strong) AVAudioRecorder *recorder; @property (nonatomic, strong) NSTimer *micAudioLevelTimer; @property (nonatomic, assign) BOOL speaking; @property (nonatomic, strong) NSTimer *sendNickTimer; @property (nonatomic, strong) NSArray *pendingUsersInRoom; @property (nonatomic, strong) NSArray *usersInRoom; @property (nonatomic, strong) NSArray *peersInCall; @property (nonatomic, strong) NCPeerConnection *ownPeerConnection; @property (nonatomic, strong) NSMutableDictionary *connectionsDict; @property (nonatomic, strong) NSMutableDictionary *pendingOffersDict; @property (nonatomic, strong) RTCMediaStream *localStream; @property (nonatomic, strong) RTCAudioTrack *localAudioTrack; @property (nonatomic, strong) RTCVideoTrack *localVideoTrack; @property (nonatomic, strong) RTCPeerConnectionFactory *peerConnectionFactory; @property (nonatomic, strong) NCSignalingController *signalingController; @property (nonatomic, strong) NCExternalSignalingController *externalSignalingController; @property (nonatomic, strong) TalkAccount *account; @property (nonatomic, strong) NSURLSessionTask *joinCallTask; @property (nonatomic, strong) NSURLSessionTask *getPeersForCallTask; @end @implementation NCCallController - (instancetype)initWithDelegate:(id)delegate inRoom:(NCRoom *)room forAudioOnlyCall:(BOOL)audioOnly withSessionId:(NSString *)sessionId { self = [super init]; if (self) { _delegate = delegate; _room = room; _isAudioOnly = audioOnly; _userSessionId = sessionId; _peerConnectionFactory = [[RTCPeerConnectionFactory alloc] init]; _connectionsDict = [[NSMutableDictionary alloc] init]; _pendingOffersDict = [[NSMutableDictionary alloc] init]; _usersInRoom = [[NSArray alloc] init]; _peersInCall = [[NSArray alloc] init]; _signalingController = [[NCSignalingController alloc] initForRoom:room]; _signalingController.observer = self; _account = [[NCDatabaseManager sharedInstance] activeAccount]; _externalSignalingController = [[NCSettingsController sharedInstance] externalSignalingControllerForAccountId:_account.accountId]; _externalSignalingController.delegate = self; if (audioOnly) { [[NCAudioController sharedInstance] setAudioSessionToVoiceChatMode]; } else { [[NCAudioController sharedInstance] setAudioSessionToVideoChatMode]; } [self initRecorder]; } return self; } - (void)startCall { [self createLocalMedia]; [self joinCall]; } - (void)joinCall { _joinCallTask = [[NCAPIController sharedInstance] joinCall:_room.token forAccount:_account withCompletionBlock:^(NSError *error) { if (!error) { [self.delegate callControllerDidJoinCall:self]; [self getPeersForCall]; [self startMonitoringMicrophoneAudioLevel]; if ([_externalSignalingController isEnabled]) { _userSessionId = [_externalSignalingController sessionId]; if ([_externalSignalingController hasMCU]) { [self createOwnPublishPeerConnection]; } if (_pendingUsersInRoom) { NSLog(@"Procees pending users on start call");; NSArray *usersInRoom = [_pendingUsersInRoom copy]; _pendingUsersInRoom = nil; [self processUsersInRoom:usersInRoom]; } } else { [_signalingController startPullingSignalingMessages]; } _joinedCallOnce = YES; [self setInCall:YES]; } else { if (_joinCallAttempts < 3) { NSLog(@"Could not join call, retrying. %ld", (long)_joinCallAttempts); _joinCallAttempts += 1; [self joinCall]; return; } [self.delegate callControllerDidFailedJoiningCall:self]; NSLog(@"Could not join call. Error: %@", error.description); } }]; } - (void)shouldRejoinCall { _userSessionId = [_externalSignalingController sessionId]; _joinCallTask = [[NCAPIController sharedInstance] joinCall:_room.token forAccount:_account withCompletionBlock:^(NSError *error) { if (!error) { [self.delegate callControllerDidJoinCall:self]; NSLog(@"Rejoined call"); if ([_externalSignalingController hasMCU]) { [self createOwnPublishPeerConnection]; } if (_pendingUsersInRoom) { NSLog(@"Procees pending users on rejoin"); NSArray *usersInRoom = [_pendingUsersInRoom copy]; _pendingUsersInRoom = nil; [self processUsersInRoom:usersInRoom]; } [self setInCall:YES]; } else { NSLog(@"Could not rejoin call. Error: %@", error.description); } }]; } - (void)willRejoinCall { NSLog(@"willRejoinCall"); [self setInCall:NO]; [self cleanCurrentPeerConnections]; [self.delegate callControllerIsReconnectingCall:self]; _preparedForRejoin = YES; } - (void)forceReconnect { NSLog(@"forceReconnect"); [self setInCall:NO]; [self cleanCurrentPeerConnections]; [self.delegate callControllerIsReconnectingCall:self]; [_externalSignalingController forceReconnect]; } - (void)leaveCall { [self setLeavingCall:YES]; [self stopSendingNick]; [[NSNotificationCenter defaultCenter] removeObserver:self]; for (NCPeerConnection *peerConnectionWrapper in [_connectionsDict allValues]) { [peerConnectionWrapper close]; } [_localStream removeAudioTrack:_localAudioTrack]; [_localStream removeVideoTrack:_localVideoTrack]; _localStream = nil; _localAudioTrack = nil; _localVideoTrack = nil; _peerConnectionFactory = nil; _connectionsDict = nil; [self stopMonitoringMicrophoneAudioLevel]; [_signalingController stopAllRequests]; if (_inCall) { [_getPeersForCallTask cancel]; _getPeersForCallTask = nil; [[NCAPIController sharedInstance] leaveCall:_room.token forAccount:[[NCDatabaseManager sharedInstance] activeAccount] withCompletionBlock:^(NSError *error) { [self.delegate callControllerDidEndCall:self]; if (error) { NSLog(@"Could not leave call. Error: %@", error.description); } }]; } else { [_joinCallTask cancel]; _joinCallTask = nil; [self.delegate callControllerDidEndCall:self]; } [[NCAudioController sharedInstance] disableAudioSession]; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; NSLog(@"NCCallController dealloc"); } - (BOOL)isVideoEnabled { RTCVideoTrack *videoTrack = [_localStream.videoTracks firstObject]; return videoTrack ? videoTrack.isEnabled : NO; } - (BOOL)isAudioEnabled { RTCAudioTrack *audioTrack = [_localStream.audioTracks firstObject]; return audioTrack ? audioTrack.isEnabled : NO; } - (void)enableVideo:(BOOL)enable { RTCVideoTrack *videoTrack = [_localStream.videoTracks firstObject]; [videoTrack setIsEnabled:enable]; [self sendDataChannelMessageToAllOfType:enable ? @"videoOn" : @"videoOff" withPayload:nil]; } - (void)enableAudio:(BOOL)enable { RTCAudioTrack *audioTrack = [_localStream.audioTracks firstObject]; [audioTrack setIsEnabled:enable]; [self sendDataChannelMessageToAllOfType:enable ? @"audioOn" : @"audioOff" withPayload:nil]; if (!enable) { _speaking = NO; [self sendDataChannelMessageToAllOfType:@"stoppedSpeaking" withPayload:nil]; } } #pragma mark - Call controller - (void)cleanCurrentPeerConnections { for (NCPeerConnection *peerConnectionWrapper in [_connectionsDict allValues]) { [self.delegate callController:self peerLeft:peerConnectionWrapper]; peerConnectionWrapper.delegate = nil; [peerConnectionWrapper close]; } for (NSTimer *pendingOfferTimer in [_pendingOffersDict allValues]) { [pendingOfferTimer invalidate]; } _connectionsDict = [[NSMutableDictionary alloc] init]; _pendingOffersDict = [[NSMutableDictionary alloc] init]; _usersInRoom = [[NSArray alloc] init]; } - (void)cleanPeerConnectionsForSessionId:(NSString *)sessionId { NSString *peerKey = [sessionId stringByAppendingString:kRoomTypeVideo]; NCPeerConnection *removedPeerConnection = [_connectionsDict objectForKey:peerKey]; if (removedPeerConnection) { NSLog(@"Removing peer connection: %@", sessionId); [self.delegate callController:self peerLeft:removedPeerConnection]; removedPeerConnection.delegate = nil; [removedPeerConnection close]; [_connectionsDict removeObjectForKey:peerKey]; } // Close possible screen peers peerKey = [sessionId stringByAppendingString:kRoomTypeScreen]; removedPeerConnection = [_connectionsDict objectForKey:peerKey]; if (removedPeerConnection) { removedPeerConnection.delegate = nil; [removedPeerConnection close]; [_connectionsDict removeObjectForKey:peerKey]; } } #pragma mark - Microphone audio level - (void)startMonitoringMicrophoneAudioLevel { _micAudioLevelTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(checkMicAudioLevel) userInfo:nil repeats:YES]; } - (void)stopMonitoringMicrophoneAudioLevel { [_micAudioLevelTimer invalidate]; _micAudioLevelTimer = nil; [_recorder stop]; _recorder = nil; } - (void)initRecorder { NSURL *url = [NSURL fileURLWithPath:@"/dev/null"]; NSDictionary *settings = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithFloat: 44100.0], AVSampleRateKey, [NSNumber numberWithInt: kAudioFormatAppleLossless], AVFormatIDKey, [NSNumber numberWithInt: 0], AVNumberOfChannelsKey, [NSNumber numberWithInt: AVAudioQualityMax], AVEncoderAudioQualityKey, nil]; NSError *error; _recorder = [[AVAudioRecorder alloc] initWithURL:url settings:settings error:&error]; if (_recorder) { [_recorder prepareToRecord]; _recorder.meteringEnabled = YES; [_recorder record]; } else { NSLog(@"Failed initializing recorder."); } } - (void)checkMicAudioLevel { if ([self isAudioEnabled]) { [_recorder updateMeters]; float averagePower = [_recorder averagePowerForChannel:0]; if (averagePower >= -50.0f && !_speaking) { _speaking = YES; [self sendDataChannelMessageToAllOfType:@"speaking" withPayload:nil]; } else if (averagePower < -50.0f && _speaking) { _speaking = NO; [self sendDataChannelMessageToAllOfType:@"stoppedSpeaking" withPayload:nil]; } } } #pragma mark - Call participants - (void)getPeersForCall { _getPeersForCallTask = [[NCAPIController sharedInstance] getPeersForCall:_room.token forAccount:_account withCompletionBlock:^(NSMutableArray *peers, NSError *error) { if (!error) { _peersInCall = peers; } }]; } #pragma mark - Audio & Video senders - (void)createLocalAudioTrack { NSDictionary *mandatoryConstraints = @{ kRTCMediaConstraintsLevelControl : kRTCMediaConstraintsValueTrue }; RTCMediaConstraints *constraints = [[RTCMediaConstraints alloc] initWithMandatoryConstraints:mandatoryConstraints optionalConstraints:nil]; RTCAudioSource *source = [_peerConnectionFactory audioSourceWithConstraints:constraints]; _localAudioTrack = [_peerConnectionFactory audioTrackWithSource:source trackId:kNCAudioTrackId]; [_localStream addAudioTrack:_localAudioTrack]; } - (void)createLocalVideoTrack { #if !TARGET_IPHONE_SIMULATOR RTCVideoSource *source = [_peerConnectionFactory videoSource]; RTCCameraVideoCapturer *capturer = [[RTCCameraVideoCapturer alloc] initWithDelegate:source]; [self.delegate callController:self didCreateLocalVideoCapturer:capturer]; _localVideoTrack = [_peerConnectionFactory videoTrackWithSource:source trackId:kNCVideoTrackId]; [_localStream addVideoTrack:_localVideoTrack]; #endif } - (void)createLocalMedia { RTCMediaStream *localMediaStream = [_peerConnectionFactory mediaStreamWithStreamId:kNCMediaStreamId]; self.localStream = localMediaStream; [self createLocalAudioTrack]; if (!_isAudioOnly) { [self createLocalVideoTrack]; } } #pragma mark - Peer Connection Wrapper - (NCPeerConnection *)getPeerConnectionWrapperForSessionId:(NSString *)sessionId ofType:(NSString *)roomType { NSString *peerKey = [sessionId stringByAppendingString:roomType]; NCPeerConnection *peerConnectionWrapper = [_connectionsDict objectForKey:peerKey]; if (!peerConnectionWrapper) { // Create peer connection. NSLog(@"Creating a peer for %@", sessionId); NSArray *iceServers = [_signalingController getIceServers]; BOOL screensharingPeer = [roomType isEqualToString:kRoomTypeScreen]; peerConnectionWrapper = [[NCPeerConnection alloc] initWithSessionId:sessionId andICEServers:iceServers forAudioOnlyCall:screensharingPeer ? NO : _isAudioOnly]; peerConnectionWrapper.roomType = roomType; peerConnectionWrapper.delegate = self; // TODO: Try to get display name here if (![_externalSignalingController hasMCU] || !screensharingPeer) { [peerConnectionWrapper.peerConnection addStream:_localStream]; } [_connectionsDict setObject:peerConnectionWrapper forKey:peerKey]; NSLog(@"Peer joined: %@", sessionId); [self.delegate callController:self peerJoined:peerConnectionWrapper]; } return peerConnectionWrapper; } - (NCPeerConnection *)peerConnectionWrapperForConnection:(RTCPeerConnection *)connection { NCPeerConnection *peerConnectionWrapper = nil; NSArray *connectionWrappers = [self.connectionsDict allValues]; for (NCPeerConnection *wrapper in connectionWrappers) { if ([wrapper.peerConnection isEqual:connection]) { peerConnectionWrapper = wrapper; break; } } return peerConnectionWrapper; } - (void)sendDataChannelMessageToAllOfType:(NSString *)type withPayload:(id)payload { if ([_externalSignalingController hasMCU]) { [_ownPeerConnection sendDataChannelMessageOfType:type withPayload:payload]; } else { NSArray *connectionWrappers = [self.connectionsDict allValues]; for (NCPeerConnection *peerConnection in connectionWrappers) { [peerConnection sendDataChannelMessageOfType:type withPayload:payload]; } } } #pragma mark - External signaling support - (void)createOwnPublishPeerConnection { if (_ownPeerConnection) { _ownPeerConnection.delegate = nil; [_ownPeerConnection close]; } NSLog(@"Creating own pusblish peer connection: %@", _userSessionId); NSArray *iceServers = [_signalingController getIceServers]; _ownPeerConnection = [[NCPeerConnection alloc] initForMCUWithSessionId:_userSessionId andICEServers:iceServers forAudioOnlyCall:YES]; _ownPeerConnection.roomType = kRoomTypeVideo; _ownPeerConnection.delegate = self; NSString *peerKey = [_userSessionId stringByAppendingString:kRoomTypeVideo]; [_connectionsDict setObject:_ownPeerConnection forKey:peerKey]; [_ownPeerConnection.peerConnection addStream:_localStream]; [_ownPeerConnection sendPublishOfferToMCU]; } - (void)sendNick { NSDictionary *payload = @{ @"userid":_account.userId, @"name":_account.userDisplayName }; [self sendDataChannelMessageToAllOfType:@"nickChanged" withPayload:payload]; } - (void)startSendingNick { dispatch_async(dispatch_get_main_queue(), ^{ [_sendNickTimer invalidate]; _sendNickTimer = nil; _sendNickTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(sendNick) userInfo:nil repeats:YES]; }); } - (void)stopSendingNick { [_sendNickTimer invalidate]; _sendNickTimer = nil; } - (void)requestNewOffer:(NSTimer *)timer { NSString *sessionId = [timer.userInfo objectForKey:@"sessionId"]; NSString *roomType = [timer.userInfo objectForKey:@"roomType"]; [_externalSignalingController requestOfferForSessionId:sessionId andRoomType:roomType]; } - (void)checkIfPendingOffer:(NCSignalingMessage *)signalingMessage { NSTimer *pendingRequestTimer = [_pendingOffersDict objectForKey:signalingMessage.from]; if (pendingRequestTimer && signalingMessage.messageType == kNCSignalingMessageTypeOffer) { NSLog(@"Pending requested offer arrived. Removing timer."); [pendingRequestTimer invalidate]; } } #pragma mark - External Signaling Controller Delegate - (void)externalSignalingController:(NCExternalSignalingController *)externalSignalingController didReceivedSignalingMessage:(NSDictionary *)signalingMessageDict { NSLog(@"External signaling message received: %@", signalingMessageDict); NCSignalingMessage *signalingMessage = [NCSignalingMessage messageFromExternalSignalingJSONDictionary:signalingMessageDict]; [self checkIfPendingOffer:signalingMessage]; [self processSignalingMessage:signalingMessage]; } - (void)externalSignalingController:(NCExternalSignalingController *)externalSignalingController didReceivedParticipantListMessage:(NSDictionary *)participantListMessageDict { NSLog(@"External participants message received: %@", participantListMessageDict); NSArray *usersInRoom = [participantListMessageDict objectForKey:@"users"]; if (_inCall) { [self processUsersInRoom:usersInRoom]; } else { // Store pending usersInRoom since this websocket message could // arrive before NCCallController knows that it's in the call. _pendingUsersInRoom = usersInRoom; } } - (void)externalSignalingControllerShouldRejoinCall:(NCExternalSignalingController *)externalSignalingController { // Call controller should rejoin the call if it was notifiy with the willRejoin notification first. // Also we should check that it has joined the call first with the startCall method. if (_preparedForRejoin && _joinedCallOnce) { _preparedForRejoin = NO; [self shouldRejoinCall]; } } - (void)externalSignalingControllerWillRejoinCall:(NCExternalSignalingController *)externalSignalingController { [self willRejoinCall]; } #pragma mark - Signaling Controller Delegate - (void)signalingController:(NCSignalingController *)signalingController didReceiveSignalingMessage:(NSDictionary *)message { NSString *messageType = [message objectForKey:@"type"]; if (_leavingCall) {return;} if ([messageType isEqualToString:@"usersInRoom"]) { [self processUsersInRoom:[message objectForKey:@"data"]]; } else if ([messageType isEqualToString:@"message"]) { NCSignalingMessage *signalingMessage = [NCSignalingMessage messageFromJSONString:[message objectForKey:@"data"]]; [self processSignalingMessage:signalingMessage]; } else { NSLog(@"Uknown message: %@", [message objectForKey:@"data"]); } } #pragma mark - Signaling functions - (void)processSignalingMessage:(NCSignalingMessage *)signalingMessage { if (signalingMessage) { switch (signalingMessage.messageType) { case kNCSignalingMessageTypeOffer: case kNCSignalingMessageTypeAnswer: { NCPeerConnection *peerConnectionWrapper = [self getPeerConnectionWrapperForSessionId:signalingMessage.from ofType:signalingMessage.roomType]; NCSessionDescriptionMessage *sdpMessage = (NCSessionDescriptionMessage *)signalingMessage; RTCSessionDescription *description = sdpMessage.sessionDescription; [peerConnectionWrapper setPeerName:sdpMessage.nick]; [peerConnectionWrapper setRemoteDescription:description]; break; } case kNCSignalingMessageTypeCandidate: { NCPeerConnection *peerConnectionWrapper = [self getPeerConnectionWrapperForSessionId:signalingMessage.from ofType:signalingMessage.roomType]; NCICECandidateMessage *candidateMessage = (NCICECandidateMessage *)signalingMessage; [peerConnectionWrapper addICECandidate:candidateMessage.candidate]; break; } case kNCSignalingMessageTypeUnshareScreen: { NCPeerConnection *peerConnectionWrapper = [self getPeerConnectionWrapperForSessionId:signalingMessage.from ofType:signalingMessage.roomType]; NSString *peerKey = [peerConnectionWrapper.peerId stringByAppendingString:kRoomTypeScreen]; NCPeerConnection *screenPeerConnection = [_connectionsDict objectForKey:peerKey]; if (screenPeerConnection) { [screenPeerConnection close]; [_connectionsDict removeObjectForKey:peerKey]; } [self.delegate callController:self didReceiveUnshareScreenFromPeer:peerConnectionWrapper]; break; } case kNCSignalingMessageTypeControl: { NSString *action = [signalingMessage.payload objectForKey:@"action"]; if ([action isEqualToString:@"forceMute"]) { NSString *peerId = [signalingMessage.payload objectForKey:@"peerId"]; [self.delegate callController:self didReceiveForceMuteActionForPeerId:peerId]; } break; } case kNCSignalingMessageTypeUknown: NSLog(@"Received an unknown signaling message: %@", signalingMessage); break; } } } - (void)processUsersInRoom:(NSArray *)users { NSMutableArray *newSessions = [self getInCallSessionsFromUsersInRoom:users]; NSMutableArray *oldSessions = [NSMutableArray arrayWithArray:_usersInRoom]; //Save current sessions in call _usersInRoom = [NSArray arrayWithArray:newSessions]; // Calculate sessions that left the call NSMutableArray *leftSessions = [NSMutableArray arrayWithArray:oldSessions]; [leftSessions removeObjectsInArray:newSessions]; // Calculate sessions that join the call [newSessions removeObjectsInArray:oldSessions]; if (_leavingCall) {return;} if (newSessions.count > 0) { [self getPeersForCall]; } // Create new peer connections for new sessions in call for (NSString *sessionId in newSessions) { NSString *peerKey = [sessionId stringByAppendingString:kRoomTypeVideo]; if (![_connectionsDict objectForKey:peerKey] && ![_userSessionId isEqualToString:sessionId]) { if ([_externalSignalingController hasMCU]) { NSLog(@"Requesting offer to the MCU for session: %@", sessionId); [_externalSignalingController requestOfferForSessionId:sessionId andRoomType:kRoomTypeVideo]; } else { NSComparisonResult result = [sessionId compare:_userSessionId]; if (result == NSOrderedAscending) { NSLog(@"Creating offer..."); NCPeerConnection *peerConnectionWrapper = [self getPeerConnectionWrapperForSessionId:sessionId ofType:kRoomTypeVideo]; [peerConnectionWrapper sendOffer]; } else { NSLog(@"Waiting for offer..."); } } } } // Close old peer connections for sessions that left the call for (NSString *sessionId in leftSessions) { [self cleanPeerConnectionsForSessionId:sessionId]; } } - (NSMutableArray *)getInCallSessionsFromUsersInRoom:(NSArray *)users { NSMutableArray *sessions = [[NSMutableArray alloc] init]; for (NSMutableDictionary *user in users) { NSString *sessionId = [user objectForKey:@"sessionId"]; BOOL inCall = [[user objectForKey:@"inCall"] boolValue]; if (inCall) { [sessions addObject:sessionId]; } } NSLog(@"InCall sessions: %@", sessions); return sessions; } - (NSString *)getUserIdFromSessionId:(NSString *)sessionId { if ([_externalSignalingController isEnabled]) { return [_externalSignalingController getUserIdFromSessionId:sessionId]; } NSString *userId = nil; for (NSMutableDictionary *user in _peersInCall) { NSString *userSessionId = [user objectForKey:@"sessionId"]; if ([userSessionId isEqualToString:sessionId]) { userId = [user objectForKey:@"userId"]; } } return userId; } - (void)getUserIdInServerFromSessionId:(NSString *)sessionId withCompletionBlock:(GetUserIdForSessionIdCompletionBlock)block { [[NCAPIController sharedInstance] getPeersForCall:_room.token forAccount:_account withCompletionBlock:^(NSMutableArray *peers, NSError *error) { if (!error) { NSString *userId = nil; for (NSMutableDictionary *user in peers) { NSString *userSessionId = [user objectForKey:@"sessionId"]; if ([userSessionId isEqualToString:sessionId]) { userId = [user objectForKey:@"userId"]; } } if (block) { block(userId, nil); } } else { if (block) { block(nil, error); } } }]; } #pragma mark - NCPeerConnectionDelegate - (void)peerConnection:(NCPeerConnection *)peerConnection didAddStream:(RTCMediaStream *)stream { if (!peerConnection.isMCUPublisherPeer) { [self.delegate callController:self didAddStream:stream ofPeer:peerConnection]; } } - (void)peerConnection:(NCPeerConnection *)peerConnection didRemoveStream:(RTCMediaStream *)stream { [self.delegate callController:self didRemoveStream:stream ofPeer:peerConnection]; } - (void)peerConnection:(NCPeerConnection *)peerConnection didChangeIceConnectionState:(RTCIceConnectionState)newState { if (newState == RTCIceConnectionStateFailed) { // If publisher peer failed then reconnect if (peerConnection.isMCUPublisherPeer) { [self forceReconnect]; // If another peer failed using MCU then request a new offer } else if ([_externalSignalingController hasMCU]) { NSString *sessionId = [peerConnection.peerId copy]; NSString *roomType = [peerConnection.roomType copy]; NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] init]; [userInfo setObject:sessionId forKey:@"sessionId"]; [userInfo setObject:roomType forKey:@"roomType"]; // Close failed peer connection [self cleanPeerConnectionsForSessionId:peerConnection.peerId]; // Request new offer [_externalSignalingController requestOfferForSessionId:peerConnection.peerId andRoomType:peerConnection.roomType]; // Set timeout to request new offer NSTimer *pendingOfferTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(requestNewOffer:) userInfo:userInfo repeats:YES]; [_pendingOffersDict setObject:pendingOfferTimer forKey:peerConnection.peerId]; } } [self.delegate callController:self iceStatusChanged:newState ofPeer:peerConnection]; } - (void)peerConnectionDidOpenStatusDataChannel:(NCPeerConnection *)peerConnection { // Send current audio state if (self.isAudioEnabled) { NSLog(@"Send audioOn"); [peerConnection sendDataChannelMessageOfType:@"audioOn" withPayload:nil]; } else { NSLog(@"Send audioOff"); [peerConnection sendDataChannelMessageOfType:@"audioOff" withPayload:nil]; } // Send current video state if (self.isVideoEnabled) { NSLog(@"Send videoOn"); [peerConnection sendDataChannelMessageOfType:@"videoOn" withPayload:nil]; } else { NSLog(@"Send videoOff"); [peerConnection sendDataChannelMessageOfType:@"videoOff" withPayload:nil]; } // Send nick using mcu if (peerConnection.isMCUPublisherPeer) { [self startSendingNick]; } } - (void)peerConnection:(NCPeerConnection *)peerConnection didGenerateIceCandidate:(RTCIceCandidate *)candidate { NCICECandidateMessage *message = [[NCICECandidateMessage alloc] initWithCandidate:candidate from:_userSessionId to:peerConnection.peerId sid:nil roomType:peerConnection.roomType]; if ([_externalSignalingController isEnabled]) { [_externalSignalingController sendCallMessage:message]; } else { [_signalingController sendSignalingMessage:message]; } } - (void)peerConnection:(NCPeerConnection *)peerConnection needsToSendSessionDescription:(RTCSessionDescription *)sessionDescription { NCSessionDescriptionMessage *message = [[NCSessionDescriptionMessage alloc] initWithSessionDescription:sessionDescription from:_userSessionId to:peerConnection.peerId sid:nil roomType:peerConnection.roomType nick:_userDisplayName]; if ([_externalSignalingController isEnabled]) { [_externalSignalingController sendCallMessage:message]; } else { [_signalingController sendSignalingMessage:message]; } } - (void)peerConnection:(NCPeerConnection *)peerConnection didReceiveStatusDataChannelMessage:(NSString *)type { [self.delegate callController:self didReceiveDataChannelMessage:type fromPeer:peerConnection]; } - (void)peerConnection:(NCPeerConnection *)peerConnection didReceivePeerNick:(NSString *)nick { [self.delegate callController:self didReceiveNick:nick fromPeer:peerConnection]; } @end