1/* 2 * Copyright 2014 The WebRTC Project Authors. All rights reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11#import "ARDAppClient+Internal.h" 12 13#import <WebRTC/RTCAudioTrack.h> 14#import <WebRTC/RTCCameraVideoCapturer.h> 15#import <WebRTC/RTCConfiguration.h> 16#import <WebRTC/RTCDefaultVideoDecoderFactory.h> 17#import <WebRTC/RTCDefaultVideoEncoderFactory.h> 18#import <WebRTC/RTCFileLogger.h> 19#import <WebRTC/RTCFileVideoCapturer.h> 20#import <WebRTC/RTCIceServer.h> 21#import <WebRTC/RTCLogging.h> 22#import <WebRTC/RTCMediaConstraints.h> 23#import <WebRTC/RTCMediaStream.h> 24#import <WebRTC/RTCPeerConnectionFactory.h> 25#import <WebRTC/RTCRtpSender.h> 26#import <WebRTC/RTCRtpTransceiver.h> 27#import <WebRTC/RTCTracing.h> 28#import <WebRTC/RTCVideoSource.h> 29#import <WebRTC/RTCVideoTrack.h> 30 31#import "ARDAppEngineClient.h" 32#import "ARDExternalSampleCapturer.h" 33#import "ARDJoinResponse.h" 34#import "ARDMessageResponse.h" 35#import "ARDSettingsModel.h" 36#import "ARDSignalingMessage.h" 37#import "ARDTURNClient+Internal.h" 38#import "ARDUtilities.h" 39#import "ARDWebSocketChannel.h" 40#import "RTCIceCandidate+JSON.h" 41#import "RTCSessionDescription+JSON.h" 42 43static NSString * const kARDIceServerRequestUrl = @"https://appr.tc/params"; 44 45static NSString * const kARDAppClientErrorDomain = @"ARDAppClient"; 46static NSInteger const kARDAppClientErrorUnknown = -1; 47static NSInteger const kARDAppClientErrorRoomFull = -2; 48static NSInteger const kARDAppClientErrorCreateSDP = -3; 49static NSInteger const kARDAppClientErrorSetSDP = -4; 50static NSInteger const kARDAppClientErrorInvalidClient = -5; 51static NSInteger const kARDAppClientErrorInvalidRoom = -6; 52static NSString * const kARDMediaStreamId = @"ARDAMS"; 53static NSString * const kARDAudioTrackId = @"ARDAMSa0"; 54static NSString * const kARDVideoTrackId = @"ARDAMSv0"; 55static NSString * const kARDVideoTrackKind = @"video"; 56 57// TODO(tkchin): Add these as UI options. 58#if defined(WEBRTC_IOS) 59static BOOL const kARDAppClientEnableTracing = NO; 60static BOOL const kARDAppClientEnableRtcEventLog = YES; 61static int64_t const kARDAppClientAecDumpMaxSizeInBytes = 5e6; // 5 MB. 62static int64_t const kARDAppClientRtcEventLogMaxSizeInBytes = 5e6; // 5 MB. 63#endif 64static int const kKbpsMultiplier = 1000; 65 66// We need a proxy to NSTimer because it causes a strong retain cycle. When 67// using the proxy, |invalidate| must be called before it properly deallocs. 68@interface ARDTimerProxy : NSObject 69 70- (instancetype)initWithInterval:(NSTimeInterval)interval 71 repeats:(BOOL)repeats 72 timerHandler:(void (^)(void))timerHandler; 73- (void)invalidate; 74 75@end 76 77@implementation ARDTimerProxy { 78 NSTimer *_timer; 79 void (^_timerHandler)(void); 80} 81 82- (instancetype)initWithInterval:(NSTimeInterval)interval 83 repeats:(BOOL)repeats 84 timerHandler:(void (^)(void))timerHandler { 85 NSParameterAssert(timerHandler); 86 if (self = [super init]) { 87 _timerHandler = timerHandler; 88 _timer = [NSTimer scheduledTimerWithTimeInterval:interval 89 target:self 90 selector:@selector(timerDidFire:) 91 userInfo:nil 92 repeats:repeats]; 93 } 94 return self; 95} 96 97- (void)invalidate { 98 [_timer invalidate]; 99} 100 101- (void)timerDidFire:(NSTimer *)timer { 102 _timerHandler(); 103} 104 105@end 106 107@implementation ARDAppClient { 108 RTC_OBJC_TYPE(RTCFileLogger) * _fileLogger; 109 ARDTimerProxy *_statsTimer; 110 ARDSettingsModel *_settings; 111 RTC_OBJC_TYPE(RTCVideoTrack) * _localVideoTrack; 112} 113 114@synthesize shouldGetStats = _shouldGetStats; 115@synthesize state = _state; 116@synthesize delegate = _delegate; 117@synthesize roomServerClient = _roomServerClient; 118@synthesize channel = _channel; 119@synthesize loopbackChannel = _loopbackChannel; 120@synthesize turnClient = _turnClient; 121@synthesize peerConnection = _peerConnection; 122@synthesize factory = _factory; 123@synthesize messageQueue = _messageQueue; 124@synthesize isTurnComplete = _isTurnComplete; 125@synthesize hasReceivedSdp = _hasReceivedSdp; 126@synthesize roomId = _roomId; 127@synthesize clientId = _clientId; 128@synthesize isInitiator = _isInitiator; 129@synthesize iceServers = _iceServers; 130@synthesize webSocketURL = _websocketURL; 131@synthesize webSocketRestURL = _websocketRestURL; 132@synthesize defaultPeerConnectionConstraints = 133 _defaultPeerConnectionConstraints; 134@synthesize isLoopback = _isLoopback; 135@synthesize broadcast = _broadcast; 136 137- (instancetype)init { 138 return [self initWithDelegate:nil]; 139} 140 141- (instancetype)initWithDelegate:(id<ARDAppClientDelegate>)delegate { 142 if (self = [super init]) { 143 _roomServerClient = [[ARDAppEngineClient alloc] init]; 144 _delegate = delegate; 145 NSURL *turnRequestURL = [NSURL URLWithString:kARDIceServerRequestUrl]; 146 _turnClient = [[ARDTURNClient alloc] initWithURL:turnRequestURL]; 147 [self configure]; 148 } 149 return self; 150} 151 152// TODO(tkchin): Provide signaling channel factory interface so we can recreate 153// channel if we need to on network failure. Also, make this the default public 154// constructor. 155- (instancetype)initWithRoomServerClient:(id<ARDRoomServerClient>)rsClient 156 signalingChannel:(id<ARDSignalingChannel>)channel 157 turnClient:(id<ARDTURNClient>)turnClient 158 delegate:(id<ARDAppClientDelegate>)delegate { 159 NSParameterAssert(rsClient); 160 NSParameterAssert(channel); 161 NSParameterAssert(turnClient); 162 if (self = [super init]) { 163 _roomServerClient = rsClient; 164 _channel = channel; 165 _turnClient = turnClient; 166 _delegate = delegate; 167 [self configure]; 168 } 169 return self; 170} 171 172- (void)configure { 173 _messageQueue = [NSMutableArray array]; 174 _iceServers = [NSMutableArray array]; 175 _fileLogger = [[RTC_OBJC_TYPE(RTCFileLogger) alloc] init]; 176 [_fileLogger start]; 177} 178 179- (void)dealloc { 180 self.shouldGetStats = NO; 181 [self disconnect]; 182} 183 184- (void)setShouldGetStats:(BOOL)shouldGetStats { 185 if (_shouldGetStats == shouldGetStats) { 186 return; 187 } 188 if (shouldGetStats) { 189 __weak ARDAppClient *weakSelf = self; 190 _statsTimer = [[ARDTimerProxy alloc] initWithInterval:1 191 repeats:YES 192 timerHandler:^{ 193 ARDAppClient *strongSelf = weakSelf; 194 [strongSelf.peerConnection statsForTrack:nil 195 statsOutputLevel:RTCStatsOutputLevelDebug 196 completionHandler:^(NSArray *stats) { 197 dispatch_async(dispatch_get_main_queue(), ^{ 198 ARDAppClient *strongSelf = weakSelf; 199 [strongSelf.delegate appClient:strongSelf didGetStats:stats]; 200 }); 201 }]; 202 }]; 203 } else { 204 [_statsTimer invalidate]; 205 _statsTimer = nil; 206 } 207 _shouldGetStats = shouldGetStats; 208} 209 210- (void)setState:(ARDAppClientState)state { 211 if (_state == state) { 212 return; 213 } 214 _state = state; 215 [_delegate appClient:self didChangeState:_state]; 216} 217 218- (void)connectToRoomWithId:(NSString *)roomId 219 settings:(ARDSettingsModel *)settings 220 isLoopback:(BOOL)isLoopback { 221 NSParameterAssert(roomId.length); 222 NSParameterAssert(_state == kARDAppClientStateDisconnected); 223 _settings = settings; 224 _isLoopback = isLoopback; 225 self.state = kARDAppClientStateConnecting; 226 227 RTC_OBJC_TYPE(RTCDefaultVideoDecoderFactory) *decoderFactory = 228 [[RTC_OBJC_TYPE(RTCDefaultVideoDecoderFactory) alloc] init]; 229 RTC_OBJC_TYPE(RTCDefaultVideoEncoderFactory) *encoderFactory = 230 [[RTC_OBJC_TYPE(RTCDefaultVideoEncoderFactory) alloc] init]; 231 encoderFactory.preferredCodec = [settings currentVideoCodecSettingFromStore]; 232 _factory = 233 [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] initWithEncoderFactory:encoderFactory 234 decoderFactory:decoderFactory]; 235 236#if defined(WEBRTC_IOS) 237 if (kARDAppClientEnableTracing) { 238 NSString *filePath = [self documentsFilePathForFileName:@"webrtc-trace.txt"]; 239 RTCStartInternalCapture(filePath); 240 } 241#endif 242 243 // Request TURN. 244 __weak ARDAppClient *weakSelf = self; 245 [_turnClient requestServersWithCompletionHandler:^(NSArray *turnServers, 246 NSError *error) { 247 if (error) { 248 RTCLogError(@"Error retrieving TURN servers: %@", error.localizedDescription); 249 } 250 ARDAppClient *strongSelf = weakSelf; 251 [strongSelf.iceServers addObjectsFromArray:turnServers]; 252 strongSelf.isTurnComplete = YES; 253 [strongSelf startSignalingIfReady]; 254 }]; 255 256 // Join room on room server. 257 [_roomServerClient joinRoomWithRoomId:roomId 258 isLoopback:isLoopback 259 completionHandler:^(ARDJoinResponse *response, NSError *error) { 260 ARDAppClient *strongSelf = weakSelf; 261 if (error) { 262 [strongSelf.delegate appClient:strongSelf didError:error]; 263 return; 264 } 265 NSError *joinError = 266 [[strongSelf class] errorForJoinResultType:response.result]; 267 if (joinError) { 268 RTCLogError(@"Failed to join room:%@ on room server.", roomId); 269 [strongSelf disconnect]; 270 [strongSelf.delegate appClient:strongSelf didError:joinError]; 271 return; 272 } 273 RTCLog(@"Joined room:%@ on room server.", roomId); 274 strongSelf.roomId = response.roomId; 275 strongSelf.clientId = response.clientId; 276 strongSelf.isInitiator = response.isInitiator; 277 for (ARDSignalingMessage *message in response.messages) { 278 if (message.type == kARDSignalingMessageTypeOffer || 279 message.type == kARDSignalingMessageTypeAnswer) { 280 strongSelf.hasReceivedSdp = YES; 281 [strongSelf.messageQueue insertObject:message atIndex:0]; 282 } else { 283 [strongSelf.messageQueue addObject:message]; 284 } 285 } 286 strongSelf.webSocketURL = response.webSocketURL; 287 strongSelf.webSocketRestURL = response.webSocketRestURL; 288 [strongSelf registerWithColliderIfReady]; 289 [strongSelf startSignalingIfReady]; 290 }]; 291} 292 293- (void)disconnect { 294 if (_state == kARDAppClientStateDisconnected) { 295 return; 296 } 297 if (self.hasJoinedRoomServerRoom) { 298 [_roomServerClient leaveRoomWithRoomId:_roomId 299 clientId:_clientId 300 completionHandler:nil]; 301 } 302 if (_channel) { 303 if (_channel.state == kARDSignalingChannelStateRegistered) { 304 // Tell the other client we're hanging up. 305 ARDByeMessage *byeMessage = [[ARDByeMessage alloc] init]; 306 [_channel sendMessage:byeMessage]; 307 } 308 // Disconnect from collider. 309 _channel = nil; 310 } 311 _clientId = nil; 312 _roomId = nil; 313 _isInitiator = NO; 314 _hasReceivedSdp = NO; 315 _messageQueue = [NSMutableArray array]; 316 _localVideoTrack = nil; 317#if defined(WEBRTC_IOS) 318 [_factory stopAecDump]; 319 [_peerConnection stopRtcEventLog]; 320#endif 321 [_peerConnection close]; 322 _peerConnection = nil; 323 self.state = kARDAppClientStateDisconnected; 324#if defined(WEBRTC_IOS) 325 if (kARDAppClientEnableTracing) { 326 RTCStopInternalCapture(); 327 } 328#endif 329} 330 331#pragma mark - ARDSignalingChannelDelegate 332 333- (void)channel:(id<ARDSignalingChannel>)channel 334 didReceiveMessage:(ARDSignalingMessage *)message { 335 switch (message.type) { 336 case kARDSignalingMessageTypeOffer: 337 case kARDSignalingMessageTypeAnswer: 338 // Offers and answers must be processed before any other message, so we 339 // place them at the front of the queue. 340 _hasReceivedSdp = YES; 341 [_messageQueue insertObject:message atIndex:0]; 342 break; 343 case kARDSignalingMessageTypeCandidate: 344 case kARDSignalingMessageTypeCandidateRemoval: 345 [_messageQueue addObject:message]; 346 break; 347 case kARDSignalingMessageTypeBye: 348 // Disconnects can be processed immediately. 349 [self processSignalingMessage:message]; 350 return; 351 } 352 [self drainMessageQueueIfReady]; 353} 354 355- (void)channel:(id<ARDSignalingChannel>)channel 356 didChangeState:(ARDSignalingChannelState)state { 357 switch (state) { 358 case kARDSignalingChannelStateOpen: 359 break; 360 case kARDSignalingChannelStateRegistered: 361 break; 362 case kARDSignalingChannelStateClosed: 363 case kARDSignalingChannelStateError: 364 // TODO(tkchin): reconnection scenarios. Right now we just disconnect 365 // completely if the websocket connection fails. 366 [self disconnect]; 367 break; 368 } 369} 370 371#pragma mark - RTC_OBJC_TYPE(RTCPeerConnectionDelegate) 372// Callbacks for this delegate occur on non-main thread and need to be 373// dispatched back to main queue as needed. 374 375- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection 376 didChangeSignalingState:(RTCSignalingState)stateChanged { 377 RTCLog(@"Signaling state changed: %ld", (long)stateChanged); 378} 379 380- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection 381 didAddStream:(RTC_OBJC_TYPE(RTCMediaStream) *)stream { 382 RTCLog(@"Stream with %lu video tracks and %lu audio tracks was added.", 383 (unsigned long)stream.videoTracks.count, 384 (unsigned long)stream.audioTracks.count); 385} 386 387- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection 388 didStartReceivingOnTransceiver:(RTC_OBJC_TYPE(RTCRtpTransceiver) *)transceiver { 389 RTC_OBJC_TYPE(RTCMediaStreamTrack) *track = transceiver.receiver.track; 390 RTCLog(@"Now receiving %@ on track %@.", track.kind, track.trackId); 391} 392 393- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection 394 didRemoveStream:(RTC_OBJC_TYPE(RTCMediaStream) *)stream { 395 RTCLog(@"Stream was removed."); 396} 397 398- (void)peerConnectionShouldNegotiate:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection { 399 RTCLog(@"WARNING: Renegotiation needed but unimplemented."); 400} 401 402- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection 403 didChangeIceConnectionState:(RTCIceConnectionState)newState { 404 RTCLog(@"ICE state changed: %ld", (long)newState); 405 dispatch_async(dispatch_get_main_queue(), ^{ 406 [self.delegate appClient:self didChangeConnectionState:newState]; 407 }); 408} 409 410- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection 411 didChangeConnectionState:(RTCPeerConnectionState)newState { 412 RTCLog(@"ICE+DTLS state changed: %ld", (long)newState); 413} 414 415- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection 416 didChangeIceGatheringState:(RTCIceGatheringState)newState { 417 RTCLog(@"ICE gathering state changed: %ld", (long)newState); 418} 419 420- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection 421 didGenerateIceCandidate:(RTC_OBJC_TYPE(RTCIceCandidate) *)candidate { 422 dispatch_async(dispatch_get_main_queue(), ^{ 423 ARDICECandidateMessage *message = 424 [[ARDICECandidateMessage alloc] initWithCandidate:candidate]; 425 [self sendSignalingMessage:message]; 426 }); 427} 428 429- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection 430 didRemoveIceCandidates:(NSArray<RTC_OBJC_TYPE(RTCIceCandidate) *> *)candidates { 431 dispatch_async(dispatch_get_main_queue(), ^{ 432 ARDICECandidateRemovalMessage *message = 433 [[ARDICECandidateRemovalMessage alloc] 434 initWithRemovedCandidates:candidates]; 435 [self sendSignalingMessage:message]; 436 }); 437} 438 439- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection 440 didChangeLocalCandidate:(RTC_OBJC_TYPE(RTCIceCandidate) *)local 441 didChangeRemoteCandidate:(RTC_OBJC_TYPE(RTCIceCandidate) *)remote 442 lastReceivedMs:(int)lastDataReceivedMs 443 didHaveReason:(NSString *)reason { 444 RTCLog(@"ICE candidate pair changed because: %@", reason); 445} 446 447- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection 448 didOpenDataChannel:(RTC_OBJC_TYPE(RTCDataChannel) *)dataChannel { 449} 450 451#pragma mark - RTCSessionDescriptionDelegate 452// Callbacks for this delegate occur on non-main thread and need to be 453// dispatched back to main queue as needed. 454 455- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection 456 didCreateSessionDescription:(RTC_OBJC_TYPE(RTCSessionDescription) *)sdp 457 error:(NSError *)error { 458 dispatch_async(dispatch_get_main_queue(), ^{ 459 if (error) { 460 RTCLogError(@"Failed to create session description. Error: %@", error); 461 [self disconnect]; 462 NSDictionary *userInfo = @{ 463 NSLocalizedDescriptionKey: @"Failed to create session description.", 464 }; 465 NSError *sdpError = 466 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain 467 code:kARDAppClientErrorCreateSDP 468 userInfo:userInfo]; 469 [self.delegate appClient:self didError:sdpError]; 470 return; 471 } 472 __weak ARDAppClient *weakSelf = self; 473 [self.peerConnection setLocalDescription:sdp 474 completionHandler:^(NSError *error) { 475 ARDAppClient *strongSelf = weakSelf; 476 [strongSelf peerConnection:strongSelf.peerConnection 477 didSetSessionDescriptionWithError:error]; 478 }]; 479 ARDSessionDescriptionMessage *message = 480 [[ARDSessionDescriptionMessage alloc] initWithDescription:sdp]; 481 [self sendSignalingMessage:message]; 482 [self setMaxBitrateForPeerConnectionVideoSender]; 483 }); 484} 485 486- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection 487 didSetSessionDescriptionWithError:(NSError *)error { 488 dispatch_async(dispatch_get_main_queue(), ^{ 489 if (error) { 490 RTCLogError(@"Failed to set session description. Error: %@", error); 491 [self disconnect]; 492 NSDictionary *userInfo = @{ 493 NSLocalizedDescriptionKey: @"Failed to set session description.", 494 }; 495 NSError *sdpError = 496 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain 497 code:kARDAppClientErrorSetSDP 498 userInfo:userInfo]; 499 [self.delegate appClient:self didError:sdpError]; 500 return; 501 } 502 // If we're answering and we've just set the remote offer we need to create 503 // an answer and set the local description. 504 if (!self.isInitiator && !self.peerConnection.localDescription) { 505 RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = [self defaultAnswerConstraints]; 506 __weak ARDAppClient *weakSelf = self; 507 [self.peerConnection 508 answerForConstraints:constraints 509 completionHandler:^(RTC_OBJC_TYPE(RTCSessionDescription) * sdp, NSError * error) { 510 ARDAppClient *strongSelf = weakSelf; 511 [strongSelf peerConnection:strongSelf.peerConnection 512 didCreateSessionDescription:sdp 513 error:error]; 514 }]; 515 } 516 }); 517} 518 519#pragma mark - Private 520 521#if defined(WEBRTC_IOS) 522 523- (NSString *)documentsFilePathForFileName:(NSString *)fileName { 524 NSParameterAssert(fileName.length); 525 NSArray *paths = NSSearchPathForDirectoriesInDomains( 526 NSDocumentDirectory, NSUserDomainMask, YES); 527 NSString *documentsDirPath = paths.firstObject; 528 NSString *filePath = 529 [documentsDirPath stringByAppendingPathComponent:fileName]; 530 return filePath; 531} 532 533#endif 534 535- (BOOL)hasJoinedRoomServerRoom { 536 return _clientId.length; 537} 538 539// Begins the peer connection connection process if we have both joined a room 540// on the room server and tried to obtain a TURN server. Otherwise does nothing. 541// A peer connection object will be created with a stream that contains local 542// audio and video capture. If this client is the caller, an offer is created as 543// well, otherwise the client will wait for an offer to arrive. 544- (void)startSignalingIfReady { 545 if (!_isTurnComplete || !self.hasJoinedRoomServerRoom) { 546 return; 547 } 548 self.state = kARDAppClientStateConnected; 549 550 // Create peer connection. 551 RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = [self defaultPeerConnectionConstraints]; 552 RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init]; 553 RTC_OBJC_TYPE(RTCCertificate) *pcert = [RTC_OBJC_TYPE(RTCCertificate) 554 generateCertificateWithParams:@{@"expires" : @100000, @"name" : @"RSASSA-PKCS1-v1_5"}]; 555 config.iceServers = _iceServers; 556 config.sdpSemantics = RTCSdpSemanticsUnifiedPlan; 557 config.certificate = pcert; 558 559 _peerConnection = [_factory peerConnectionWithConfiguration:config 560 constraints:constraints 561 delegate:self]; 562 // Create AV senders. 563 [self createMediaSenders]; 564 if (_isInitiator) { 565 // Send offer. 566 __weak ARDAppClient *weakSelf = self; 567 [_peerConnection 568 offerForConstraints:[self defaultOfferConstraints] 569 completionHandler:^(RTC_OBJC_TYPE(RTCSessionDescription) * sdp, NSError * error) { 570 ARDAppClient *strongSelf = weakSelf; 571 [strongSelf peerConnection:strongSelf.peerConnection 572 didCreateSessionDescription:sdp 573 error:error]; 574 }]; 575 } else { 576 // Check if we've received an offer. 577 [self drainMessageQueueIfReady]; 578 } 579#if defined(WEBRTC_IOS) 580 // Start event log. 581 if (kARDAppClientEnableRtcEventLog) { 582 NSString *filePath = [self documentsFilePathForFileName:@"webrtc-rtceventlog"]; 583 if (![_peerConnection startRtcEventLogWithFilePath:filePath 584 maxSizeInBytes:kARDAppClientRtcEventLogMaxSizeInBytes]) { 585 RTCLogError(@"Failed to start event logging."); 586 } 587 } 588 589 // Start aecdump diagnostic recording. 590 if ([_settings currentCreateAecDumpSettingFromStore]) { 591 NSString *filePath = [self documentsFilePathForFileName:@"webrtc-audio.aecdump"]; 592 if (![_factory startAecDumpWithFilePath:filePath 593 maxSizeInBytes:kARDAppClientAecDumpMaxSizeInBytes]) { 594 RTCLogError(@"Failed to start aec dump."); 595 } 596 } 597#endif 598} 599 600// Processes the messages that we've received from the room server and the 601// signaling channel. The offer or answer message must be processed before other 602// signaling messages, however they can arrive out of order. Hence, this method 603// only processes pending messages if there is a peer connection object and 604// if we have received either an offer or answer. 605- (void)drainMessageQueueIfReady { 606 if (!_peerConnection || !_hasReceivedSdp) { 607 return; 608 } 609 for (ARDSignalingMessage *message in _messageQueue) { 610 [self processSignalingMessage:message]; 611 } 612 [_messageQueue removeAllObjects]; 613} 614 615// Processes the given signaling message based on its type. 616- (void)processSignalingMessage:(ARDSignalingMessage *)message { 617 NSParameterAssert(_peerConnection || 618 message.type == kARDSignalingMessageTypeBye); 619 switch (message.type) { 620 case kARDSignalingMessageTypeOffer: 621 case kARDSignalingMessageTypeAnswer: { 622 ARDSessionDescriptionMessage *sdpMessage = 623 (ARDSessionDescriptionMessage *)message; 624 RTC_OBJC_TYPE(RTCSessionDescription) *description = sdpMessage.sessionDescription; 625 __weak ARDAppClient *weakSelf = self; 626 [_peerConnection setRemoteDescription:description 627 completionHandler:^(NSError *error) { 628 ARDAppClient *strongSelf = weakSelf; 629 [strongSelf peerConnection:strongSelf.peerConnection 630 didSetSessionDescriptionWithError:error]; 631 }]; 632 break; 633 } 634 case kARDSignalingMessageTypeCandidate: { 635 ARDICECandidateMessage *candidateMessage = 636 (ARDICECandidateMessage *)message; 637 [_peerConnection addIceCandidate:candidateMessage.candidate]; 638 break; 639 } 640 case kARDSignalingMessageTypeCandidateRemoval: { 641 ARDICECandidateRemovalMessage *candidateMessage = 642 (ARDICECandidateRemovalMessage *)message; 643 [_peerConnection removeIceCandidates:candidateMessage.candidates]; 644 break; 645 } 646 case kARDSignalingMessageTypeBye: 647 // Other client disconnected. 648 // TODO(tkchin): support waiting in room for next client. For now just 649 // disconnect. 650 [self disconnect]; 651 break; 652 } 653} 654 655// Sends a signaling message to the other client. The caller will send messages 656// through the room server, whereas the callee will send messages over the 657// signaling channel. 658- (void)sendSignalingMessage:(ARDSignalingMessage *)message { 659 if (_isInitiator) { 660 __weak ARDAppClient *weakSelf = self; 661 [_roomServerClient sendMessage:message 662 forRoomId:_roomId 663 clientId:_clientId 664 completionHandler:^(ARDMessageResponse *response, 665 NSError *error) { 666 ARDAppClient *strongSelf = weakSelf; 667 if (error) { 668 [strongSelf.delegate appClient:strongSelf didError:error]; 669 return; 670 } 671 NSError *messageError = 672 [[strongSelf class] errorForMessageResultType:response.result]; 673 if (messageError) { 674 [strongSelf.delegate appClient:strongSelf didError:messageError]; 675 return; 676 } 677 }]; 678 } else { 679 [_channel sendMessage:message]; 680 } 681} 682 683- (void)setMaxBitrateForPeerConnectionVideoSender { 684 for (RTC_OBJC_TYPE(RTCRtpSender) * sender in _peerConnection.senders) { 685 if (sender.track != nil) { 686 if ([sender.track.kind isEqualToString:kARDVideoTrackKind]) { 687 [self setMaxBitrate:[_settings currentMaxBitrateSettingFromStore] forVideoSender:sender]; 688 } 689 } 690 } 691} 692 693- (void)setMaxBitrate:(NSNumber *)maxBitrate forVideoSender:(RTC_OBJC_TYPE(RTCRtpSender) *)sender { 694 if (maxBitrate.intValue <= 0) { 695 return; 696 } 697 698 RTC_OBJC_TYPE(RTCRtpParameters) *parametersToModify = sender.parameters; 699 for (RTC_OBJC_TYPE(RTCRtpEncodingParameters) * encoding in parametersToModify.encodings) { 700 encoding.maxBitrateBps = @(maxBitrate.intValue * kKbpsMultiplier); 701 } 702 [sender setParameters:parametersToModify]; 703} 704 705- (RTC_OBJC_TYPE(RTCRtpTransceiver) *)videoTransceiver { 706 for (RTC_OBJC_TYPE(RTCRtpTransceiver) * transceiver in _peerConnection.transceivers) { 707 if (transceiver.mediaType == RTCRtpMediaTypeVideo) { 708 return transceiver; 709 } 710 } 711 return nil; 712} 713 714- (void)createMediaSenders { 715 RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = [self defaultMediaAudioConstraints]; 716 RTC_OBJC_TYPE(RTCAudioSource) *source = [_factory audioSourceWithConstraints:constraints]; 717 RTC_OBJC_TYPE(RTCAudioTrack) *track = [_factory audioTrackWithSource:source 718 trackId:kARDAudioTrackId]; 719 [_peerConnection addTrack:track streamIds:@[ kARDMediaStreamId ]]; 720 _localVideoTrack = [self createLocalVideoTrack]; 721 if (_localVideoTrack) { 722 [_peerConnection addTrack:_localVideoTrack streamIds:@[ kARDMediaStreamId ]]; 723 [_delegate appClient:self didReceiveLocalVideoTrack:_localVideoTrack]; 724 // We can set up rendering for the remote track right away since the transceiver already has an 725 // RTC_OBJC_TYPE(RTCRtpReceiver) with a track. The track will automatically get unmuted and 726 // produce frames once RTP is received. 727 RTC_OBJC_TYPE(RTCVideoTrack) *track = 728 (RTC_OBJC_TYPE(RTCVideoTrack) *)([self videoTransceiver].receiver.track); 729 [_delegate appClient:self didReceiveRemoteVideoTrack:track]; 730 } 731} 732 733- (RTC_OBJC_TYPE(RTCVideoTrack) *)createLocalVideoTrack { 734 if ([_settings currentAudioOnlySettingFromStore]) { 735 return nil; 736 } 737 738 RTC_OBJC_TYPE(RTCVideoSource) *source = [_factory videoSource]; 739 740#if !TARGET_IPHONE_SIMULATOR 741 if (self.isBroadcast) { 742 ARDExternalSampleCapturer *capturer = 743 [[ARDExternalSampleCapturer alloc] initWithDelegate:source]; 744 [_delegate appClient:self didCreateLocalExternalSampleCapturer:capturer]; 745 } else { 746 RTC_OBJC_TYPE(RTCCameraVideoCapturer) *capturer = 747 [[RTC_OBJC_TYPE(RTCCameraVideoCapturer) alloc] initWithDelegate:source]; 748 [_delegate appClient:self didCreateLocalCapturer:capturer]; 749 } 750#else 751#if defined(__IPHONE_11_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0) 752 if (@available(iOS 10, *)) { 753 RTC_OBJC_TYPE(RTCFileVideoCapturer) *fileCapturer = 754 [[RTC_OBJC_TYPE(RTCFileVideoCapturer) alloc] initWithDelegate:source]; 755 [_delegate appClient:self didCreateLocalFileCapturer:fileCapturer]; 756 } 757#endif 758#endif 759 760 return [_factory videoTrackWithSource:source trackId:kARDVideoTrackId]; 761} 762 763#pragma mark - Collider methods 764 765- (void)registerWithColliderIfReady { 766 if (!self.hasJoinedRoomServerRoom) { 767 return; 768 } 769 // Open WebSocket connection. 770 if (!_channel) { 771 _channel = 772 [[ARDWebSocketChannel alloc] initWithURL:_websocketURL 773 restURL:_websocketRestURL 774 delegate:self]; 775 if (_isLoopback) { 776 _loopbackChannel = 777 [[ARDLoopbackWebSocketChannel alloc] initWithURL:_websocketURL 778 restURL:_websocketRestURL]; 779 } 780 } 781 [_channel registerForRoomId:_roomId clientId:_clientId]; 782 if (_isLoopback) { 783 [_loopbackChannel registerForRoomId:_roomId clientId:@"LOOPBACK_CLIENT_ID"]; 784 } 785} 786 787#pragma mark - Defaults 788 789- (RTC_OBJC_TYPE(RTCMediaConstraints) *)defaultMediaAudioConstraints { 790 NSDictionary *mandatoryConstraints = @{}; 791 RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = 792 [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:mandatoryConstraints 793 optionalConstraints:nil]; 794 return constraints; 795} 796 797- (RTC_OBJC_TYPE(RTCMediaConstraints) *)defaultAnswerConstraints { 798 return [self defaultOfferConstraints]; 799} 800 801- (RTC_OBJC_TYPE(RTCMediaConstraints) *)defaultOfferConstraints { 802 NSDictionary *mandatoryConstraints = @{ 803 @"OfferToReceiveAudio" : @"true", 804 @"OfferToReceiveVideo" : @"true" 805 }; 806 RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = 807 [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:mandatoryConstraints 808 optionalConstraints:nil]; 809 return constraints; 810} 811 812- (RTC_OBJC_TYPE(RTCMediaConstraints) *)defaultPeerConnectionConstraints { 813 if (_defaultPeerConnectionConstraints) { 814 return _defaultPeerConnectionConstraints; 815 } 816 NSString *value = _isLoopback ? @"false" : @"true"; 817 NSDictionary *optionalConstraints = @{ @"DtlsSrtpKeyAgreement" : value }; 818 RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = 819 [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:nil 820 optionalConstraints:optionalConstraints]; 821 return constraints; 822} 823 824#pragma mark - Errors 825 826+ (NSError *)errorForJoinResultType:(ARDJoinResultType)resultType { 827 NSError *error = nil; 828 switch (resultType) { 829 case kARDJoinResultTypeSuccess: 830 break; 831 case kARDJoinResultTypeUnknown: { 832 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain 833 code:kARDAppClientErrorUnknown 834 userInfo:@{ 835 NSLocalizedDescriptionKey: @"Unknown error.", 836 }]; 837 break; 838 } 839 case kARDJoinResultTypeFull: { 840 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain 841 code:kARDAppClientErrorRoomFull 842 userInfo:@{ 843 NSLocalizedDescriptionKey: @"Room is full.", 844 }]; 845 break; 846 } 847 } 848 return error; 849} 850 851+ (NSError *)errorForMessageResultType:(ARDMessageResultType)resultType { 852 NSError *error = nil; 853 switch (resultType) { 854 case kARDMessageResultTypeSuccess: 855 break; 856 case kARDMessageResultTypeUnknown: 857 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain 858 code:kARDAppClientErrorUnknown 859 userInfo:@{ 860 NSLocalizedDescriptionKey: @"Unknown error.", 861 }]; 862 break; 863 case kARDMessageResultTypeInvalidClient: 864 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain 865 code:kARDAppClientErrorInvalidClient 866 userInfo:@{ 867 NSLocalizedDescriptionKey: @"Invalid client.", 868 }]; 869 break; 870 case kARDMessageResultTypeInvalidRoom: 871 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain 872 code:kARDAppClientErrorInvalidRoom 873 userInfo:@{ 874 NSLocalizedDescriptionKey: @"Invalid room.", 875 }]; 876 break; 877 } 878 return error; 879} 880 881@end 882