OLD | NEW |
---|---|
1 /* | 1 /* |
2 * Copyright 2014 The WebRTC Project Authors. All rights reserved. | 2 * Copyright 2014 The WebRTC Project Authors. All rights reserved. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license | 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 | 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 | 6 * tree. An additional intellectual property rights grant can be found |
7 * in the file PATENTS. All contributing project authors may | 7 * in the file PATENTS. All contributing project authors may |
8 * be found in the AUTHORS file in the root of the source tree. | 8 * be found in the AUTHORS file in the root of the source tree. |
9 */ | 9 */ |
10 | 10 |
11 #import "ARDAppClient+Internal.h" | 11 #import "ARDAppClient+Internal.h" |
12 | 12 |
13 #if defined(WEBRTC_IOS) | 13 #if defined(WEBRTC_IOS) |
14 #import "webrtc/base/objc/RTCTracing.h" | 14 #import "webrtc/base/objc/RTCTracing.h" |
15 #import "RTCAVFoundationVideoSource.h" | 15 #import "webrtc/api/objc/RTCAVFoundationVideoSource.h" |
16 #endif | 16 #endif |
17 #import "RTCFileLogger.h" | 17 #import "RTCFileLogger.h" |
hjon_webrtc
2016/02/12 18:26:58
I seem to recall that we held off on this because
| |
18 #import "RTCICEServer.h" | 18 #import "webrtc/api/objc/RTCConfiguration.h" |
19 #import "RTCLogging.h" | 19 #import "webrtc/api/objc/RTCIceServer.h" |
20 #import "RTCMediaConstraints.h" | 20 #import "webrtc/api/objc/RTCMediaConstraints.h" |
21 #import "RTCMediaStream.h" | 21 #import "webrtc/api/objc/RTCMediaStream.h" |
22 #import "RTCPair.h" | 22 #import "webrtc/api/objc/RTCVideoCapturer.h" |
23 #import "RTCPeerConnectionInterface.h" | 23 #import "webrtc/base/objc/RTCLogging.h" |
24 #import "RTCVideoCapturer.h" | |
25 | 24 |
26 #import "ARDAppEngineClient.h" | 25 #import "ARDAppEngineClient.h" |
27 #import "ARDCEODTURNClient.h" | 26 #import "ARDCEODTURNClient.h" |
28 #import "ARDJoinResponse.h" | 27 #import "ARDJoinResponse.h" |
29 #import "ARDMessageResponse.h" | 28 #import "ARDMessageResponse.h" |
30 #import "ARDSDPUtils.h" | 29 #import "ARDSDPUtils.h" |
31 #import "ARDSignalingMessage.h" | 30 #import "ARDSignalingMessage.h" |
32 #import "ARDUtilities.h" | 31 #import "ARDUtilities.h" |
33 #import "ARDWebSocketChannel.h" | 32 #import "ARDWebSocketChannel.h" |
34 #import "RTCICECandidate+JSON.h" | 33 #import "RTCIceCandidate+JSON.h" |
35 #import "RTCSessionDescription+JSON.h" | 34 #import "RTCSessionDescription+JSON.h" |
36 | 35 |
37 static NSString * const kARDDefaultSTUNServerUrl = | 36 static NSString * const kARDDefaultSTUNServerUrl = |
38 @"stun:stun.l.google.com:19302"; | 37 @"stun:stun.l.google.com:19302"; |
39 // TODO(tkchin): figure out a better username for CEOD statistics. | 38 // TODO(tkchin): figure out a better username for CEOD statistics. |
40 static NSString * const kARDTurnRequestUrl = | 39 static NSString * const kARDTurnRequestUrl = |
41 @"https://computeengineondemand.appspot.com" | 40 @"https://computeengineondemand.appspot.com" |
42 @"/turn?username=iapprtc&key=4080218913"; | 41 @"/turn?username=iapprtc&key=4080218913"; |
43 | 42 |
44 static NSString * const kARDAppClientErrorDomain = @"ARDAppClient"; | 43 static NSString * const kARDAppClientErrorDomain = @"ARDAppClient"; |
(...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
181 - (void)setShouldGetStats:(BOOL)shouldGetStats { | 180 - (void)setShouldGetStats:(BOOL)shouldGetStats { |
182 if (_shouldGetStats == shouldGetStats) { | 181 if (_shouldGetStats == shouldGetStats) { |
183 return; | 182 return; |
184 } | 183 } |
185 if (shouldGetStats) { | 184 if (shouldGetStats) { |
186 __weak ARDAppClient *weakSelf = self; | 185 __weak ARDAppClient *weakSelf = self; |
187 _statsTimer = [[ARDTimerProxy alloc] initWithInterval:1 | 186 _statsTimer = [[ARDTimerProxy alloc] initWithInterval:1 |
188 repeats:YES | 187 repeats:YES |
189 timerHandler:^{ | 188 timerHandler:^{ |
190 ARDAppClient *strongSelf = weakSelf; | 189 ARDAppClient *strongSelf = weakSelf; |
191 [strongSelf.peerConnection getStatsWithDelegate:strongSelf | 190 [strongSelf.peerConnection statsForTrack:nil |
192 mediaStreamTrack:nil | 191 statsOutputLevel:RTCStatsOutputLevelDebug |
193 statsOutputLevel:RTCStatsOutputLevelDebug]; | 192 completionHandler:^(NSArray<RTCStatsReport *> *stat s) { |
193 dispatch_async(dispatch_get_main_queue(), ^{ | |
194 ARDAppClient *strongSelf = weakSelf; | |
195 [strongSelf.delegate appClient:strongSelf didGetStats:stats]; | |
196 }); | |
197 }]; | |
194 }]; | 198 }]; |
195 } else { | 199 } else { |
196 [_statsTimer invalidate]; | 200 [_statsTimer invalidate]; |
197 _statsTimer = nil; | 201 _statsTimer = nil; |
198 } | 202 } |
199 _shouldGetStats = shouldGetStats; | 203 _shouldGetStats = shouldGetStats; |
200 } | 204 } |
201 | 205 |
202 - (void)setState:(ARDAppClientState)state { | 206 - (void)setState:(ARDAppClientState)state { |
203 if (_state == state) { | 207 if (_state == state) { |
(...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
345 [self disconnect]; | 349 [self disconnect]; |
346 break; | 350 break; |
347 } | 351 } |
348 } | 352 } |
349 | 353 |
350 #pragma mark - RTCPeerConnectionDelegate | 354 #pragma mark - RTCPeerConnectionDelegate |
351 // Callbacks for this delegate occur on non-main thread and need to be | 355 // Callbacks for this delegate occur on non-main thread and need to be |
352 // dispatched back to main queue as needed. | 356 // dispatched back to main queue as needed. |
353 | 357 |
354 - (void)peerConnection:(RTCPeerConnection *)peerConnection | 358 - (void)peerConnection:(RTCPeerConnection *)peerConnection |
355 signalingStateChanged:(RTCSignalingState)stateChanged { | 359 didChangeSignalingState:(RTCSignalingState)stateChanged { |
356 RTCLog(@"Signaling state changed: %d", stateChanged); | 360 RTCLog(@"Signaling state changed: %d", stateChanged); |
357 } | 361 } |
358 | 362 |
359 - (void)peerConnection:(RTCPeerConnection *)peerConnection | 363 - (void)peerConnection:(RTCPeerConnection *)peerConnection |
360 addedStream:(RTCMediaStream *)stream { | 364 didAddStream:(RTCMediaStream *)stream { |
361 dispatch_async(dispatch_get_main_queue(), ^{ | 365 dispatch_async(dispatch_get_main_queue(), ^{ |
362 RTCLog(@"Received %lu video tracks and %lu audio tracks", | 366 RTCLog(@"Received %lu video tracks and %lu audio tracks", |
363 (unsigned long)stream.videoTracks.count, | 367 (unsigned long)stream.videoTracks.count, |
364 (unsigned long)stream.audioTracks.count); | 368 (unsigned long)stream.audioTracks.count); |
365 if (stream.videoTracks.count) { | 369 if (stream.videoTracks.count) { |
366 RTCVideoTrack *videoTrack = stream.videoTracks[0]; | 370 RTCVideoTrack *videoTrack = stream.videoTracks[0]; |
367 [_delegate appClient:self didReceiveRemoteVideoTrack:videoTrack]; | 371 [_delegate appClient:self didReceiveRemoteVideoTrack:videoTrack]; |
368 } | 372 } |
369 }); | 373 }); |
370 } | 374 } |
371 | 375 |
372 - (void)peerConnection:(RTCPeerConnection *)peerConnection | 376 - (void)peerConnection:(RTCPeerConnection *)peerConnection |
373 removedStream:(RTCMediaStream *)stream { | 377 didRemoveStream:(RTCMediaStream *)stream { |
374 RTCLog(@"Stream was removed."); | 378 RTCLog(@"Stream was removed."); |
375 } | 379 } |
376 | 380 |
377 - (void)peerConnectionOnRenegotiationNeeded: | 381 - (void)peerConnectionShouldNegotiate:(RTCPeerConnection *)peerConnection { |
378 (RTCPeerConnection *)peerConnection { | |
379 RTCLog(@"WARNING: Renegotiation needed but unimplemented."); | 382 RTCLog(@"WARNING: Renegotiation needed but unimplemented."); |
380 } | 383 } |
381 | 384 |
382 - (void)peerConnection:(RTCPeerConnection *)peerConnection | 385 - (void)peerConnection:(RTCPeerConnection *)peerConnection |
383 iceConnectionChanged:(RTCICEConnectionState)newState { | 386 didChangeIceConnectionState:(RTCIceConnectionState)newState { |
384 RTCLog(@"ICE state changed: %d", newState); | 387 RTCLog(@"ICE state changed: %d", newState); |
385 dispatch_async(dispatch_get_main_queue(), ^{ | 388 dispatch_async(dispatch_get_main_queue(), ^{ |
386 [_delegate appClient:self didChangeConnectionState:newState]; | 389 [_delegate appClient:self didChangeConnectionState:newState]; |
387 }); | 390 }); |
388 } | 391 } |
389 | 392 |
390 - (void)peerConnection:(RTCPeerConnection *)peerConnection | 393 - (void)peerConnection:(RTCPeerConnection *)peerConnection |
391 iceGatheringChanged:(RTCICEGatheringState)newState { | 394 didChangeIceGatheringState:(RTCIceGatheringState)newState { |
392 RTCLog(@"ICE gathering state changed: %d", newState); | 395 RTCLog(@"ICE gathering state changed: %d", newState); |
393 } | 396 } |
394 | 397 |
395 - (void)peerConnection:(RTCPeerConnection *)peerConnection | 398 - (void)peerConnection:(RTCPeerConnection *)peerConnection |
396 gotICECandidate:(RTCICECandidate *)candidate { | 399 didGenerateIceCandidate:(RTCIceCandidate *)candidate { |
397 dispatch_async(dispatch_get_main_queue(), ^{ | 400 dispatch_async(dispatch_get_main_queue(), ^{ |
398 ARDICECandidateMessage *message = | 401 ARDICECandidateMessage *message = |
399 [[ARDICECandidateMessage alloc] initWithCandidate:candidate]; | 402 [[ARDICECandidateMessage alloc] initWithCandidate:candidate]; |
400 [self sendSignalingMessage:message]; | 403 [self sendSignalingMessage:message]; |
401 }); | 404 }); |
402 } | 405 } |
403 | 406 |
404 - (void)peerConnection:(RTCPeerConnection *)peerConnection | 407 - (void)peerConnection:(RTCPeerConnection *)peerConnection |
405 didOpenDataChannel:(RTCDataChannel *)dataChannel { | 408 didOpenDataChannel:(RTCDataChannel *)dataChannel { |
406 } | 409 } |
407 | 410 |
408 #pragma mark - RTCStatsDelegate | |
409 | |
410 - (void)peerConnection:(RTCPeerConnection *)peerConnection | |
411 didGetStats:(NSArray *)stats { | |
412 dispatch_async(dispatch_get_main_queue(), ^{ | |
413 [_delegate appClient:self didGetStats:stats]; | |
414 }); | |
415 } | |
416 | |
417 #pragma mark - RTCSessionDescriptionDelegate | 411 #pragma mark - RTCSessionDescriptionDelegate |
418 // Callbacks for this delegate occur on non-main thread and need to be | 412 // Callbacks for this delegate occur on non-main thread and need to be |
419 // dispatched back to main queue as needed. | 413 // dispatched back to main queue as needed. |
420 | 414 |
421 - (void)peerConnection:(RTCPeerConnection *)peerConnection | 415 - (void)peerConnection:(RTCPeerConnection *)peerConnection |
422 didCreateSessionDescription:(RTCSessionDescription *)sdp | 416 didCreateSessionDescription:(RTCSessionDescription *)sdp |
423 error:(NSError *)error { | 417 error:(NSError *)error { |
424 dispatch_async(dispatch_get_main_queue(), ^{ | 418 dispatch_async(dispatch_get_main_queue(), ^{ |
425 if (error) { | 419 if (error) { |
426 RTCLogError(@"Failed to create session description. Error: %@", error); | 420 RTCLogError(@"Failed to create session description. Error: %@", error); |
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
489 - (void)startSignalingIfReady { | 483 - (void)startSignalingIfReady { |
490 if (!_isTurnComplete || !self.hasJoinedRoomServerRoom) { | 484 if (!_isTurnComplete || !self.hasJoinedRoomServerRoom) { |
491 return; | 485 return; |
492 } | 486 } |
493 self.state = kARDAppClientStateConnected; | 487 self.state = kARDAppClientStateConnected; |
494 | 488 |
495 // Create peer connection. | 489 // Create peer connection. |
496 RTCMediaConstraints *constraints = [self defaultPeerConnectionConstraints]; | 490 RTCMediaConstraints *constraints = [self defaultPeerConnectionConstraints]; |
497 RTCConfiguration *config = [[RTCConfiguration alloc] init]; | 491 RTCConfiguration *config = [[RTCConfiguration alloc] init]; |
498 config.iceServers = _iceServers; | 492 config.iceServers = _iceServers; |
499 _peerConnection = [_factory peerConnectionWithConfiguration:config | 493 _peerConnection = [[RTCPeerConnection alloc] initWithFactory:_factory |
500 constraints:constraints | 494 configuration:config |
501 delegate:self]; | 495 constraints:constraints |
496 delegate:self]; | |
502 // Create AV media stream and add it to the peer connection. | 497 // Create AV media stream and add it to the peer connection. |
503 RTCMediaStream *localStream = [self createLocalMediaStream]; | 498 RTCMediaStream *localStream = [self createLocalMediaStream]; |
504 [_peerConnection addStream:localStream]; | 499 [_peerConnection addStream:localStream]; |
505 if (_isInitiator) { | 500 if (_isInitiator) { |
506 // Send offer. | 501 // Send offer. |
507 [_peerConnection createOfferWithDelegate:self | 502 [_peerConnection createOfferWithDelegate:self |
508 constraints:[self defaultOfferConstraints]]; | 503 constraints:[self defaultOfferConstraints]]; |
hjon_webrtc
2016/02/12 18:26:58
Did you have any thoughts on how you wanted to han
| |
504 [_peerConnection offerForConstraints:[self defaultOfferConstraints] | |
505 completionHandler:^(RTCSessionDescription *sdp, | |
506 NSError *error) { | |
507 | |
508 }]; | |
509 } else { | 509 } else { |
510 // Check if we've received an offer. | 510 // Check if we've received an offer. |
511 [self drainMessageQueueIfReady]; | 511 [self drainMessageQueueIfReady]; |
512 } | 512 } |
513 } | 513 } |
514 | 514 |
515 // Processes the messages that we've received from the room server and the | 515 // Processes the messages that we've received from the room server and the |
516 // signaling channel. The offer or answer message must be processed before other | 516 // signaling channel. The offer or answer message must be processed before other |
517 // signaling messages, however they can arrive out of order. Hence, this method | 517 // signaling messages, however they can arrive out of order. Hence, this method |
518 // only processes pending messages if there is a peer connection object and | 518 // only processes pending messages if there is a peer connection object and |
(...skipping 22 matching lines...) Expand all Loading... | |
541 RTCSessionDescription *sdpPreferringH264 = | 541 RTCSessionDescription *sdpPreferringH264 = |
542 [ARDSDPUtils descriptionForDescription:description | 542 [ARDSDPUtils descriptionForDescription:description |
543 preferredVideoCodec:@"H264"]; | 543 preferredVideoCodec:@"H264"]; |
544 [_peerConnection setRemoteDescriptionWithDelegate:self | 544 [_peerConnection setRemoteDescriptionWithDelegate:self |
545 sessionDescription:sdpPreferringH264]; | 545 sessionDescription:sdpPreferringH264]; |
546 break; | 546 break; |
547 } | 547 } |
548 case kARDSignalingMessageTypeCandidate: { | 548 case kARDSignalingMessageTypeCandidate: { |
549 ARDICECandidateMessage *candidateMessage = | 549 ARDICECandidateMessage *candidateMessage = |
550 (ARDICECandidateMessage *)message; | 550 (ARDICECandidateMessage *)message; |
551 [_peerConnection addICECandidate:candidateMessage.candidate]; | 551 [_peerConnection addIceCandidate:candidateMessage.candidate]; |
552 break; | 552 break; |
553 } | 553 } |
554 case kARDSignalingMessageTypeBye: | 554 case kARDSignalingMessageTypeBye: |
555 // Other client disconnected. | 555 // Other client disconnected. |
556 // TODO(tkchin): support waiting in room for next client. For now just | 556 // TODO(tkchin): support waiting in room for next client. For now just |
557 // disconnect. | 557 // disconnect. |
558 [self disconnect]; | 558 [self disconnect]; |
559 break; | 559 break; |
560 } | 560 } |
561 } | 561 } |
(...skipping 26 matching lines...) Expand all Loading... | |
588 } | 588 } |
589 } | 589 } |
590 | 590 |
591 - (RTCMediaStream *)createLocalMediaStream { | 591 - (RTCMediaStream *)createLocalMediaStream { |
592 RTCMediaStream* localStream = [_factory mediaStreamWithLabel:@"ARDAMS"]; | 592 RTCMediaStream* localStream = [_factory mediaStreamWithLabel:@"ARDAMS"]; |
593 RTCVideoTrack* localVideoTrack = [self createLocalVideoTrack]; | 593 RTCVideoTrack* localVideoTrack = [self createLocalVideoTrack]; |
594 if (localVideoTrack) { | 594 if (localVideoTrack) { |
595 [localStream addVideoTrack:localVideoTrack]; | 595 [localStream addVideoTrack:localVideoTrack]; |
596 [_delegate appClient:self didReceiveLocalVideoTrack:localVideoTrack]; | 596 [_delegate appClient:self didReceiveLocalVideoTrack:localVideoTrack]; |
597 } | 597 } |
598 [localStream addAudioTrack:[_factory audioTrackWithID:@"ARDAMSa0"]]; | 598 RTCAudioTrack *localAudioTrack = |
599 [[RTCAudioTrack alloc] initWithFactory:_factory | |
600 trackId:@"ARDAMSa0"]; | |
601 [localStream addAudioTrack:localAudioTrack]; | |
599 return localStream; | 602 return localStream; |
600 } | 603 } |
601 | 604 |
602 - (RTCVideoTrack *)createLocalVideoTrack { | 605 - (RTCVideoTrack *)createLocalVideoTrack { |
603 RTCVideoTrack* localVideoTrack = nil; | 606 RTCVideoTrack* localVideoTrack = nil; |
604 // The iOS simulator doesn't provide any sort of camera capture | 607 // The iOS simulator doesn't provide any sort of camera capture |
605 // support or emulation (http://goo.gl/rHAnC1) so don't bother | 608 // support or emulation (http://goo.gl/rHAnC1) so don't bother |
606 // trying to open a local stream. | 609 // trying to open a local stream. |
607 // TODO(tkchin): local video capture for OSX. See | 610 // TODO(tkchin): local video capture for OSX. See |
608 // https://code.google.com/p/webrtc/issues/detail?id=3417. | 611 // https://code.google.com/p/webrtc/issues/detail?id=3417. |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
654 initWithMandatoryConstraints:nil | 657 initWithMandatoryConstraints:nil |
655 optionalConstraints:nil]; | 658 optionalConstraints:nil]; |
656 return constraints; | 659 return constraints; |
657 } | 660 } |
658 | 661 |
659 - (RTCMediaConstraints *)defaultAnswerConstraints { | 662 - (RTCMediaConstraints *)defaultAnswerConstraints { |
660 return [self defaultOfferConstraints]; | 663 return [self defaultOfferConstraints]; |
661 } | 664 } |
662 | 665 |
663 - (RTCMediaConstraints *)defaultOfferConstraints { | 666 - (RTCMediaConstraints *)defaultOfferConstraints { |
664 NSArray *mandatoryConstraints = @[ | 667 NSDictionary *mandatoryConstraints = @{ |
665 [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" value:@"true"], | 668 @"OfferToReceiveAudio" : @"true", |
666 [[RTCPair alloc] initWithKey:@"OfferToReceiveVideo" value:@"true"] | 669 @"OfferToReceiveVideo" : @"true" |
667 ]; | 670 }; |
668 RTCMediaConstraints* constraints = | 671 RTCMediaConstraints* constraints = |
669 [[RTCMediaConstraints alloc] | 672 [[RTCMediaConstraints alloc] |
670 initWithMandatoryConstraints:mandatoryConstraints | 673 initWithMandatoryConstraints:mandatoryConstraints |
671 optionalConstraints:nil]; | 674 optionalConstraints:nil]; |
672 return constraints; | 675 return constraints; |
673 } | 676 } |
674 | 677 |
675 - (RTCMediaConstraints *)defaultPeerConnectionConstraints { | 678 - (RTCMediaConstraints *)defaultPeerConnectionConstraints { |
676 if (_defaultPeerConnectionConstraints) { | 679 if (_defaultPeerConnectionConstraints) { |
677 return _defaultPeerConnectionConstraints; | 680 return _defaultPeerConnectionConstraints; |
678 } | 681 } |
679 NSString *value = _isLoopback ? @"false" : @"true"; | 682 NSString *value = _isLoopback ? @"false" : @"true"; |
680 NSArray *optionalConstraints = @[ | 683 NSDictionary *optionalConstraints = @{ @"DtlsSrtpKeyAgreement" : value }; |
681 [[RTCPair alloc] initWithKey:@"DtlsSrtpKeyAgreement" value:value] | |
682 ]; | |
683 RTCMediaConstraints* constraints = | 684 RTCMediaConstraints* constraints = |
684 [[RTCMediaConstraints alloc] | 685 [[RTCMediaConstraints alloc] |
685 initWithMandatoryConstraints:nil | 686 initWithMandatoryConstraints:nil |
686 optionalConstraints:optionalConstraints]; | 687 optionalConstraints:optionalConstraints]; |
687 return constraints; | 688 return constraints; |
688 } | 689 } |
689 | 690 |
690 - (RTCICEServer *)defaultSTUNServer { | 691 - (RTCICEServer *)defaultSTUNServer { |
691 NSURL *defaultSTUNServerURL = [NSURL URLWithString:kARDDefaultSTUNServerUrl]; | 692 return [[RTCIceServer alloc] initWithURLStrings:@[kARDDefaultSTUNServerUrl] |
692 return [[RTCICEServer alloc] initWithURI:defaultSTUNServerURL | 693 username:@"" |
693 username:@"" | 694 credential:@""]; |
694 password:@""]; | |
695 } | 695 } |
696 | 696 |
697 #pragma mark - Errors | 697 #pragma mark - Errors |
698 | 698 |
699 + (NSError *)errorForJoinResultType:(ARDJoinResultType)resultType { | 699 + (NSError *)errorForJoinResultType:(ARDJoinResultType)resultType { |
700 NSError *error = nil; | 700 NSError *error = nil; |
701 switch (resultType) { | 701 switch (resultType) { |
702 case kARDJoinResultTypeSuccess: | 702 case kARDJoinResultTypeSuccess: |
703 break; | 703 break; |
704 case kARDJoinResultTypeUnknown: { | 704 case kARDJoinResultTypeUnknown: { |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
745 code:kARDAppClientErrorInvalidRoom | 745 code:kARDAppClientErrorInvalidRoom |
746 userInfo:@{ | 746 userInfo:@{ |
747 NSLocalizedDescriptionKey: @"Invalid room.", | 747 NSLocalizedDescriptionKey: @"Invalid room.", |
748 }]; | 748 }]; |
749 break; | 749 break; |
750 } | 750 } |
751 return error; | 751 return error; |
752 } | 752 } |
753 | 753 |
754 @end | 754 @end |
OLD | NEW |