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