зеркало из https://github.com/nextcloud/talk-ios.git
1301 строка
49 KiB
Objective-C
1301 строка
49 KiB
Objective-C
/**
|
|
* @copyright Copyright (c) 2020 Ivan Sein <ivan@nextcloud.com>
|
|
*
|
|
* @author Ivan Sein <ivan@nextcloud.com>
|
|
*
|
|
* @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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#import "NCCallController.h"
|
|
|
|
#import <WebRTC/RTCConfiguration.h>
|
|
#import <WebRTC/RTCDataChannelConfiguration.h>
|
|
#import <WebRTC/RTCMediaConstraints.h>
|
|
#import <WebRTC/RTCMediaStream.h>
|
|
#import <WebRTC/RTCPeerConnectionFactory.h>
|
|
#import <WebRTC/RTCAudioTrack.h>
|
|
#import <WebRTC/RTCVideoTrack.h>
|
|
#import <WebRTC/RTCVideoCapturer.h>
|
|
#import <WebRTC/RTCCameraVideoCapturer.h>
|
|
#import <WebRTC/RTCDefaultVideoEncoderFactory.h>
|
|
#import <WebRTC/RTCDefaultVideoDecoderFactory.h>
|
|
|
|
#import "ARDCaptureController.h"
|
|
|
|
#import "CallConstants.h"
|
|
#import "CallKitManager.h"
|
|
#import "NCAPIController.h"
|
|
#import "NCAudioController.h"
|
|
#import "NCDatabaseManager.h"
|
|
#import "NCSettingsController.h"
|
|
#import "NCSignalingController.h"
|
|
#import "NCExternalSignalingController.h"
|
|
|
|
#import "NextcloudTalk-Swift.h"
|
|
|
|
static NSString * const kNCMediaStreamId = @"NCMS";
|
|
static NSString * const kNCAudioTrackId = @"NCa0";
|
|
static NSString * const kNCVideoTrackId = @"NCv0";
|
|
static NSString * const kNCVideoTrackKind = @"video";
|
|
|
|
@interface NCCallController () <NCPeerConnectionDelegate, NCSignalingControllerObserver, NCExternalSignalingControllerDelegate>
|
|
|
|
@property (nonatomic, assign) BOOL isAudioOnly;
|
|
@property (nonatomic, assign) BOOL leavingCall;
|
|
@property (nonatomic, assign) BOOL preparedForRejoin;
|
|
@property (nonatomic, assign) BOOL joinedCallOnce;
|
|
@property (nonatomic, assign) BOOL shouldRejoinCallUsingInternalSignaling;
|
|
@property (nonatomic, assign) BOOL serverSupportsConversationPermissions;
|
|
@property (nonatomic, assign) NSInteger joinCallAttempts;
|
|
@property (nonatomic, strong) AVAudioRecorder *recorder;
|
|
@property (nonatomic, strong) NSTimer *micAudioLevelTimer;
|
|
@property (nonatomic, assign) BOOL speaking;
|
|
@property (nonatomic, assign) NSInteger userInCall;
|
|
@property (nonatomic, assign) NSInteger userPermissions;
|
|
@property (nonatomic, strong) NSTimer *sendNickTimer;
|
|
@property (nonatomic, strong) NSArray *usersInRoom;
|
|
@property (nonatomic, strong) NSArray *sessionsInCall;
|
|
@property (nonatomic, strong) NSArray *peersInCall;
|
|
@property (nonatomic, strong) NCPeerConnection *publisherPeerConnection;
|
|
@property (nonatomic, strong) NSMutableDictionary *connectionsDict;
|
|
@property (nonatomic, strong) NSMutableDictionary *pendingOffersDict;
|
|
@property (nonatomic, strong) RTCAudioTrack *localAudioTrack;
|
|
@property (nonatomic, strong) RTCVideoTrack *localVideoTrack;
|
|
@property (nonatomic, strong) RTCCameraVideoCapturer *localVideoCapturer;
|
|
@property (nonatomic, strong) ARDCaptureController *localVideoCaptureController;
|
|
@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<NCCallControllerDelegate>)delegate inRoom:(NCRoom *)room forAudioOnlyCall:(BOOL)audioOnly withSessionId:(NSString *)sessionId andVoiceChatMode:(BOOL)voiceChatMode
|
|
{
|
|
self = [super init];
|
|
|
|
if (self) {
|
|
_delegate = delegate;
|
|
_room = room;
|
|
_userPermissions = _room.permissions;
|
|
_isAudioOnly = audioOnly;
|
|
_userSessionId = sessionId;
|
|
_connectionsDict = [[NSMutableDictionary alloc] init];
|
|
_pendingOffersDict = [[NSMutableDictionary alloc] init];
|
|
_usersInRoom = [[NSArray alloc] init];
|
|
_sessionsInCall = [[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;
|
|
|
|
// 'conversation-permissions' capability was not added in Talk 13 release, so we check for 'direct-mention-flag' capability
|
|
// as a workaround.
|
|
_serverSupportsConversationPermissions =
|
|
[[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilityConversationPermissions forAccountId:_account.accountId] ||
|
|
[[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilityDirectMentionFlag forAccountId:_account.accountId];
|
|
|
|
[[WebRTCCommon shared] dispatch:^{
|
|
if (audioOnly || voiceChatMode) {
|
|
[[NCAudioController sharedInstance] setAudioSessionToVoiceChatMode];
|
|
} else {
|
|
[[NCAudioController sharedInstance] setAudioSessionToVideoChatMode];
|
|
}
|
|
}];
|
|
|
|
[self initRecorder];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)startCall
|
|
{
|
|
[self createLocalMedia];
|
|
[self joinCall];
|
|
}
|
|
|
|
- (NSString *)signalingSessionId
|
|
{
|
|
if ([_externalSignalingController isEnabled]) {
|
|
return [_externalSignalingController sessionId];
|
|
}
|
|
return _userSessionId;
|
|
}
|
|
|
|
- (NSInteger)joinCallFlags
|
|
{
|
|
NSInteger flags = CallFlagInCall;
|
|
|
|
if ((_userPermissions & NCPermissionCanPublishAudio) != 0 || !_serverSupportsConversationPermissions) {
|
|
flags += CallFlagWithAudio;
|
|
}
|
|
|
|
if (!_isAudioOnly && ((_userPermissions & NCPermissionCanPublishVideo) != 0 || !_serverSupportsConversationPermissions)) {
|
|
flags += CallFlagWithVideo;
|
|
}
|
|
|
|
return flags;
|
|
}
|
|
|
|
- (void)joinCall
|
|
{
|
|
_joinCallTask = [[NCAPIController sharedInstance] joinCall:_room.token withCallFlags:[self joinCallFlags] silently:_silentCall forAccount:_account withCompletionBlock:^(NSError *error, NSInteger statusCode) {
|
|
[[WebRTCCommon shared] dispatch:^{
|
|
if (!error) {
|
|
[self.delegate callControllerDidJoinCall:self];
|
|
[self getPeersForCall];
|
|
[self startMonitoringMicrophoneAudioLevel];
|
|
|
|
if ([self->_externalSignalingController isEnabled]) {
|
|
if ([self->_externalSignalingController hasMCU]) {
|
|
[self createPublisherPeerConnection];
|
|
}
|
|
} else {
|
|
[self->_signalingController startPullingSignalingMessages];
|
|
}
|
|
|
|
self->_joinedCallOnce = YES;
|
|
self->_joinCallAttempts = 0;
|
|
} else {
|
|
if (self->_joinCallAttempts < 3) {
|
|
NSLog(@"Could not join call, retrying. %ld", (long)self->_joinCallAttempts);
|
|
self->_joinCallAttempts += 1;
|
|
[self joinCall];
|
|
return;
|
|
}
|
|
|
|
[self.delegate callControllerDidFailedJoiningCall:self statusCode:@(statusCode) errorReason:[self getJoinCallErrorReason:statusCode]];
|
|
NSLog(@"Could not join call. Error: %@", error.description);
|
|
}
|
|
}];
|
|
}];
|
|
}
|
|
|
|
- (NSString *)getJoinCallErrorReason:(NSInteger)statusCode
|
|
{
|
|
NSString *errorReason = NSLocalizedString(@"Unknown error occurred", nil);
|
|
|
|
switch (statusCode) {
|
|
case 0:
|
|
errorReason = NSLocalizedString(@"No response from server", nil);
|
|
break;
|
|
|
|
case 403:
|
|
errorReason = NSLocalizedString(@"This conversation is read-only", nil);
|
|
break;
|
|
|
|
case 404:
|
|
errorReason = NSLocalizedString(@"Conversation not found or not joined", nil);
|
|
break;
|
|
|
|
case 412:
|
|
errorReason = NSLocalizedString(@"Lobby is still active and you're not a moderator", nil);
|
|
break;
|
|
}
|
|
|
|
return errorReason;
|
|
}
|
|
|
|
- (void)shouldRejoinCall
|
|
{
|
|
[self createLocalMedia];
|
|
|
|
_joinCallTask = [[NCAPIController sharedInstance] joinCall:_room.token withCallFlags:[self joinCallFlags] silently:_silentCall forAccount:_account withCompletionBlock:^(NSError *error, NSInteger statusCode) {
|
|
[[WebRTCCommon shared] dispatch:^{
|
|
if (!error) {
|
|
[self.delegate callControllerDidJoinCall:self];
|
|
NSLog(@"Rejoined call");
|
|
|
|
if ([self->_externalSignalingController hasMCU]) {
|
|
[self createPublisherPeerConnection];
|
|
}
|
|
|
|
self->_joinCallAttempts = 0;
|
|
} else {
|
|
if (self->_joinCallAttempts < 3) {
|
|
NSLog(@"Could not rejoin call, retrying. %ld", (long)self->_joinCallAttempts);
|
|
self->_joinCallAttempts += 1;
|
|
[self shouldRejoinCall];
|
|
return;
|
|
}
|
|
|
|
[self.delegate callControllerDidFailedJoiningCall:self statusCode:@(statusCode) errorReason:[self getJoinCallErrorReason:statusCode]];
|
|
NSLog(@"Could not rejoin call. Error: %@", error.description);
|
|
}
|
|
}];
|
|
}];
|
|
}
|
|
|
|
- (void)willRejoinCall
|
|
{
|
|
NSLog(@"willRejoinCall");
|
|
|
|
[[WebRTCCommon shared] dispatch:^{
|
|
self->_userInCall = 0;
|
|
[self cleanCurrentPeerConnections];
|
|
[self.delegate callControllerIsReconnectingCall:self];
|
|
self->_preparedForRejoin = YES;
|
|
}];
|
|
}
|
|
|
|
- (void)willSwitchToCall:(NSString *)token
|
|
{
|
|
NSLog(@"willSwitchToCall");
|
|
|
|
BOOL isAudioEnabled = [self isAudioEnabled];
|
|
BOOL isVideoEnabled = [self isVideoEnabled];
|
|
|
|
[self stopCallController];
|
|
|
|
[self leaveCallInServerWithCompletionBlock:^(NSError *error) {
|
|
if (error) {
|
|
NSLog(@"Could not leave call. Error: %@", error.description);
|
|
}
|
|
[self.delegate callController:self isSwitchingToCall:token withAudioEnabled:isAudioEnabled andVideoEnabled:isVideoEnabled];
|
|
}];
|
|
}
|
|
|
|
|
|
- (void)forceReconnect
|
|
{
|
|
NSLog(@"forceReconnect");
|
|
|
|
[[WebRTCCommon shared] dispatch:^{
|
|
self->_userInCall = 0;
|
|
[self cleanCurrentPeerConnections];
|
|
[self.delegate callControllerIsReconnectingCall:self];
|
|
|
|
// Remember current audio and video status before rejoin the call
|
|
self->_disableAudioAtStart = ![self isAudioEnabled];
|
|
self->_disableVideoAtStart = ![self isVideoEnabled];
|
|
|
|
if ([self->_externalSignalingController isEnabled]) {
|
|
[self->_externalSignalingController forceReconnect];
|
|
} else {
|
|
[self rejoinCallUsingInternalSignaling];
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)rejoinCallUsingInternalSignaling
|
|
{
|
|
[[NCAPIController sharedInstance] leaveCall:_room.token forAccount:[[NCDatabaseManager sharedInstance] activeAccount] withCompletionBlock:^(NSError *error) {
|
|
if (!error) {
|
|
self->_shouldRejoinCallUsingInternalSignaling = YES;
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)stopCallController
|
|
{
|
|
[self setLeavingCall:YES];
|
|
[self stopSendingNick];
|
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
_externalSignalingController.delegate = nil;
|
|
|
|
[[WebRTCCommon shared] dispatch:^{
|
|
[self cleanCurrentPeerConnections];
|
|
}];
|
|
|
|
[_localVideoCapturer stopCapture];
|
|
_localVideoCapturer = nil;
|
|
_localAudioTrack = nil;
|
|
_localVideoTrack = nil;
|
|
_connectionsDict = nil;
|
|
|
|
[self stopMonitoringMicrophoneAudioLevel];
|
|
[_signalingController stopAllRequests];
|
|
|
|
[_getPeersForCallTask cancel];
|
|
_getPeersForCallTask = nil;
|
|
|
|
[_joinCallTask cancel];
|
|
_joinCallTask = nil;
|
|
}
|
|
|
|
- (void)leaveCallInServerWithCompletionBlock:(LeaveCallCompletionBlock)block
|
|
{
|
|
if (_userInCall) {
|
|
[[NCAPIController sharedInstance] leaveCall:_room.token forAccount:[[NCDatabaseManager sharedInstance] activeAccount] withCompletionBlock:^(NSError *error) {
|
|
block(error);
|
|
}];
|
|
} else {
|
|
block(nil);
|
|
}
|
|
}
|
|
|
|
- (void)leaveCall
|
|
{
|
|
[self stopCallController];
|
|
|
|
[self leaveCallInServerWithCompletionBlock:^(NSError *error) {
|
|
if (error) {
|
|
NSLog(@"Could not leave call. Error: %@", error.description);
|
|
}
|
|
[self.delegate callControllerDidEndCall:self];
|
|
}];
|
|
|
|
[[WebRTCCommon shared] dispatch:^{
|
|
[[NCAudioController sharedInstance] disableAudioSession];
|
|
}];
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
NSLog(@"NCCallController dealloc");
|
|
}
|
|
|
|
- (BOOL)isVideoEnabled
|
|
{
|
|
return _localVideoTrack ? _localVideoTrack.isEnabled : NO;
|
|
}
|
|
|
|
- (BOOL)isAudioEnabled
|
|
{
|
|
return _localAudioTrack ? _localAudioTrack.isEnabled : NO;
|
|
}
|
|
|
|
- (void)switchCamera
|
|
{
|
|
[_localVideoCaptureController switchCamera];
|
|
}
|
|
|
|
- (void)enableVideo:(BOOL)enable
|
|
{
|
|
if (enable) {
|
|
[_localVideoCaptureController startCapture];
|
|
} else {
|
|
[_localVideoCaptureController stopCapture];
|
|
}
|
|
|
|
[_localVideoTrack setIsEnabled:enable];
|
|
[self sendDataChannelMessageToAllOfType:enable ? @"videoOn" : @"videoOff" withPayload:nil];
|
|
}
|
|
|
|
- (void)enableAudio:(BOOL)enable
|
|
{
|
|
[_localAudioTrack setIsEnabled:enable];
|
|
[self sendDataChannelMessageToAllOfType:enable ? @"audioOn" : @"audioOff" withPayload:nil];
|
|
|
|
if (!enable) {
|
|
_speaking = NO;
|
|
[self sendDataChannelMessageToAllOfType:@"stoppedSpeaking" withPayload:nil];
|
|
}
|
|
}
|
|
|
|
- (void)raiseHand:(BOOL)raised
|
|
{
|
|
[[WebRTCCommon shared] dispatch:^{
|
|
NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970] * 1000;
|
|
|
|
NSDictionary *payload = @{
|
|
@"state": @(raised),
|
|
@"timestamp": [NSString stringWithFormat:@"%.0f", timeStamp]
|
|
};
|
|
|
|
for (NCPeerConnection *peer in [self->_connectionsDict allValues]) {
|
|
NCRaiseHandMessage *message = [[NCRaiseHandMessage alloc] initWithFrom:[self signalingSessionId]
|
|
sendTo:peer.peerId
|
|
withPayload:payload
|
|
forRoomType:peer.roomType];
|
|
|
|
if ([self->_externalSignalingController isEnabled]) {
|
|
[self->_externalSignalingController sendCallMessage:message];
|
|
} else {
|
|
[self->_signalingController sendSignalingMessage:message];
|
|
}
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)startRecording
|
|
{
|
|
[[NCAPIController sharedInstance] startRecording:_room.token forAccount:[[NCDatabaseManager sharedInstance] activeAccount] withCompletionBlock:^(NSError *error) {
|
|
if (error) {
|
|
NSLog(@"Could not start call recording. Error: %@", error.description);
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)stopRecording
|
|
{
|
|
[[NCAPIController sharedInstance] stopRecording:_room.token forAccount:[[NCDatabaseManager sharedInstance] activeAccount] withCompletionBlock:^(NSError *error) {
|
|
if (error) {
|
|
NSLog(@"Could not stop call recording. Error: %@", error.description);
|
|
}
|
|
}];
|
|
}
|
|
|
|
#pragma mark - Call controller
|
|
|
|
- (void)cleanCurrentPeerConnections
|
|
{
|
|
[[WebRTCCommon shared] assertQueue];
|
|
|
|
for (NCPeerConnection *peerConnectionWrapper in [_connectionsDict allValues]) {
|
|
if (!peerConnectionWrapper.isMCUPublisherPeer) {
|
|
[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];
|
|
_sessionsInCall = [[NSArray alloc] init];
|
|
_publisherPeerConnection = nil;
|
|
}
|
|
|
|
- (void)cleanPeerConnectionForSessionId:(NSString *)sessionId ofType:(NSString *)roomType
|
|
{
|
|
[[WebRTCCommon shared] assertQueue];
|
|
|
|
NSString *peerKey = [sessionId stringByAppendingString:roomType];
|
|
NCPeerConnection *removedPeerConnection = [_connectionsDict objectForKey:peerKey];
|
|
|
|
if (removedPeerConnection) {
|
|
if ([roomType isEqualToString:kRoomTypeVideo]) {
|
|
NSLog(@"Removing peer from call: %@", sessionId);
|
|
[self.delegate callController:self peerLeft:removedPeerConnection];
|
|
} else if ([roomType isEqualToString:kRoomTypeScreen]) {
|
|
NSLog(@"Removing screensharing from peer: %@", sessionId);
|
|
[self.delegate callController:self didReceiveUnshareScreenFromPeer:removedPeerConnection];
|
|
}
|
|
|
|
removedPeerConnection.delegate = nil;
|
|
[removedPeerConnection close];
|
|
|
|
[_connectionsDict removeObjectForKey:peerKey];
|
|
}
|
|
}
|
|
|
|
- (void)cleanAllPeerConnectionsForSessionId:(NSString *)sessionId
|
|
{
|
|
[[WebRTCCommon shared] assertQueue];
|
|
|
|
[self cleanPeerConnectionForSessionId:sessionId ofType:kRoomTypeVideo];
|
|
[self cleanPeerConnectionForSessionId:sessionId ofType:kRoomTypeScreen];
|
|
|
|
// Invalidate possible request timers
|
|
NSString *peerVideoKey = [sessionId stringByAppendingString:kRoomTypeVideo];
|
|
NSTimer *pendingVideoRequestTimer = [_pendingOffersDict objectForKey:peerVideoKey];
|
|
|
|
if (pendingVideoRequestTimer) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[pendingVideoRequestTimer invalidate];
|
|
});
|
|
}
|
|
|
|
NSString *peerScreenKey = [sessionId stringByAppendingString:kRoomTypeVideo];
|
|
NSTimer *pendingScreenRequestTimer = [_pendingOffersDict objectForKey:peerScreenKey];
|
|
|
|
if (pendingScreenRequestTimer) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[pendingScreenRequestTimer invalidate];
|
|
});
|
|
}
|
|
}
|
|
|
|
#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) {
|
|
return;
|
|
}
|
|
|
|
[[WebRTCCommon shared] dispatch:^{
|
|
self->_peersInCall = peers;
|
|
}];
|
|
}];
|
|
}
|
|
|
|
#pragma mark - Audio & Video senders
|
|
|
|
- (void)createLocalAudioTrack
|
|
{
|
|
RTCPeerConnectionFactory *peerConnectionFactory = [WebRTCCommon shared].peerConnectionFactory;
|
|
RTCAudioSource *source = [peerConnectionFactory audioSourceWithConstraints:nil];
|
|
_localAudioTrack = [peerConnectionFactory audioTrackWithSource:source trackId:kNCAudioTrackId];
|
|
[_localAudioTrack setIsEnabled:!_disableAudioAtStart];
|
|
if ([CallKitManager isCallKitAvailable]) {
|
|
[[CallKitManager sharedInstance] changeAudioMuted:_disableAudioAtStart forCall:_room.token];
|
|
}
|
|
|
|
[self.delegate callController:self didCreateLocalAudioTrack:_localAudioTrack];
|
|
}
|
|
|
|
- (void)createLocalVideoTrack
|
|
{
|
|
#if !TARGET_IPHONE_SIMULATOR
|
|
RTCPeerConnectionFactory *peerConnectionFactory = [WebRTCCommon shared].peerConnectionFactory;
|
|
RTCVideoSource *source = [peerConnectionFactory videoSource];
|
|
_localVideoCapturer = [[RTCCameraVideoCapturer alloc] initWithDelegate:source];
|
|
_localVideoCaptureController = [[ARDCaptureController alloc] initWithCapturer:_localVideoCapturer settings:[[NCSettingsController sharedInstance] videoSettingsModel]];
|
|
[_localVideoCaptureController startCapture];
|
|
|
|
[self.delegate callController:self didCreateLocalVideoCapturer:_localVideoCapturer];
|
|
|
|
_localVideoTrack = [peerConnectionFactory videoTrackWithSource:source trackId:kNCVideoTrackId];
|
|
[_localVideoTrack setIsEnabled:!_disableVideoAtStart];
|
|
|
|
[self.delegate callController:self didCreateLocalVideoTrack:_localVideoTrack];
|
|
#endif
|
|
}
|
|
|
|
- (void)createLocalMedia
|
|
{
|
|
[[WebRTCCommon shared] dispatch:^{
|
|
self->_localAudioTrack = nil;
|
|
self->_localVideoTrack = nil;
|
|
[self->_localVideoCapturer stopCapture];
|
|
self->_localVideoCapturer = nil;
|
|
|
|
if ((self->_userPermissions & NCPermissionCanPublishAudio) != 0 || !self->_serverSupportsConversationPermissions) {
|
|
[self createLocalAudioTrack];
|
|
} else {
|
|
[self.delegate callController:self didCreateLocalAudioTrack:nil];
|
|
}
|
|
|
|
if (!self->_isAudioOnly && ((self->_userPermissions & NCPermissionCanPublishVideo) != 0 || !self->_serverSupportsConversationPermissions)) {
|
|
[self createLocalVideoTrack];
|
|
} else {
|
|
[self.delegate callController:self didCreateLocalVideoTrack:nil];
|
|
}
|
|
}];
|
|
}
|
|
|
|
#pragma mark - Peer Connection Wrapper
|
|
|
|
- (NCPeerConnection *)getPeerConnectionWrapperForSessionId:(NSString *)sessionId ofType:(NSString *)roomType
|
|
{
|
|
[[WebRTCCommon shared] assertQueue];
|
|
|
|
NSString *peerKey = [sessionId stringByAppendingString:roomType];
|
|
NCPeerConnection *peerConnectionWrapper = [_connectionsDict objectForKey:peerKey];
|
|
|
|
return peerConnectionWrapper;
|
|
}
|
|
|
|
- (NCPeerConnection *)getOrCreatePeerConnectionWrapperForSessionId:(NSString *)sessionId ofType:(NSString *)roomType
|
|
{
|
|
[[WebRTCCommon shared] assertQueue];
|
|
|
|
NSString *peerKey = [sessionId stringByAppendingString:roomType];
|
|
NCPeerConnection *peerConnectionWrapper = [self getPeerConnectionWrapperForSessionId:sessionId ofType:roomType];
|
|
|
|
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;
|
|
|
|
// Try to get displayName early
|
|
NSString *displayName = [self getDisplayNameFromSessionId:sessionId];
|
|
if (displayName) {
|
|
[peerConnectionWrapper setPeerName:displayName];
|
|
}
|
|
|
|
// Do not add local stream when using a MCU or to screensharing peers
|
|
if (![_externalSignalingController hasMCU] || !screensharingPeer) {
|
|
if (_localAudioTrack) {
|
|
[peerConnectionWrapper.peerConnection addTrack:_localAudioTrack streamIds:@[kNCMediaStreamId]];
|
|
}
|
|
if (_localVideoTrack) {
|
|
[peerConnectionWrapper.peerConnection addTrack:_localVideoTrack streamIds:@[kNCMediaStreamId]];
|
|
}
|
|
}
|
|
|
|
// Add peer connection to the connections dictionary
|
|
[_connectionsDict setObject:peerConnectionWrapper forKey:peerKey];
|
|
|
|
|
|
// Notify about the new peer
|
|
if (!screensharingPeer) {
|
|
[self.delegate callController:self peerJoined:peerConnectionWrapper];
|
|
}
|
|
}
|
|
|
|
return peerConnectionWrapper;
|
|
}
|
|
|
|
- (void)sendDataChannelMessageToAllOfType:(NSString *)type withPayload:(id)payload
|
|
{
|
|
[[WebRTCCommon shared] dispatch:^{
|
|
if ([self->_externalSignalingController hasMCU]) {
|
|
[self->_publisherPeerConnection 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)createPublisherPeerConnection
|
|
{
|
|
[[WebRTCCommon shared] assertQueue];
|
|
|
|
if (self->_publisherPeerConnection || (!self->_localAudioTrack && !self->_localVideoTrack)) {
|
|
NSLog(@"Not creating publisher peer connection. Already created or no local media.");
|
|
return;
|
|
}
|
|
|
|
NSLog(@"Creating publisher peer connection with sessionId: %@", [self signalingSessionId]);
|
|
|
|
NSArray *iceServers = [self->_signalingController getIceServers];
|
|
self->_publisherPeerConnection = [[NCPeerConnection alloc] initForPublisherWithSessionId:[self signalingSessionId] andICEServers:iceServers forAudioOnlyCall:YES];
|
|
self->_publisherPeerConnection.roomType = kRoomTypeVideo;
|
|
self->_publisherPeerConnection.delegate = self;
|
|
|
|
NSString *peerKey = [[self signalingSessionId] stringByAppendingString:kRoomTypeVideo];
|
|
[self->_connectionsDict setObject:self->_publisherPeerConnection forKey:peerKey];
|
|
|
|
if (self->_localAudioTrack) {
|
|
[self->_publisherPeerConnection.peerConnection addTrack:self->_localAudioTrack streamIds:@[kNCMediaStreamId]];
|
|
}
|
|
|
|
if (self->_localVideoTrack) {
|
|
[self->_publisherPeerConnection.peerConnection addTrack:self->_localVideoTrack streamIds:@[kNCMediaStreamId]];
|
|
}
|
|
|
|
[self->_publisherPeerConnection sendPublisherOffer];
|
|
}
|
|
|
|
- (void)sendNick
|
|
{
|
|
NSDictionary *payload = @{
|
|
@"userid":_account.userId,
|
|
@"name":_account.userDisplayName
|
|
};
|
|
[self sendDataChannelMessageToAllOfType:@"nickChanged" withPayload:payload];
|
|
}
|
|
|
|
- (void)startSendingNick
|
|
{
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[self->_sendNickTimer invalidate];
|
|
self->_sendNickTimer = nil;
|
|
self->_sendNickTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(sendNick) userInfo:nil repeats:YES];
|
|
});
|
|
}
|
|
|
|
- (void)stopSendingNick
|
|
{
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[self->_sendNickTimer invalidate];
|
|
self->_sendNickTimer = nil;
|
|
});
|
|
}
|
|
|
|
- (void)requestNewOffer:(NSTimer *)timer
|
|
{
|
|
[[WebRTCCommon shared] dispatch:^{
|
|
NSString *sessionId = [timer.userInfo objectForKey:@"sessionId"];
|
|
NSString *roomType = [timer.userInfo objectForKey:@"roomType"];
|
|
NSInteger timeout = [[timer.userInfo objectForKey:@"timeout"] integerValue];
|
|
|
|
if ([[NSDate date] timeIntervalSince1970] < timeout) {
|
|
[self->_externalSignalingController requestOfferForSessionId:sessionId andRoomType:roomType];
|
|
} else {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[timer invalidate];
|
|
});
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)checkIfPendingOffer:(NCSignalingMessage *)signalingMessage
|
|
{
|
|
if (signalingMessage.messageType == kNCSignalingMessageTypeOffer) {
|
|
NSString *peerKey = [signalingMessage.from stringByAppendingString:signalingMessage.roomType];
|
|
NSTimer *pendingRequestTimer = [_pendingOffersDict objectForKey:peerKey];
|
|
|
|
if (pendingRequestTimer) {
|
|
NSLog(@"Pending requested offer arrived. Removing timer.");
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[pendingRequestTimer invalidate];
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma mark - External Signaling Controller Delegate
|
|
|
|
- (void)externalSignalingController:(NCExternalSignalingController *)externalSignalingController didReceivedSignalingMessage:(NSDictionary *)signalingMessageDict
|
|
{
|
|
//NSLog(@"External signaling message received: %@", signalingMessageDict);
|
|
|
|
[[WebRTCCommon shared] dispatch:^{
|
|
NCSignalingMessage *signalingMessage = [NCSignalingMessage messageFromExternalSignalingJSONDictionary:signalingMessageDict];
|
|
[self checkIfPendingOffer:signalingMessage];
|
|
[self processSignalingMessage:signalingMessage];
|
|
}];
|
|
}
|
|
|
|
- (void)externalSignalingController:(NCExternalSignalingController *)externalSignalingController didReceivedParticipantListMessage:(NSDictionary *)participantListMessageDict
|
|
{
|
|
//NSLog(@"External participants message received: %@", participantListMessageDict);
|
|
|
|
[[WebRTCCommon shared] dispatch:^{
|
|
NSArray *usersInRoom = [participantListMessageDict objectForKey:@"users"];
|
|
|
|
// Update for "all" participants
|
|
if ([[participantListMessageDict objectForKey:@"all"] boolValue]) {
|
|
// Check if "incall" key exist
|
|
if ([[participantListMessageDict allKeys] containsObject:@"incall"]) {
|
|
// Clear usersInRoom array if incall=false
|
|
if (![[participantListMessageDict objectForKey:@"incall"] boolValue]) {
|
|
usersInRoom = @[];
|
|
}
|
|
}
|
|
}
|
|
|
|
[self processUsersInRoom: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.
|
|
|
|
[[WebRTCCommon shared] dispatch:^{
|
|
if (self->_preparedForRejoin && self->_joinedCallOnce) {
|
|
self->_preparedForRejoin = NO;
|
|
[self shouldRejoinCall];
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)externalSignalingControllerWillRejoinCall:(NCExternalSignalingController *)externalSignalingController
|
|
{
|
|
[[WebRTCCommon shared] dispatch:^{
|
|
[self willRejoinCall];
|
|
}];
|
|
}
|
|
|
|
- (void)externalSignalingController:(NCExternalSignalingController *)externalSignalingController shouldSwitchToCall:(NSString *)roomToken
|
|
{
|
|
[self willSwitchToCall:roomToken];
|
|
}
|
|
|
|
#pragma mark - Signaling Controller Delegate
|
|
|
|
- (void)signalingController:(NCSignalingController *)signalingController didReceiveSignalingMessage:(NSDictionary *)message
|
|
{
|
|
[[WebRTCCommon shared] dispatch:^{
|
|
NSString *messageType = [message objectForKey:@"type"];
|
|
|
|
if (self->_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) {
|
|
return;
|
|
}
|
|
|
|
[[WebRTCCommon shared] assertQueue];
|
|
|
|
switch (signalingMessage.messageType) {
|
|
case kNCSignalingMessageTypeOffer:
|
|
case kNCSignalingMessageTypeAnswer:
|
|
{
|
|
NCPeerConnection *peerConnectionWrapper = [self getOrCreatePeerConnectionWrapperForSessionId:signalingMessage.from ofType:signalingMessage.roomType];
|
|
NCSessionDescriptionMessage *sdpMessage = (NCSessionDescriptionMessage *)signalingMessage;
|
|
RTCSessionDescription *sessionDescription = sdpMessage.sessionDescription;
|
|
[peerConnectionWrapper setPeerName:sdpMessage.nick];
|
|
[peerConnectionWrapper setRemoteDescription:sessionDescription];
|
|
break;
|
|
}
|
|
case kNCSignalingMessageTypeCandidate:
|
|
{
|
|
NCPeerConnection *peerConnectionWrapper = [self getOrCreatePeerConnectionWrapperForSessionId: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];
|
|
if (peerConnectionWrapper) {
|
|
NSString *peerKey = [peerConnectionWrapper.peerId stringByAppendingString:kRoomTypeScreen];
|
|
NCPeerConnection *screenPeerConnection = [self->_connectionsDict objectForKey:peerKey];
|
|
if (screenPeerConnection) {
|
|
[screenPeerConnection close];
|
|
[self->_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 kNCSignalingMessageTypeMute:
|
|
case kNCSignalingMessageTypeUnmute:
|
|
{
|
|
NCPeerConnection *peerConnectionWrapper = [self getPeerConnectionWrapperForSessionId:signalingMessage.from ofType:signalingMessage.roomType];
|
|
if (peerConnectionWrapper) {
|
|
NSString *name = [signalingMessage.payload objectForKey:@"name"];
|
|
if ([name isEqualToString:@"audio"]) {
|
|
NSString *messageType = (signalingMessage.messageType == kNCSignalingMessageTypeMute) ? @"audioOff" : @"audioOn";
|
|
[peerConnectionWrapper setStatusForDataChannelMessageType:messageType withPayload:nil];
|
|
} else if ([name isEqualToString:@"video"]) {
|
|
NSString *messageType = (signalingMessage.messageType == kNCSignalingMessageTypeMute) ? @"videoOff" : @"videoOn";
|
|
[peerConnectionWrapper setStatusForDataChannelMessageType:messageType withPayload:nil];
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case kNCSignalingMessageTypeNickChanged:
|
|
{
|
|
NCPeerConnection *peerConnectionWrapper = [self getPeerConnectionWrapperForSessionId:signalingMessage.from ofType:signalingMessage.roomType];
|
|
if (peerConnectionWrapper) {
|
|
NSString *name = [signalingMessage.payload objectForKey:@"name"];
|
|
if (name.length > 0) {
|
|
[peerConnectionWrapper setStatusForDataChannelMessageType:@"nickChanged" withPayload:name];
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case kNCSignalingMessageTypeRaiseHand:
|
|
{
|
|
NCPeerConnection *peerConnectionWrapper = [self getPeerConnectionWrapperForSessionId:signalingMessage.from ofType:signalingMessage.roomType];
|
|
if (peerConnectionWrapper) {
|
|
BOOL raised = [[signalingMessage.payload objectForKey:@"state"] boolValue];
|
|
[peerConnectionWrapper setStatusForDataChannelMessageType:@"raiseHand" withPayload:@(raised)];
|
|
}
|
|
break;
|
|
}
|
|
case kNCSignalingMessageTypeRecording:
|
|
{
|
|
NCRecordingMessage *recordingMessage = (NCRecordingMessage *)signalingMessage;
|
|
self->_room.callRecording = recordingMessage.status;
|
|
[self.delegate callControllerDidChangeRecording:self];
|
|
|
|
break;
|
|
}
|
|
case kNCSignalingMessageTypeUnknown:
|
|
NSLog(@"Received an unknown signaling message: %@", signalingMessage);
|
|
break;
|
|
}
|
|
}
|
|
|
|
- (void)processUsersInRoom:(NSArray *)users
|
|
{
|
|
[[WebRTCCommon shared] assertQueue];
|
|
|
|
_usersInRoom = users;
|
|
|
|
NSInteger previousUserInCall = _userInCall;
|
|
NSMutableArray *newSessions = [self getInCallSessionsFromUsersInRoom:users];
|
|
|
|
if (_leavingCall) {
|
|
return;
|
|
}
|
|
|
|
// Detect if user should rejoin call (internal signaling)
|
|
if (!_userInCall && _shouldRejoinCallUsingInternalSignaling) {
|
|
_shouldRejoinCallUsingInternalSignaling = NO;
|
|
[self shouldRejoinCall];
|
|
}
|
|
|
|
if (!previousUserInCall) {
|
|
// Do nothing if app user is stil not in the call
|
|
if (!_userInCall) {
|
|
return;
|
|
}
|
|
|
|
// Create publisher peer connection
|
|
if ([_externalSignalingController hasMCU]) {
|
|
[self createPublisherPeerConnection];
|
|
}
|
|
}
|
|
|
|
NSMutableArray *oldSessions = [NSMutableArray arrayWithArray:_sessionsInCall];
|
|
|
|
//Save current sessions in call
|
|
_sessionsInCall = [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 (newSessions.count > 0) {
|
|
[self getPeersForCall];
|
|
}
|
|
|
|
if (_serverSupportsConversationPermissions) {
|
|
[self checkUserPermissionsChange];
|
|
}
|
|
|
|
// Create new peer connections for new sessions in call
|
|
for (NSString *sessionId in newSessions) {
|
|
NSString *peerKey = [sessionId stringByAppendingString:kRoomTypeVideo];
|
|
if (![_connectionsDict objectForKey:peerKey] && ![[self signalingSessionId] isEqualToString:sessionId]) {
|
|
// Always create a peer connection, so the peer is added to the call view.
|
|
// When using a MCU we request an offer, but in case there are no streams published, we won't get an offer.
|
|
// When using internal signaling if we and the other participant are not publishing any stream,
|
|
// we won't receive or send any offer.
|
|
NCPeerConnection *peerConnectionWrapper = [self getOrCreatePeerConnectionWrapperForSessionId:sessionId ofType:kRoomTypeVideo];
|
|
if ([_externalSignalingController hasMCU]) {
|
|
// Only request offer if user is sharing audio or video streams
|
|
if ([self userHasStreams:sessionId]) {
|
|
NSLog(@"Requesting offer to the MCU for session: %@", sessionId);
|
|
[_externalSignalingController requestOfferForSessionId:sessionId andRoomType:kRoomTypeVideo];
|
|
}
|
|
} else {
|
|
NSComparisonResult result = [sessionId compare:[self signalingSessionId]];
|
|
if (result == NSOrderedAscending) {
|
|
NSLog(@"Creating offer...");
|
|
[peerConnectionWrapper sendOffer];
|
|
} else {
|
|
NSLog(@"Waiting for offer...");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Close old peer connections for sessions that left the call
|
|
for (NSString *sessionId in leftSessions) {
|
|
// Hang up call if user sessionId is no longer in the call
|
|
// Could be because a moderator "ended the call for everyone"
|
|
if ([[self signalingSessionId] isEqualToString:sessionId]) {
|
|
NSLog(@"User sessionId is no longer in the call -> hang up call");
|
|
[self.delegate callControllerWantsToHangUpCall:self];
|
|
|
|
return;
|
|
}
|
|
|
|
// Remove all peer connections for that user
|
|
[self cleanAllPeerConnectionsForSessionId:sessionId];
|
|
}
|
|
}
|
|
|
|
- (BOOL)userHasStreams:(NSString *)sessionId
|
|
{
|
|
for (NSMutableDictionary *user in _usersInRoom) {
|
|
NSString *userSession = [user objectForKey:@"sessionId"];
|
|
if ([userSession isEqualToString:sessionId]) {
|
|
NSInteger userCallFlags = [[user objectForKey:@"inCall"] integerValue];
|
|
NSInteger requiredFlags = CallFlagWithAudio | CallFlagWithVideo;
|
|
return (userCallFlags & requiredFlags) != 0;
|
|
}
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
- (void)checkUserPermissionsChange
|
|
{
|
|
for (NSMutableDictionary *user in _usersInRoom) {
|
|
NSString *userSession = [user objectForKey:@"sessionId"];
|
|
id userPermissionValue = [user objectForKey:@"participantPermissions"];
|
|
if ([userSession isEqualToString:[self signalingSessionId]] && [userPermissionValue isKindOfClass:[NSNumber class]]) {
|
|
NSInteger userPermissions = [userPermissionValue integerValue];
|
|
NSInteger changedPermissions = userPermissions ^ _userPermissions;
|
|
if ((changedPermissions & NCPermissionCanPublishAudio) || (changedPermissions & NCPermissionCanPublishVideo)) {
|
|
_userPermissions = userPermissions;
|
|
[self.delegate callController:self userPermissionsChanged:_userPermissions];
|
|
[self forceReconnect];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (NSMutableArray *)getInCallSessionsFromUsersInRoom:(NSArray *)users
|
|
{
|
|
NSMutableArray *sessions = [[NSMutableArray alloc] init];
|
|
for (NSMutableDictionary *user in users) {
|
|
NSString *sessionId = [user objectForKey:@"sessionId"];
|
|
NSInteger inCall = [[user objectForKey:@"inCall"] integerValue];
|
|
// Set inCall flag for app user
|
|
if ([sessionId isEqualToString:[self signalingSessionId]]) {
|
|
_userInCall = inCall;
|
|
}
|
|
// Add session if inCall
|
|
if (inCall) {
|
|
[sessions addObject:sessionId];
|
|
}
|
|
}
|
|
//NSLog(@"InCall sessions: %@", sessions);
|
|
return sessions;
|
|
}
|
|
|
|
- (NSString *)getUserIdFromSessionId:(NSString *)sessionId
|
|
{
|
|
[[WebRTCCommon shared] assertQueue];
|
|
|
|
if ([_externalSignalingController isEnabled]) {
|
|
return [_externalSignalingController getUserIdFromSessionId:sessionId];
|
|
}
|
|
|
|
NSInteger callAPIVersion = [[NCAPIController sharedInstance] callAPIVersionForAccount:_account];
|
|
NSString *userId = nil;
|
|
for (NSMutableDictionary *user in _peersInCall) {
|
|
NSString *userSessionId = [user objectForKey:@"sessionId"];
|
|
if ([userSessionId isEqualToString:sessionId]) {
|
|
userId = [user objectForKey:@"userId"];
|
|
if (callAPIVersion >= APIv3) {
|
|
userId = [user objectForKey:@"actorId"];
|
|
}
|
|
}
|
|
}
|
|
return userId;
|
|
}
|
|
|
|
- (NSString *)getDisplayNameFromSessionId:(NSString *)sessionId
|
|
{
|
|
[[WebRTCCommon shared] assertQueue];
|
|
|
|
if ([_externalSignalingController isEnabled]) {
|
|
return [_externalSignalingController getDisplayNameFromSessionId:sessionId];
|
|
}
|
|
for (NSMutableDictionary *user in _peersInCall) {
|
|
NSString *userSessionId = [user objectForKey:@"sessionId"];
|
|
if ([userSessionId isEqualToString:sessionId]) {
|
|
return [user objectForKey:@"displayName"];
|
|
}
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
#pragma mark - NCPeerConnectionDelegate
|
|
// Delegates from NCPeerConnection are already dispatched to the webrtc worker queue
|
|
|
|
- (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
|
|
{
|
|
if (!peerConnection.isMCUPublisherPeer) {
|
|
[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];
|
|
NSNumber *timeout = [NSNumber numberWithInt:[[NSDate date] timeIntervalSince1970] + 60];
|
|
NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] init];
|
|
[userInfo setObject:sessionId forKey:@"sessionId"];
|
|
[userInfo setObject:roomType forKey:@"roomType"];
|
|
[userInfo setValue:timeout forKey:@"timeout"];
|
|
|
|
// Close failed peer connection
|
|
[self cleanPeerConnectionForSessionId:peerConnection.peerId ofType:peerConnection.roomType];
|
|
// 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];
|
|
|
|
NSString *peerKey = [peerConnection.peerId stringByAppendingString:peerConnection.roomType];
|
|
[_pendingOffersDict setObject:pendingOfferTimer forKey:peerKey];
|
|
}
|
|
}
|
|
|
|
if (!peerConnection.isMCUPublisherPeer) {
|
|
[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:[self signalingSessionId]
|
|
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:[self signalingSessionId]
|
|
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
|