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#if defined(WEBRTC_IOS) 14#import "RTCAVFoundationVideoSource.h" 15#endif 16#import "RTCFileLogger.h" 17#import "RTCICEServer.h" 18#import "RTCLogging.h" 19#import "RTCMediaConstraints.h" 20#import "RTCMediaStream.h" 21#import "RTCPair.h" 22#import "RTCPeerConnectionInterface.h" 23#import "RTCVideoCapturer.h" 24 25#import "ARDAppEngineClient.h" 26#import "ARDCEODTURNClient.h" 27#import "ARDJoinResponse.h" 28#import "ARDMessageResponse.h" 29#import "ARDSDPUtils.h" 30#import "ARDSignalingMessage.h" 31#import "ARDUtilities.h" 32#import "ARDWebSocketChannel.h" 33#import "RTCICECandidate+JSON.h" 34#import "RTCSessionDescription+JSON.h" 35 36static NSString * const kARDDefaultSTUNServerUrl = 37 @"stun:stun.l.google.com:19302"; 38// TODO(tkchin): figure out a better username for CEOD statistics. 39static NSString * const kARDTurnRequestUrl = 40 @"https://computeengineondemand.appspot.com" 41 @"/turn?username=iapprtc&key=4080218913"; 42 43static NSString * const kARDAppClientErrorDomain = @"ARDAppClient"; 44static NSInteger const kARDAppClientErrorUnknown = -1; 45static NSInteger const kARDAppClientErrorRoomFull = -2; 46static NSInteger const kARDAppClientErrorCreateSDP = -3; 47static NSInteger const kARDAppClientErrorSetSDP = -4; 48static NSInteger const kARDAppClientErrorInvalidClient = -5; 49static NSInteger const kARDAppClientErrorInvalidRoom = -6; 50 51// We need a proxy to NSTimer because it causes a strong retain cycle. When 52// using the proxy, |invalidate| must be called before it properly deallocs. 53@interface ARDTimerProxy : NSObject 54 55- (instancetype)initWithInterval:(NSTimeInterval)interval 56 repeats:(BOOL)repeats 57 timerHandler:(void (^)(void))timerHandler; 58- (void)invalidate; 59 60@end 61 62@implementation ARDTimerProxy { 63 NSTimer *_timer; 64 void (^_timerHandler)(void); 65} 66 67- (instancetype)initWithInterval:(NSTimeInterval)interval 68 repeats:(BOOL)repeats 69 timerHandler:(void (^)(void))timerHandler { 70 NSParameterAssert(timerHandler); 71 if (self = [super init]) { 72 _timerHandler = timerHandler; 73 _timer = [NSTimer scheduledTimerWithTimeInterval:interval 74 target:self 75 selector:@selector(timerDidFire:) 76 userInfo:nil 77 repeats:repeats]; 78 } 79 return self; 80} 81 82- (void)invalidate { 83 [_timer invalidate]; 84} 85 86- (void)timerDidFire:(NSTimer *)timer { 87 _timerHandler(); 88} 89 90@end 91 92@implementation ARDAppClient { 93 RTCFileLogger *_fileLogger; 94 ARDTimerProxy *_statsTimer; 95} 96 97@synthesize shouldGetStats = _shouldGetStats; 98@synthesize state = _state; 99@synthesize delegate = _delegate; 100@synthesize roomServerClient = _roomServerClient; 101@synthesize channel = _channel; 102@synthesize loopbackChannel = _loopbackChannel; 103@synthesize turnClient = _turnClient; 104@synthesize peerConnection = _peerConnection; 105@synthesize factory = _factory; 106@synthesize messageQueue = _messageQueue; 107@synthesize isTurnComplete = _isTurnComplete; 108@synthesize hasReceivedSdp = _hasReceivedSdp; 109@synthesize roomId = _roomId; 110@synthesize clientId = _clientId; 111@synthesize isInitiator = _isInitiator; 112@synthesize iceServers = _iceServers; 113@synthesize webSocketURL = _websocketURL; 114@synthesize webSocketRestURL = _websocketRestURL; 115@synthesize defaultPeerConnectionConstraints = 116 _defaultPeerConnectionConstraints; 117@synthesize isLoopback = _isLoopback; 118@synthesize isAudioOnly = _isAudioOnly; 119 120- (instancetype)init { 121 if (self = [super init]) { 122 _roomServerClient = [[ARDAppEngineClient alloc] init]; 123 NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl]; 124 _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL]; 125 [self configure]; 126 } 127 return self; 128} 129 130- (instancetype)initWithDelegate:(id<ARDAppClientDelegate>)delegate { 131 if (self = [super init]) { 132 _roomServerClient = [[ARDAppEngineClient alloc] init]; 133 _delegate = delegate; 134 NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl]; 135 _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL]; 136 [self configure]; 137 } 138 return self; 139} 140 141// TODO(tkchin): Provide signaling channel factory interface so we can recreate 142// channel if we need to on network failure. Also, make this the default public 143// constructor. 144- (instancetype)initWithRoomServerClient:(id<ARDRoomServerClient>)rsClient 145 signalingChannel:(id<ARDSignalingChannel>)channel 146 turnClient:(id<ARDTURNClient>)turnClient 147 delegate:(id<ARDAppClientDelegate>)delegate { 148 NSParameterAssert(rsClient); 149 NSParameterAssert(channel); 150 NSParameterAssert(turnClient); 151 if (self = [super init]) { 152 _roomServerClient = rsClient; 153 _channel = channel; 154 _turnClient = turnClient; 155 _delegate = delegate; 156 [self configure]; 157 } 158 return self; 159} 160 161- (void)configure { 162 _factory = [[RTCPeerConnectionFactory alloc] init]; 163 _messageQueue = [NSMutableArray array]; 164 _iceServers = [NSMutableArray arrayWithObject:[self defaultSTUNServer]]; 165 _fileLogger = [[RTCFileLogger alloc] init]; 166 [_fileLogger start]; 167} 168 169- (void)dealloc { 170 self.shouldGetStats = NO; 171 [self disconnect]; 172} 173 174- (void)setShouldGetStats:(BOOL)shouldGetStats { 175 if (_shouldGetStats == shouldGetStats) { 176 return; 177 } 178 if (shouldGetStats) { 179 __weak ARDAppClient *weakSelf = self; 180 _statsTimer = [[ARDTimerProxy alloc] initWithInterval:1 181 repeats:YES 182 timerHandler:^{ 183 ARDAppClient *strongSelf = weakSelf; 184 [strongSelf.peerConnection getStatsWithDelegate:strongSelf 185 mediaStreamTrack:nil 186 statsOutputLevel:RTCStatsOutputLevelDebug]; 187 }]; 188 } else { 189 [_statsTimer invalidate]; 190 _statsTimer = nil; 191 } 192 _shouldGetStats = shouldGetStats; 193} 194 195- (void)setState:(ARDAppClientState)state { 196 if (_state == state) { 197 return; 198 } 199 _state = state; 200 [_delegate appClient:self didChangeState:_state]; 201} 202 203- (void)connectToRoomWithId:(NSString *)roomId 204 isLoopback:(BOOL)isLoopback 205 isAudioOnly:(BOOL)isAudioOnly { 206 NSParameterAssert(roomId.length); 207 NSParameterAssert(_state == kARDAppClientStateDisconnected); 208 _isLoopback = isLoopback; 209 _isAudioOnly = isAudioOnly; 210 self.state = kARDAppClientStateConnecting; 211 212 // Request TURN. 213 __weak ARDAppClient *weakSelf = self; 214 [_turnClient requestServersWithCompletionHandler:^(NSArray *turnServers, 215 NSError *error) { 216 if (error) { 217 RTCLogError("Error retrieving TURN servers: %@", 218 error.localizedDescription); 219 } 220 ARDAppClient *strongSelf = weakSelf; 221 [strongSelf.iceServers addObjectsFromArray:turnServers]; 222 strongSelf.isTurnComplete = YES; 223 [strongSelf startSignalingIfReady]; 224 }]; 225 226 // Join room on room server. 227 [_roomServerClient joinRoomWithRoomId:roomId 228 isLoopback:isLoopback 229 completionHandler:^(ARDJoinResponse *response, NSError *error) { 230 ARDAppClient *strongSelf = weakSelf; 231 if (error) { 232 [strongSelf.delegate appClient:strongSelf didError:error]; 233 return; 234 } 235 NSError *joinError = 236 [[strongSelf class] errorForJoinResultType:response.result]; 237 if (joinError) { 238 RTCLogError(@"Failed to join room:%@ on room server.", roomId); 239 [strongSelf disconnect]; 240 [strongSelf.delegate appClient:strongSelf didError:joinError]; 241 return; 242 } 243 RTCLog(@"Joined room:%@ on room server.", roomId); 244 strongSelf.roomId = response.roomId; 245 strongSelf.clientId = response.clientId; 246 strongSelf.isInitiator = response.isInitiator; 247 for (ARDSignalingMessage *message in response.messages) { 248 if (message.type == kARDSignalingMessageTypeOffer || 249 message.type == kARDSignalingMessageTypeAnswer) { 250 strongSelf.hasReceivedSdp = YES; 251 [strongSelf.messageQueue insertObject:message atIndex:0]; 252 } else { 253 [strongSelf.messageQueue addObject:message]; 254 } 255 } 256 strongSelf.webSocketURL = response.webSocketURL; 257 strongSelf.webSocketRestURL = response.webSocketRestURL; 258 [strongSelf registerWithColliderIfReady]; 259 [strongSelf startSignalingIfReady]; 260 }]; 261} 262 263- (void)disconnect { 264 if (_state == kARDAppClientStateDisconnected) { 265 return; 266 } 267 if (self.hasJoinedRoomServerRoom) { 268 [_roomServerClient leaveRoomWithRoomId:_roomId 269 clientId:_clientId 270 completionHandler:nil]; 271 } 272 if (_channel) { 273 if (_channel.state == kARDSignalingChannelStateRegistered) { 274 // Tell the other client we're hanging up. 275 ARDByeMessage *byeMessage = [[ARDByeMessage alloc] init]; 276 [_channel sendMessage:byeMessage]; 277 } 278 // Disconnect from collider. 279 _channel = nil; 280 } 281 _clientId = nil; 282 _roomId = nil; 283 _isInitiator = NO; 284 _hasReceivedSdp = NO; 285 _messageQueue = [NSMutableArray array]; 286 _peerConnection = nil; 287 self.state = kARDAppClientStateDisconnected; 288} 289 290#pragma mark - ARDSignalingChannelDelegate 291 292- (void)channel:(id<ARDSignalingChannel>)channel 293 didReceiveMessage:(ARDSignalingMessage *)message { 294 switch (message.type) { 295 case kARDSignalingMessageTypeOffer: 296 case kARDSignalingMessageTypeAnswer: 297 // Offers and answers must be processed before any other message, so we 298 // place them at the front of the queue. 299 _hasReceivedSdp = YES; 300 [_messageQueue insertObject:message atIndex:0]; 301 break; 302 case kARDSignalingMessageTypeCandidate: 303 [_messageQueue addObject:message]; 304 break; 305 case kARDSignalingMessageTypeBye: 306 // Disconnects can be processed immediately. 307 [self processSignalingMessage:message]; 308 return; 309 } 310 [self drainMessageQueueIfReady]; 311} 312 313- (void)channel:(id<ARDSignalingChannel>)channel 314 didChangeState:(ARDSignalingChannelState)state { 315 switch (state) { 316 case kARDSignalingChannelStateOpen: 317 break; 318 case kARDSignalingChannelStateRegistered: 319 break; 320 case kARDSignalingChannelStateClosed: 321 case kARDSignalingChannelStateError: 322 // TODO(tkchin): reconnection scenarios. Right now we just disconnect 323 // completely if the websocket connection fails. 324 [self disconnect]; 325 break; 326 } 327} 328 329#pragma mark - RTCPeerConnectionDelegate 330// Callbacks for this delegate occur on non-main thread and need to be 331// dispatched back to main queue as needed. 332 333- (void)peerConnection:(RTCPeerConnection *)peerConnection 334 signalingStateChanged:(RTCSignalingState)stateChanged { 335 RTCLog(@"Signaling state changed: %d", stateChanged); 336} 337 338- (void)peerConnection:(RTCPeerConnection *)peerConnection 339 addedStream:(RTCMediaStream *)stream { 340 dispatch_async(dispatch_get_main_queue(), ^{ 341 RTCLog(@"Received %lu video tracks and %lu audio tracks", 342 (unsigned long)stream.videoTracks.count, 343 (unsigned long)stream.audioTracks.count); 344 if (stream.videoTracks.count) { 345 RTCVideoTrack *videoTrack = stream.videoTracks[0]; 346 [_delegate appClient:self didReceiveRemoteVideoTrack:videoTrack]; 347 } 348 }); 349} 350 351- (void)peerConnection:(RTCPeerConnection *)peerConnection 352 removedStream:(RTCMediaStream *)stream { 353 RTCLog(@"Stream was removed."); 354} 355 356- (void)peerConnectionOnRenegotiationNeeded: 357 (RTCPeerConnection *)peerConnection { 358 RTCLog(@"WARNING: Renegotiation needed but unimplemented."); 359} 360 361- (void)peerConnection:(RTCPeerConnection *)peerConnection 362 iceConnectionChanged:(RTCICEConnectionState)newState { 363 RTCLog(@"ICE state changed: %d", newState); 364 dispatch_async(dispatch_get_main_queue(), ^{ 365 [_delegate appClient:self didChangeConnectionState:newState]; 366 }); 367} 368 369- (void)peerConnection:(RTCPeerConnection *)peerConnection 370 iceGatheringChanged:(RTCICEGatheringState)newState { 371 RTCLog(@"ICE gathering state changed: %d", newState); 372} 373 374- (void)peerConnection:(RTCPeerConnection *)peerConnection 375 gotICECandidate:(RTCICECandidate *)candidate { 376 dispatch_async(dispatch_get_main_queue(), ^{ 377 ARDICECandidateMessage *message = 378 [[ARDICECandidateMessage alloc] initWithCandidate:candidate]; 379 [self sendSignalingMessage:message]; 380 }); 381} 382 383- (void)peerConnection:(RTCPeerConnection *)peerConnection 384 didOpenDataChannel:(RTCDataChannel *)dataChannel { 385} 386 387#pragma mark - RTCStatsDelegate 388 389- (void)peerConnection:(RTCPeerConnection *)peerConnection 390 didGetStats:(NSArray *)stats { 391 dispatch_async(dispatch_get_main_queue(), ^{ 392 [_delegate appClient:self didGetStats:stats]; 393 }); 394} 395 396#pragma mark - RTCSessionDescriptionDelegate 397// Callbacks for this delegate occur on non-main thread and need to be 398// dispatched back to main queue as needed. 399 400- (void)peerConnection:(RTCPeerConnection *)peerConnection 401 didCreateSessionDescription:(RTCSessionDescription *)sdp 402 error:(NSError *)error { 403 dispatch_async(dispatch_get_main_queue(), ^{ 404 if (error) { 405 RTCLogError(@"Failed to create session description. Error: %@", error); 406 [self disconnect]; 407 NSDictionary *userInfo = @{ 408 NSLocalizedDescriptionKey: @"Failed to create session description.", 409 }; 410 NSError *sdpError = 411 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain 412 code:kARDAppClientErrorCreateSDP 413 userInfo:userInfo]; 414 [_delegate appClient:self didError:sdpError]; 415 return; 416 } 417 // Prefer H264 if available. 418 RTCSessionDescription *sdpPreferringH264 = 419 [ARDSDPUtils descriptionForDescription:sdp 420 preferredVideoCodec:@"H264"]; 421 [_peerConnection setLocalDescriptionWithDelegate:self 422 sessionDescription:sdpPreferringH264]; 423 ARDSessionDescriptionMessage *message = 424 [[ARDSessionDescriptionMessage alloc] 425 initWithDescription:sdpPreferringH264]; 426 [self sendSignalingMessage:message]; 427 }); 428} 429 430- (void)peerConnection:(RTCPeerConnection *)peerConnection 431 didSetSessionDescriptionWithError:(NSError *)error { 432 dispatch_async(dispatch_get_main_queue(), ^{ 433 if (error) { 434 RTCLogError(@"Failed to set session description. Error: %@", error); 435 [self disconnect]; 436 NSDictionary *userInfo = @{ 437 NSLocalizedDescriptionKey: @"Failed to set session description.", 438 }; 439 NSError *sdpError = 440 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain 441 code:kARDAppClientErrorSetSDP 442 userInfo:userInfo]; 443 [_delegate appClient:self didError:sdpError]; 444 return; 445 } 446 // If we're answering and we've just set the remote offer we need to create 447 // an answer and set the local description. 448 if (!_isInitiator && !_peerConnection.localDescription) { 449 RTCMediaConstraints *constraints = [self defaultAnswerConstraints]; 450 [_peerConnection createAnswerWithDelegate:self 451 constraints:constraints]; 452 453 } 454 }); 455} 456 457#pragma mark - Private 458 459- (BOOL)hasJoinedRoomServerRoom { 460 return _clientId.length; 461} 462 463// Begins the peer connection connection process if we have both joined a room 464// on the room server and tried to obtain a TURN server. Otherwise does nothing. 465// A peer connection object will be created with a stream that contains local 466// audio and video capture. If this client is the caller, an offer is created as 467// well, otherwise the client will wait for an offer to arrive. 468- (void)startSignalingIfReady { 469 if (!_isTurnComplete || !self.hasJoinedRoomServerRoom) { 470 return; 471 } 472 self.state = kARDAppClientStateConnected; 473 474 // Create peer connection. 475 RTCMediaConstraints *constraints = [self defaultPeerConnectionConstraints]; 476 RTCConfiguration *config = [[RTCConfiguration alloc] init]; 477 config.iceServers = _iceServers; 478 _peerConnection = [_factory peerConnectionWithConfiguration:config 479 constraints:constraints 480 delegate:self]; 481 // Create AV media stream and add it to the peer connection. 482 RTCMediaStream *localStream = [self createLocalMediaStream]; 483 [_peerConnection addStream:localStream]; 484 if (_isInitiator) { 485 // Send offer. 486 [_peerConnection createOfferWithDelegate:self 487 constraints:[self defaultOfferConstraints]]; 488 } else { 489 // Check if we've received an offer. 490 [self drainMessageQueueIfReady]; 491 } 492} 493 494// Processes the messages that we've received from the room server and the 495// signaling channel. The offer or answer message must be processed before other 496// signaling messages, however they can arrive out of order. Hence, this method 497// only processes pending messages if there is a peer connection object and 498// if we have received either an offer or answer. 499- (void)drainMessageQueueIfReady { 500 if (!_peerConnection || !_hasReceivedSdp) { 501 return; 502 } 503 for (ARDSignalingMessage *message in _messageQueue) { 504 [self processSignalingMessage:message]; 505 } 506 [_messageQueue removeAllObjects]; 507} 508 509// Processes the given signaling message based on its type. 510- (void)processSignalingMessage:(ARDSignalingMessage *)message { 511 NSParameterAssert(_peerConnection || 512 message.type == kARDSignalingMessageTypeBye); 513 switch (message.type) { 514 case kARDSignalingMessageTypeOffer: 515 case kARDSignalingMessageTypeAnswer: { 516 ARDSessionDescriptionMessage *sdpMessage = 517 (ARDSessionDescriptionMessage *)message; 518 RTCSessionDescription *description = sdpMessage.sessionDescription; 519 // Prefer H264 if available. 520 RTCSessionDescription *sdpPreferringH264 = 521 [ARDSDPUtils descriptionForDescription:description 522 preferredVideoCodec:@"H264"]; 523 [_peerConnection setRemoteDescriptionWithDelegate:self 524 sessionDescription:sdpPreferringH264]; 525 break; 526 } 527 case kARDSignalingMessageTypeCandidate: { 528 ARDICECandidateMessage *candidateMessage = 529 (ARDICECandidateMessage *)message; 530 [_peerConnection addICECandidate:candidateMessage.candidate]; 531 break; 532 } 533 case kARDSignalingMessageTypeBye: 534 // Other client disconnected. 535 // TODO(tkchin): support waiting in room for next client. For now just 536 // disconnect. 537 [self disconnect]; 538 break; 539 } 540} 541 542// Sends a signaling message to the other client. The caller will send messages 543// through the room server, whereas the callee will send messages over the 544// signaling channel. 545- (void)sendSignalingMessage:(ARDSignalingMessage *)message { 546 if (_isInitiator) { 547 __weak ARDAppClient *weakSelf = self; 548 [_roomServerClient sendMessage:message 549 forRoomId:_roomId 550 clientId:_clientId 551 completionHandler:^(ARDMessageResponse *response, 552 NSError *error) { 553 ARDAppClient *strongSelf = weakSelf; 554 if (error) { 555 [strongSelf.delegate appClient:strongSelf didError:error]; 556 return; 557 } 558 NSError *messageError = 559 [[strongSelf class] errorForMessageResultType:response.result]; 560 if (messageError) { 561 [strongSelf.delegate appClient:strongSelf didError:messageError]; 562 return; 563 } 564 }]; 565 } else { 566 [_channel sendMessage:message]; 567 } 568} 569 570- (RTCMediaStream *)createLocalMediaStream { 571 RTCMediaStream* localStream = [_factory mediaStreamWithLabel:@"ARDAMS"]; 572 RTCVideoTrack* localVideoTrack = [self createLocalVideoTrack]; 573 if (localVideoTrack) { 574 [localStream addVideoTrack:localVideoTrack]; 575 [_delegate appClient:self didReceiveLocalVideoTrack:localVideoTrack]; 576 } 577 [localStream addAudioTrack:[_factory audioTrackWithID:@"ARDAMSa0"]]; 578 return localStream; 579} 580 581- (RTCVideoTrack *)createLocalVideoTrack { 582 RTCVideoTrack* localVideoTrack = nil; 583 // The iOS simulator doesn't provide any sort of camera capture 584 // support or emulation (http://goo.gl/rHAnC1) so don't bother 585 // trying to open a local stream. 586 // TODO(tkchin): local video capture for OSX. See 587 // https://code.google.com/p/webrtc/issues/detail?id=3417. 588#if !TARGET_IPHONE_SIMULATOR && TARGET_OS_IPHONE 589 if (!_isAudioOnly) { 590 RTCMediaConstraints *mediaConstraints = 591 [self defaultMediaStreamConstraints]; 592 RTCAVFoundationVideoSource *source = 593 [[RTCAVFoundationVideoSource alloc] initWithFactory:_factory 594 constraints:mediaConstraints]; 595 localVideoTrack = 596 [[RTCVideoTrack alloc] initWithFactory:_factory 597 source:source 598 trackId:@"ARDAMSv0"]; 599 } 600#endif 601 return localVideoTrack; 602} 603 604#pragma mark - Collider methods 605 606- (void)registerWithColliderIfReady { 607 if (!self.hasJoinedRoomServerRoom) { 608 return; 609 } 610 // Open WebSocket connection. 611 if (!_channel) { 612 _channel = 613 [[ARDWebSocketChannel alloc] initWithURL:_websocketURL 614 restURL:_websocketRestURL 615 delegate:self]; 616 if (_isLoopback) { 617 _loopbackChannel = 618 [[ARDLoopbackWebSocketChannel alloc] initWithURL:_websocketURL 619 restURL:_websocketRestURL]; 620 } 621 } 622 [_channel registerForRoomId:_roomId clientId:_clientId]; 623 if (_isLoopback) { 624 [_loopbackChannel registerForRoomId:_roomId clientId:@"LOOPBACK_CLIENT_ID"]; 625 } 626} 627 628#pragma mark - Defaults 629 630- (RTCMediaConstraints *)defaultMediaStreamConstraints { 631 RTCMediaConstraints* constraints = 632 [[RTCMediaConstraints alloc] 633 initWithMandatoryConstraints:nil 634 optionalConstraints:nil]; 635 return constraints; 636} 637 638- (RTCMediaConstraints *)defaultAnswerConstraints { 639 return [self defaultOfferConstraints]; 640} 641 642- (RTCMediaConstraints *)defaultOfferConstraints { 643 NSArray *mandatoryConstraints = @[ 644 [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" value:@"true"], 645 [[RTCPair alloc] initWithKey:@"OfferToReceiveVideo" value:@"true"] 646 ]; 647 RTCMediaConstraints* constraints = 648 [[RTCMediaConstraints alloc] 649 initWithMandatoryConstraints:mandatoryConstraints 650 optionalConstraints:nil]; 651 return constraints; 652} 653 654- (RTCMediaConstraints *)defaultPeerConnectionConstraints { 655 if (_defaultPeerConnectionConstraints) { 656 return _defaultPeerConnectionConstraints; 657 } 658 NSString *value = _isLoopback ? @"false" : @"true"; 659 NSArray *optionalConstraints = @[ 660 [[RTCPair alloc] initWithKey:@"DtlsSrtpKeyAgreement" value:value] 661 ]; 662 RTCMediaConstraints* constraints = 663 [[RTCMediaConstraints alloc] 664 initWithMandatoryConstraints:nil 665 optionalConstraints:optionalConstraints]; 666 return constraints; 667} 668 669- (RTCICEServer *)defaultSTUNServer { 670 NSURL *defaultSTUNServerURL = [NSURL URLWithString:kARDDefaultSTUNServerUrl]; 671 return [[RTCICEServer alloc] initWithURI:defaultSTUNServerURL 672 username:@"" 673 password:@""]; 674} 675 676#pragma mark - Errors 677 678+ (NSError *)errorForJoinResultType:(ARDJoinResultType)resultType { 679 NSError *error = nil; 680 switch (resultType) { 681 case kARDJoinResultTypeSuccess: 682 break; 683 case kARDJoinResultTypeUnknown: { 684 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain 685 code:kARDAppClientErrorUnknown 686 userInfo:@{ 687 NSLocalizedDescriptionKey: @"Unknown error.", 688 }]; 689 break; 690 } 691 case kARDJoinResultTypeFull: { 692 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain 693 code:kARDAppClientErrorRoomFull 694 userInfo:@{ 695 NSLocalizedDescriptionKey: @"Room is full.", 696 }]; 697 break; 698 } 699 } 700 return error; 701} 702 703+ (NSError *)errorForMessageResultType:(ARDMessageResultType)resultType { 704 NSError *error = nil; 705 switch (resultType) { 706 case kARDMessageResultTypeSuccess: 707 break; 708 case kARDMessageResultTypeUnknown: 709 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain 710 code:kARDAppClientErrorUnknown 711 userInfo:@{ 712 NSLocalizedDescriptionKey: @"Unknown error.", 713 }]; 714 break; 715 case kARDMessageResultTypeInvalidClient: 716 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain 717 code:kARDAppClientErrorInvalidClient 718 userInfo:@{ 719 NSLocalizedDescriptionKey: @"Invalid client.", 720 }]; 721 break; 722 case kARDMessageResultTypeInvalidRoom: 723 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain 724 code:kARDAppClientErrorInvalidRoom 725 userInfo:@{ 726 NSLocalizedDescriptionKey: @"Invalid room.", 727 }]; 728 break; 729 } 730 return error; 731} 732 733@end 734