blob: ea86c5ed48279e2b6da87edb1cf28cf263b0bf5c [file] [log] [blame]
tkchin@webrtc.org87776a82014-12-09 19:32:35 +00001/*
Donald E Curtisa8736442015-08-05 15:48:13 -07002 * Copyright 2014 The WebRTC Project Authors. All rights reserved.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +00003 *
Donald E Curtisa8736442015-08-05 15:48:13 -07004 * 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.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +00009 */
10
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000011#import "ARDAppClient+Internal.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000012
Zeke Chin57cc74e2015-05-05 07:52:31 -070013#if defined(WEBRTC_IOS)
tkchin9eeb6242016-04-27 01:54:20 -070014#import "WebRTC/RTCAVFoundationVideoSource.h"
Zeke Chin57cc74e2015-05-05 07:52:31 -070015#endif
tkchin9eeb6242016-04-27 01:54:20 -070016#import "WebRTC/RTCAudioTrack.h"
17#import "WebRTC/RTCConfiguration.h"
18#import "WebRTC/RTCFileLogger.h"
19#import "WebRTC/RTCIceServer.h"
20#import "WebRTC/RTCLogging.h"
21#import "WebRTC/RTCMediaConstraints.h"
22#import "WebRTC/RTCMediaStream.h"
23#import "WebRTC/RTCPeerConnectionFactory.h"
skvladf3569c82016-04-29 15:30:16 -070024#import "WebRTC/RTCRtpSender.h"
tkchin204177f2016-06-14 15:03:11 -070025#import "WebRTC/RTCTracing.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000026
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000027#import "ARDAppEngineClient.h"
28#import "ARDCEODTURNClient.h"
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +000029#import "ARDJoinResponse.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000030#import "ARDMessageResponse.h"
Zeke Chin71f6f442015-06-29 14:34:58 -070031#import "ARDSDPUtils.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000032#import "ARDSignalingMessage.h"
33#import "ARDUtilities.h"
34#import "ARDWebSocketChannel.h"
hjon79858f82016-03-13 22:08:26 -070035#import "RTCIceCandidate+JSON.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000036#import "RTCSessionDescription+JSON.h"
Zeke Chin57cc74e2015-05-05 07:52:31 -070037
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +000038static NSString * const kARDDefaultSTUNServerUrl =
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000039 @"stun:stun.l.google.com:19302";
40// TODO(tkchin): figure out a better username for CEOD statistics.
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +000041static NSString * const kARDTurnRequestUrl =
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000042 @"https://computeengineondemand.appspot.com"
43 @"/turn?username=iapprtc&key=4080218913";
44
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +000045static 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;
skvladf3569c82016-04-29 15:30:16 -070052static NSString * const kARDMediaStreamId = @"ARDAMS";
53static NSString * const kARDAudioTrackId = @"ARDAMSa0";
54static NSString * const kARDVideoTrackId = @"ARDAMSv0";
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000055
tkchin9eeb6242016-04-27 01:54:20 -070056// TODO(tkchin): Remove guard once rtc_sdk_common_objc compiles on Mac.
tkchind1fb26d2016-02-03 01:51:18 -080057#if defined(WEBRTC_IOS)
tkchin204177f2016-06-14 15:03:11 -070058// TODO(tkchin): Add these as UI options.
tkchind1fb26d2016-02-03 01:51:18 -080059static BOOL const kARDAppClientEnableTracing = NO;
tkchin204177f2016-06-14 15:03:11 -070060static BOOL const kARDAppClientEnableRtcEventLog = YES;
61static int64_t const kARDAppClientRtcEventLogMaxSizeInBytes = 5e6; // 5 MB.
tkchind1fb26d2016-02-03 01:51:18 -080062#endif
63
Zeke Chind3325802015-08-14 11:00:02 -070064// We need a proxy to NSTimer because it causes a strong retain cycle. When
65// using the proxy, |invalidate| must be called before it properly deallocs.
66@interface ARDTimerProxy : NSObject
67
68- (instancetype)initWithInterval:(NSTimeInterval)interval
69 repeats:(BOOL)repeats
70 timerHandler:(void (^)(void))timerHandler;
71- (void)invalidate;
72
73@end
74
75@implementation ARDTimerProxy {
76 NSTimer *_timer;
77 void (^_timerHandler)(void);
Zeke Chin2d3b7e22015-07-14 12:55:44 -070078}
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000079
Zeke Chind3325802015-08-14 11:00:02 -070080- (instancetype)initWithInterval:(NSTimeInterval)interval
81 repeats:(BOOL)repeats
82 timerHandler:(void (^)(void))timerHandler {
83 NSParameterAssert(timerHandler);
84 if (self = [super init]) {
85 _timerHandler = timerHandler;
86 _timer = [NSTimer scheduledTimerWithTimeInterval:interval
87 target:self
88 selector:@selector(timerDidFire:)
89 userInfo:nil
90 repeats:repeats];
91 }
92 return self;
93}
94
95- (void)invalidate {
96 [_timer invalidate];
97}
98
99- (void)timerDidFire:(NSTimer *)timer {
100 _timerHandler();
101}
102
103@end
104
105@implementation ARDAppClient {
106 RTCFileLogger *_fileLogger;
107 ARDTimerProxy *_statsTimer;
108}
109
110@synthesize shouldGetStats = _shouldGetStats;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000111@synthesize state = _state;
Zeke Chind3325802015-08-14 11:00:02 -0700112@synthesize delegate = _delegate;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000113@synthesize roomServerClient = _roomServerClient;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000114@synthesize channel = _channel;
haysc913e6452015-10-02 11:44:03 -0700115@synthesize loopbackChannel = _loopbackChannel;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000116@synthesize turnClient = _turnClient;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000117@synthesize peerConnection = _peerConnection;
118@synthesize factory = _factory;
119@synthesize messageQueue = _messageQueue;
120@synthesize isTurnComplete = _isTurnComplete;
121@synthesize hasReceivedSdp = _hasReceivedSdp;
122@synthesize roomId = _roomId;
123@synthesize clientId = _clientId;
124@synthesize isInitiator = _isInitiator;
125@synthesize iceServers = _iceServers;
126@synthesize webSocketURL = _websocketURL;
127@synthesize webSocketRestURL = _websocketRestURL;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000128@synthesize defaultPeerConnectionConstraints =
129 _defaultPeerConnectionConstraints;
haysc913e6452015-10-02 11:44:03 -0700130@synthesize isLoopback = _isLoopback;
131@synthesize isAudioOnly = _isAudioOnly;
peah5085b0c2016-08-25 22:15:14 -0700132@synthesize shouldMakeAecDump = _shouldMakeAecDump;
133@synthesize isAecDumpActive = _isAecDumpActive;
tkchinab1293a2016-08-30 12:35:05 -0700134@synthesize shouldUseLevelControl = _shouldUseLevelControl;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000135
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000136- (instancetype)init {
137 if (self = [super init]) {
138 _roomServerClient = [[ARDAppEngineClient alloc] init];
139 NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl];
140 _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL];
141 [self configure];
142 }
143 return self;
144}
145
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000146- (instancetype)initWithDelegate:(id<ARDAppClientDelegate>)delegate {
147 if (self = [super init]) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000148 _roomServerClient = [[ARDAppEngineClient alloc] init];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000149 _delegate = delegate;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000150 NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl];
151 _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL];
152 [self configure];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000153 }
154 return self;
155}
156
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000157// TODO(tkchin): Provide signaling channel factory interface so we can recreate
158// channel if we need to on network failure. Also, make this the default public
159// constructor.
160- (instancetype)initWithRoomServerClient:(id<ARDRoomServerClient>)rsClient
161 signalingChannel:(id<ARDSignalingChannel>)channel
162 turnClient:(id<ARDTURNClient>)turnClient
163 delegate:(id<ARDAppClientDelegate>)delegate {
164 NSParameterAssert(rsClient);
165 NSParameterAssert(channel);
166 NSParameterAssert(turnClient);
167 if (self = [super init]) {
168 _roomServerClient = rsClient;
169 _channel = channel;
170 _turnClient = turnClient;
171 _delegate = delegate;
172 [self configure];
173 }
174 return self;
175}
176
177- (void)configure {
178 _factory = [[RTCPeerConnectionFactory alloc] init];
179 _messageQueue = [NSMutableArray array];
180 _iceServers = [NSMutableArray arrayWithObject:[self defaultSTUNServer]];
Zeke Chin2d3b7e22015-07-14 12:55:44 -0700181 _fileLogger = [[RTCFileLogger alloc] init];
182 [_fileLogger start];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000183}
184
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000185- (void)dealloc {
Zeke Chind3325802015-08-14 11:00:02 -0700186 self.shouldGetStats = NO;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000187 [self disconnect];
188}
189
Zeke Chind3325802015-08-14 11:00:02 -0700190- (void)setShouldGetStats:(BOOL)shouldGetStats {
191 if (_shouldGetStats == shouldGetStats) {
192 return;
193 }
194 if (shouldGetStats) {
195 __weak ARDAppClient *weakSelf = self;
196 _statsTimer = [[ARDTimerProxy alloc] initWithInterval:1
197 repeats:YES
198 timerHandler:^{
199 ARDAppClient *strongSelf = weakSelf;
hjon79858f82016-03-13 22:08:26 -0700200 [strongSelf.peerConnection statsForTrack:nil
201 statsOutputLevel:RTCStatsOutputLevelDebug
202 completionHandler:^(NSArray *stats) {
203 dispatch_async(dispatch_get_main_queue(), ^{
204 ARDAppClient *strongSelf = weakSelf;
205 [strongSelf.delegate appClient:strongSelf didGetStats:stats];
206 });
207 }];
Zeke Chind3325802015-08-14 11:00:02 -0700208 }];
209 } else {
210 [_statsTimer invalidate];
211 _statsTimer = nil;
212 }
213 _shouldGetStats = shouldGetStats;
214}
215
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000216- (void)setState:(ARDAppClientState)state {
217 if (_state == state) {
218 return;
219 }
220 _state = state;
221 [_delegate appClient:self didChangeState:_state];
222}
223
224- (void)connectToRoomWithId:(NSString *)roomId
haysc913e6452015-10-02 11:44:03 -0700225 isLoopback:(BOOL)isLoopback
peah5085b0c2016-08-25 22:15:14 -0700226 isAudioOnly:(BOOL)isAudioOnly
tkchinab1293a2016-08-30 12:35:05 -0700227 shouldMakeAecDump:(BOOL)shouldMakeAecDump
228 shouldUseLevelControl:(BOOL)shouldUseLevelControl {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000229 NSParameterAssert(roomId.length);
230 NSParameterAssert(_state == kARDAppClientStateDisconnected);
haysc913e6452015-10-02 11:44:03 -0700231 _isLoopback = isLoopback;
232 _isAudioOnly = isAudioOnly;
peah5085b0c2016-08-25 22:15:14 -0700233 _shouldMakeAecDump = shouldMakeAecDump;
tkchinab1293a2016-08-30 12:35:05 -0700234 _shouldUseLevelControl = shouldUseLevelControl;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000235 self.state = kARDAppClientStateConnecting;
236
tkchind1fb26d2016-02-03 01:51:18 -0800237#if defined(WEBRTC_IOS)
238 if (kARDAppClientEnableTracing) {
tkchin204177f2016-06-14 15:03:11 -0700239 NSString *filePath = [self documentsFilePathForFileName:@"webrtc-trace.txt"];
tkchind1fb26d2016-02-03 01:51:18 -0800240 RTCStartInternalCapture(filePath);
241 }
242#endif
243
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000244 // Request TURN.
245 __weak ARDAppClient *weakSelf = self;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000246 [_turnClient requestServersWithCompletionHandler:^(NSArray *turnServers,
247 NSError *error) {
248 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700249 RTCLogError("Error retrieving TURN servers: %@",
250 error.localizedDescription);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000251 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000252 ARDAppClient *strongSelf = weakSelf;
253 [strongSelf.iceServers addObjectsFromArray:turnServers];
254 strongSelf.isTurnComplete = YES;
255 [strongSelf startSignalingIfReady];
256 }];
jiayl@webrtc.org27f53172014-12-31 00:26:20 +0000257
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000258 // Join room on room server.
259 [_roomServerClient joinRoomWithRoomId:roomId
haysc913e6452015-10-02 11:44:03 -0700260 isLoopback:isLoopback
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000261 completionHandler:^(ARDJoinResponse *response, NSError *error) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000262 ARDAppClient *strongSelf = weakSelf;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000263 if (error) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000264 [strongSelf.delegate appClient:strongSelf didError:error];
265 return;
266 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000267 NSError *joinError =
268 [[strongSelf class] errorForJoinResultType:response.result];
269 if (joinError) {
tkchinc3f46a92015-07-23 12:50:55 -0700270 RTCLogError(@"Failed to join room:%@ on room server.", roomId);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000271 [strongSelf disconnect];
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000272 [strongSelf.delegate appClient:strongSelf didError:joinError];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000273 return;
274 }
tkchinc3f46a92015-07-23 12:50:55 -0700275 RTCLog(@"Joined room:%@ on room server.", roomId);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000276 strongSelf.roomId = response.roomId;
277 strongSelf.clientId = response.clientId;
278 strongSelf.isInitiator = response.isInitiator;
279 for (ARDSignalingMessage *message in response.messages) {
280 if (message.type == kARDSignalingMessageTypeOffer ||
281 message.type == kARDSignalingMessageTypeAnswer) {
282 strongSelf.hasReceivedSdp = YES;
283 [strongSelf.messageQueue insertObject:message atIndex:0];
284 } else {
285 [strongSelf.messageQueue addObject:message];
286 }
287 }
288 strongSelf.webSocketURL = response.webSocketURL;
289 strongSelf.webSocketRestURL = response.webSocketRestURL;
290 [strongSelf registerWithColliderIfReady];
291 [strongSelf startSignalingIfReady];
292 }];
293}
294
295- (void)disconnect {
296 if (_state == kARDAppClientStateDisconnected) {
297 return;
298 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000299 if (self.hasJoinedRoomServerRoom) {
300 [_roomServerClient leaveRoomWithRoomId:_roomId
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000301 clientId:_clientId
302 completionHandler:nil];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000303 }
304 if (_channel) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000305 if (_channel.state == kARDSignalingChannelStateRegistered) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000306 // Tell the other client we're hanging up.
307 ARDByeMessage *byeMessage = [[ARDByeMessage alloc] init];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000308 [_channel sendMessage:byeMessage];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000309 }
310 // Disconnect from collider.
311 _channel = nil;
312 }
313 _clientId = nil;
314 _roomId = nil;
315 _isInitiator = NO;
316 _hasReceivedSdp = NO;
317 _messageQueue = [NSMutableArray array];
ivoc14d5dbe2016-07-04 07:06:55 -0700318#if defined(WEBRTC_IOS)
peah5085b0c2016-08-25 22:15:14 -0700319 if (_isAecDumpActive) {
320 [_factory stopAecDump];
321 _isAecDumpActive = NO;
322 }
ivoc14d5dbe2016-07-04 07:06:55 -0700323 [_peerConnection stopRtcEventLog];
324#endif
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000325 _peerConnection = nil;
326 self.state = kARDAppClientStateDisconnected;
tkchind1fb26d2016-02-03 01:51:18 -0800327#if defined(WEBRTC_IOS)
328 RTCStopInternalCapture();
329#endif
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000330}
331
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000332#pragma mark - ARDSignalingChannelDelegate
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000333
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000334- (void)channel:(id<ARDSignalingChannel>)channel
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000335 didReceiveMessage:(ARDSignalingMessage *)message {
336 switch (message.type) {
337 case kARDSignalingMessageTypeOffer:
338 case kARDSignalingMessageTypeAnswer:
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000339 // Offers and answers must be processed before any other message, so we
340 // place them at the front of the queue.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000341 _hasReceivedSdp = YES;
342 [_messageQueue insertObject:message atIndex:0];
343 break;
344 case kARDSignalingMessageTypeCandidate:
Honghai Zhangda2ba4d2016-05-23 11:53:14 -0700345 case kARDSignalingMessageTypeCandidateRemoval:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000346 [_messageQueue addObject:message];
347 break;
348 case kARDSignalingMessageTypeBye:
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000349 // Disconnects can be processed immediately.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000350 [self processSignalingMessage:message];
351 return;
352 }
353 [self drainMessageQueueIfReady];
354}
355
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000356- (void)channel:(id<ARDSignalingChannel>)channel
357 didChangeState:(ARDSignalingChannelState)state {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000358 switch (state) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000359 case kARDSignalingChannelStateOpen:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000360 break;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000361 case kARDSignalingChannelStateRegistered:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000362 break;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000363 case kARDSignalingChannelStateClosed:
364 case kARDSignalingChannelStateError:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000365 // TODO(tkchin): reconnection scenarios. Right now we just disconnect
366 // completely if the websocket connection fails.
367 [self disconnect];
368 break;
369 }
370}
371
372#pragma mark - RTCPeerConnectionDelegate
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000373// Callbacks for this delegate occur on non-main thread and need to be
374// dispatched back to main queue as needed.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000375
376- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700377 didChangeSignalingState:(RTCSignalingState)stateChanged {
378 RTCLog(@"Signaling state changed: %ld", (long)stateChanged);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000379}
380
381- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700382 didAddStream:(RTCMediaStream *)stream {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000383 dispatch_async(dispatch_get_main_queue(), ^{
tkchinc3f46a92015-07-23 12:50:55 -0700384 RTCLog(@"Received %lu video tracks and %lu audio tracks",
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000385 (unsigned long)stream.videoTracks.count,
386 (unsigned long)stream.audioTracks.count);
387 if (stream.videoTracks.count) {
388 RTCVideoTrack *videoTrack = stream.videoTracks[0];
389 [_delegate appClient:self didReceiveRemoteVideoTrack:videoTrack];
390 }
391 });
392}
393
394- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700395 didRemoveStream:(RTCMediaStream *)stream {
tkchinc3f46a92015-07-23 12:50:55 -0700396 RTCLog(@"Stream was removed.");
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000397}
398
hjon79858f82016-03-13 22:08:26 -0700399- (void)peerConnectionShouldNegotiate:(RTCPeerConnection *)peerConnection {
tkchinc3f46a92015-07-23 12:50:55 -0700400 RTCLog(@"WARNING: Renegotiation needed but unimplemented.");
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000401}
402
403- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700404 didChangeIceConnectionState:(RTCIceConnectionState)newState {
405 RTCLog(@"ICE state changed: %ld", (long)newState);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000406 dispatch_async(dispatch_get_main_queue(), ^{
407 [_delegate appClient:self didChangeConnectionState:newState];
408 });
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000409}
410
411- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700412 didChangeIceGatheringState:(RTCIceGatheringState)newState {
413 RTCLog(@"ICE gathering state changed: %ld", (long)newState);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000414}
415
416- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700417 didGenerateIceCandidate:(RTCIceCandidate *)candidate {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000418 dispatch_async(dispatch_get_main_queue(), ^{
419 ARDICECandidateMessage *message =
420 [[ARDICECandidateMessage alloc] initWithCandidate:candidate];
421 [self sendSignalingMessage:message];
422 });
423}
424
Zeke Chind3325802015-08-14 11:00:02 -0700425- (void)peerConnection:(RTCPeerConnection *)peerConnection
Honghai Zhangda2ba4d2016-05-23 11:53:14 -0700426 didRemoveIceCandidates:(NSArray<RTCIceCandidate *> *)candidates {
427 dispatch_async(dispatch_get_main_queue(), ^{
428 ARDICECandidateRemovalMessage *message =
429 [[ARDICECandidateRemovalMessage alloc]
430 initWithRemovedCandidates:candidates];
431 [self sendSignalingMessage:message];
432 });
433}
434
435- (void)peerConnection:(RTCPeerConnection *)peerConnection
Zeke Chind3325802015-08-14 11:00:02 -0700436 didOpenDataChannel:(RTCDataChannel *)dataChannel {
437}
438
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000439#pragma mark - RTCSessionDescriptionDelegate
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000440// Callbacks for this delegate occur on non-main thread and need to be
441// dispatched back to main queue as needed.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000442
443- (void)peerConnection:(RTCPeerConnection *)peerConnection
444 didCreateSessionDescription:(RTCSessionDescription *)sdp
445 error:(NSError *)error {
446 dispatch_async(dispatch_get_main_queue(), ^{
447 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700448 RTCLogError(@"Failed to create session description. Error: %@", error);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000449 [self disconnect];
450 NSDictionary *userInfo = @{
451 NSLocalizedDescriptionKey: @"Failed to create session description.",
452 };
453 NSError *sdpError =
454 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
455 code:kARDAppClientErrorCreateSDP
456 userInfo:userInfo];
457 [_delegate appClient:self didError:sdpError];
458 return;
459 }
Zeke Chin71f6f442015-06-29 14:34:58 -0700460 // Prefer H264 if available.
461 RTCSessionDescription *sdpPreferringH264 =
462 [ARDSDPUtils descriptionForDescription:sdp
463 preferredVideoCodec:@"H264"];
hjon79858f82016-03-13 22:08:26 -0700464 __weak ARDAppClient *weakSelf = self;
465 [_peerConnection setLocalDescription:sdpPreferringH264
466 completionHandler:^(NSError *error) {
467 ARDAppClient *strongSelf = weakSelf;
468 [strongSelf peerConnection:strongSelf.peerConnection
469 didSetSessionDescriptionWithError:error];
470 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000471 ARDSessionDescriptionMessage *message =
Zeke Chin71f6f442015-06-29 14:34:58 -0700472 [[ARDSessionDescriptionMessage alloc]
473 initWithDescription:sdpPreferringH264];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000474 [self sendSignalingMessage:message];
475 });
476}
477
478- (void)peerConnection:(RTCPeerConnection *)peerConnection
479 didSetSessionDescriptionWithError:(NSError *)error {
480 dispatch_async(dispatch_get_main_queue(), ^{
481 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700482 RTCLogError(@"Failed to set session description. Error: %@", error);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000483 [self disconnect];
484 NSDictionary *userInfo = @{
485 NSLocalizedDescriptionKey: @"Failed to set session description.",
486 };
487 NSError *sdpError =
488 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
489 code:kARDAppClientErrorSetSDP
490 userInfo:userInfo];
491 [_delegate appClient:self didError:sdpError];
492 return;
493 }
494 // If we're answering and we've just set the remote offer we need to create
495 // an answer and set the local description.
496 if (!_isInitiator && !_peerConnection.localDescription) {
497 RTCMediaConstraints *constraints = [self defaultAnswerConstraints];
hjon79858f82016-03-13 22:08:26 -0700498 __weak ARDAppClient *weakSelf = self;
499 [_peerConnection answerForConstraints:constraints
500 completionHandler:^(RTCSessionDescription *sdp,
501 NSError *error) {
502 ARDAppClient *strongSelf = weakSelf;
503 [strongSelf peerConnection:strongSelf.peerConnection
504 didCreateSessionDescription:sdp
505 error:error];
506 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000507 }
508 });
509}
510
511#pragma mark - Private
512
tkchin204177f2016-06-14 15:03:11 -0700513#if defined(WEBRTC_IOS)
514
515- (NSString *)documentsFilePathForFileName:(NSString *)fileName {
516 NSParameterAssert(fileName.length);
517 NSArray *paths = NSSearchPathForDirectoriesInDomains(
518 NSDocumentDirectory, NSUserDomainMask, YES);
519 NSString *documentsDirPath = paths.firstObject;
520 NSString *filePath =
521 [documentsDirPath stringByAppendingPathComponent:fileName];
522 return filePath;
523}
524
525#endif
526
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000527- (BOOL)hasJoinedRoomServerRoom {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000528 return _clientId.length;
529}
530
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000531// Begins the peer connection connection process if we have both joined a room
532// on the room server and tried to obtain a TURN server. Otherwise does nothing.
533// A peer connection object will be created with a stream that contains local
534// audio and video capture. If this client is the caller, an offer is created as
535// well, otherwise the client will wait for an offer to arrive.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000536- (void)startSignalingIfReady {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000537 if (!_isTurnComplete || !self.hasJoinedRoomServerRoom) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000538 return;
539 }
540 self.state = kARDAppClientStateConnected;
541
542 // Create peer connection.
543 RTCMediaConstraints *constraints = [self defaultPeerConnectionConstraints];
Zeke Chinbc7dd7e2015-05-29 14:59:14 -0700544 RTCConfiguration *config = [[RTCConfiguration alloc] init];
545 config.iceServers = _iceServers;
Tze Kwang Chinf3cb49f2016-03-22 10:57:40 -0700546 _peerConnection = [_factory peerConnectionWithConfiguration:config
547 constraints:constraints
548 delegate:self];
skvladf3569c82016-04-29 15:30:16 -0700549 // Create AV senders.
550 [self createAudioSender];
551 [self createVideoSender];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000552 if (_isInitiator) {
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000553 // Send offer.
hjon79858f82016-03-13 22:08:26 -0700554 __weak ARDAppClient *weakSelf = self;
555 [_peerConnection offerForConstraints:[self defaultOfferConstraints]
556 completionHandler:^(RTCSessionDescription *sdp,
557 NSError *error) {
558 ARDAppClient *strongSelf = weakSelf;
559 [strongSelf peerConnection:strongSelf.peerConnection
560 didCreateSessionDescription:sdp
561 error:error];
562 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000563 } else {
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000564 // Check if we've received an offer.
565 [self drainMessageQueueIfReady];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000566 }
ivoc14d5dbe2016-07-04 07:06:55 -0700567#if defined(WEBRTC_IOS)
568 // Start event log.
569 if (kARDAppClientEnableRtcEventLog) {
570 NSString *filePath = [self documentsFilePathForFileName:@"webrtc-rtceventlog"];
571 if (![_peerConnection startRtcEventLogWithFilePath:filePath
572 maxSizeInBytes:kARDAppClientRtcEventLogMaxSizeInBytes]) {
573 RTCLogError(@"Failed to start event logging.");
574 }
575 }
peah5085b0c2016-08-25 22:15:14 -0700576
577 // Start aecdump diagnostic recording.
578 if (_shouldMakeAecDump) {
579 _isAecDumpActive = YES;
580 NSString *filePath = [self documentsFilePathForFileName:@"audio.aecdump"];
581 int fd = open(filePath.UTF8String, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
582 if (fd < 0) {
583 RTCLogError(@"Failed to create the aecdump file!");
584 _isAecDumpActive = NO;
585 } else {
586 if (![_factory startAecDumpWithFileDescriptor:fd maxFileSizeInBytes:-1]) {
587 RTCLogError(@"Failed to create aecdump.");
588 _isAecDumpActive = NO;
589 }
590 }
591 }
ivoc14d5dbe2016-07-04 07:06:55 -0700592#endif
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000593}
594
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000595// Processes the messages that we've received from the room server and the
596// signaling channel. The offer or answer message must be processed before other
597// signaling messages, however they can arrive out of order. Hence, this method
598// only processes pending messages if there is a peer connection object and
599// if we have received either an offer or answer.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000600- (void)drainMessageQueueIfReady {
601 if (!_peerConnection || !_hasReceivedSdp) {
602 return;
603 }
604 for (ARDSignalingMessage *message in _messageQueue) {
605 [self processSignalingMessage:message];
606 }
607 [_messageQueue removeAllObjects];
608}
609
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000610// Processes the given signaling message based on its type.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000611- (void)processSignalingMessage:(ARDSignalingMessage *)message {
612 NSParameterAssert(_peerConnection ||
613 message.type == kARDSignalingMessageTypeBye);
614 switch (message.type) {
615 case kARDSignalingMessageTypeOffer:
616 case kARDSignalingMessageTypeAnswer: {
617 ARDSessionDescriptionMessage *sdpMessage =
618 (ARDSessionDescriptionMessage *)message;
619 RTCSessionDescription *description = sdpMessage.sessionDescription;
Zeke Chin71f6f442015-06-29 14:34:58 -0700620 // Prefer H264 if available.
621 RTCSessionDescription *sdpPreferringH264 =
622 [ARDSDPUtils descriptionForDescription:description
623 preferredVideoCodec:@"H264"];
hjon79858f82016-03-13 22:08:26 -0700624 __weak ARDAppClient *weakSelf = self;
625 [_peerConnection setRemoteDescription:sdpPreferringH264
626 completionHandler:^(NSError *error) {
627 ARDAppClient *strongSelf = weakSelf;
628 [strongSelf peerConnection:strongSelf.peerConnection
629 didSetSessionDescriptionWithError:error];
630 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000631 break;
632 }
633 case kARDSignalingMessageTypeCandidate: {
634 ARDICECandidateMessage *candidateMessage =
635 (ARDICECandidateMessage *)message;
hjon79858f82016-03-13 22:08:26 -0700636 [_peerConnection addIceCandidate:candidateMessage.candidate];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000637 break;
638 }
Honghai Zhangda2ba4d2016-05-23 11:53:14 -0700639 case kARDSignalingMessageTypeCandidateRemoval: {
640 ARDICECandidateRemovalMessage *candidateMessage =
641 (ARDICECandidateRemovalMessage *)message;
642 [_peerConnection removeIceCandidates:candidateMessage.candidates];
643 break;
644 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000645 case kARDSignalingMessageTypeBye:
646 // Other client disconnected.
647 // TODO(tkchin): support waiting in room for next client. For now just
648 // disconnect.
649 [self disconnect];
650 break;
651 }
652}
653
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000654// Sends a signaling message to the other client. The caller will send messages
655// through the room server, whereas the callee will send messages over the
656// signaling channel.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000657- (void)sendSignalingMessage:(ARDSignalingMessage *)message {
658 if (_isInitiator) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000659 __weak ARDAppClient *weakSelf = self;
660 [_roomServerClient sendMessage:message
661 forRoomId:_roomId
662 clientId:_clientId
663 completionHandler:^(ARDMessageResponse *response,
664 NSError *error) {
665 ARDAppClient *strongSelf = weakSelf;
666 if (error) {
667 [strongSelf.delegate appClient:strongSelf didError:error];
668 return;
669 }
670 NSError *messageError =
671 [[strongSelf class] errorForMessageResultType:response.result];
672 if (messageError) {
673 [strongSelf.delegate appClient:strongSelf didError:messageError];
674 return;
675 }
676 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000677 } else {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000678 [_channel sendMessage:message];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000679 }
680}
681
skvladf3569c82016-04-29 15:30:16 -0700682- (RTCRtpSender *)createVideoSender {
683 RTCRtpSender *sender =
684 [_peerConnection senderWithKind:kRTCMediaStreamTrackKindVideo
685 streamId:kARDMediaStreamId];
686 RTCVideoTrack *track = [self createLocalVideoTrack];
687 if (track) {
688 sender.track = track;
689 [_delegate appClient:self didReceiveLocalVideoTrack:track];
Zeke Chin57cc74e2015-05-05 07:52:31 -0700690 }
skvladf3569c82016-04-29 15:30:16 -0700691 return sender;
692}
693
694- (RTCRtpSender *)createAudioSender {
tkchinab1293a2016-08-30 12:35:05 -0700695 RTCMediaConstraints *constraints = [self defaultMediaAudioConstraints];
696 RTCAudioSource *source = [_factory audioSourceWithConstraints:constraints];
697 RTCAudioTrack *track = [_factory audioTrackWithSource:source
698 trackId:kARDAudioTrackId];
skvladf3569c82016-04-29 15:30:16 -0700699 RTCRtpSender *sender =
700 [_peerConnection senderWithKind:kRTCMediaStreamTrackKindAudio
701 streamId:kARDMediaStreamId];
skvladf3569c82016-04-29 15:30:16 -0700702 sender.track = track;
703 return sender;
Zeke Chin57cc74e2015-05-05 07:52:31 -0700704}
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000705
Zeke Chin57cc74e2015-05-05 07:52:31 -0700706- (RTCVideoTrack *)createLocalVideoTrack {
707 RTCVideoTrack* localVideoTrack = nil;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000708 // The iOS simulator doesn't provide any sort of camera capture
709 // support or emulation (http://goo.gl/rHAnC1) so don't bother
710 // trying to open a local stream.
711 // TODO(tkchin): local video capture for OSX. See
712 // https://code.google.com/p/webrtc/issues/detail?id=3417.
713#if !TARGET_IPHONE_SIMULATOR && TARGET_OS_IPHONE
haysc913e6452015-10-02 11:44:03 -0700714 if (!_isAudioOnly) {
715 RTCMediaConstraints *mediaConstraints =
716 [self defaultMediaStreamConstraints];
717 RTCAVFoundationVideoSource *source =
Tze Kwang Chinf3cb49f2016-03-22 10:57:40 -0700718 [_factory avFoundationVideoSourceWithConstraints:mediaConstraints];
haysc913e6452015-10-02 11:44:03 -0700719 localVideoTrack =
Tze Kwang Chinf3cb49f2016-03-22 10:57:40 -0700720 [_factory videoTrackWithSource:source
skvladf3569c82016-04-29 15:30:16 -0700721 trackId:kARDVideoTrackId];
haysc913e6452015-10-02 11:44:03 -0700722 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000723#endif
Zeke Chin57cc74e2015-05-05 07:52:31 -0700724 return localVideoTrack;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000725}
726
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000727#pragma mark - Collider methods
728
729- (void)registerWithColliderIfReady {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000730 if (!self.hasJoinedRoomServerRoom) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000731 return;
732 }
733 // Open WebSocket connection.
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000734 if (!_channel) {
735 _channel =
736 [[ARDWebSocketChannel alloc] initWithURL:_websocketURL
737 restURL:_websocketRestURL
738 delegate:self];
haysc913e6452015-10-02 11:44:03 -0700739 if (_isLoopback) {
740 _loopbackChannel =
741 [[ARDLoopbackWebSocketChannel alloc] initWithURL:_websocketURL
742 restURL:_websocketRestURL];
743 }
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000744 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000745 [_channel registerForRoomId:_roomId clientId:_clientId];
haysc913e6452015-10-02 11:44:03 -0700746 if (_isLoopback) {
747 [_loopbackChannel registerForRoomId:_roomId clientId:@"LOOPBACK_CLIENT_ID"];
748 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000749}
750
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000751#pragma mark - Defaults
752
tkchinab1293a2016-08-30 12:35:05 -0700753 - (RTCMediaConstraints *)defaultMediaAudioConstraints {
754 NSString *valueLevelControl = _shouldUseLevelControl ?
755 kRTCMediaConstraintsValueTrue : kRTCMediaConstraintsValueFalse;
756 NSDictionary *mandatoryConstraints = @{ kRTCMediaConstraintsLevelControl : valueLevelControl };
757 RTCMediaConstraints* constraints =
758 [[RTCMediaConstraints alloc] initWithMandatoryConstraints:mandatoryConstraints
759 optionalConstraints:nil];
760 return constraints;
761}
762
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000763- (RTCMediaConstraints *)defaultMediaStreamConstraints {
764 RTCMediaConstraints* constraints =
765 [[RTCMediaConstraints alloc]
766 initWithMandatoryConstraints:nil
767 optionalConstraints:nil];
768 return constraints;
769}
770
771- (RTCMediaConstraints *)defaultAnswerConstraints {
772 return [self defaultOfferConstraints];
773}
774
775- (RTCMediaConstraints *)defaultOfferConstraints {
hjon79858f82016-03-13 22:08:26 -0700776 NSDictionary *mandatoryConstraints = @{
777 @"OfferToReceiveAudio" : @"true",
778 @"OfferToReceiveVideo" : @"true"
779 };
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000780 RTCMediaConstraints* constraints =
781 [[RTCMediaConstraints alloc]
782 initWithMandatoryConstraints:mandatoryConstraints
783 optionalConstraints:nil];
784 return constraints;
785}
786
787- (RTCMediaConstraints *)defaultPeerConnectionConstraints {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000788 if (_defaultPeerConnectionConstraints) {
789 return _defaultPeerConnectionConstraints;
790 }
haysc913e6452015-10-02 11:44:03 -0700791 NSString *value = _isLoopback ? @"false" : @"true";
hjon79858f82016-03-13 22:08:26 -0700792 NSDictionary *optionalConstraints = @{ @"DtlsSrtpKeyAgreement" : value };
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000793 RTCMediaConstraints* constraints =
794 [[RTCMediaConstraints alloc]
795 initWithMandatoryConstraints:nil
796 optionalConstraints:optionalConstraints];
797 return constraints;
798}
799
hjon79858f82016-03-13 22:08:26 -0700800- (RTCIceServer *)defaultSTUNServer {
801 return [[RTCIceServer alloc] initWithURLStrings:@[kARDDefaultSTUNServerUrl]
802 username:@""
803 credential:@""];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000804}
805
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000806#pragma mark - Errors
807
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000808+ (NSError *)errorForJoinResultType:(ARDJoinResultType)resultType {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000809 NSError *error = nil;
810 switch (resultType) {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000811 case kARDJoinResultTypeSuccess:
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000812 break;
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000813 case kARDJoinResultTypeUnknown: {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000814 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
815 code:kARDAppClientErrorUnknown
816 userInfo:@{
817 NSLocalizedDescriptionKey: @"Unknown error.",
818 }];
819 break;
820 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000821 case kARDJoinResultTypeFull: {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000822 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
823 code:kARDAppClientErrorRoomFull
824 userInfo:@{
825 NSLocalizedDescriptionKey: @"Room is full.",
826 }];
827 break;
828 }
829 }
830 return error;
831}
832
833+ (NSError *)errorForMessageResultType:(ARDMessageResultType)resultType {
834 NSError *error = nil;
835 switch (resultType) {
836 case kARDMessageResultTypeSuccess:
837 break;
838 case kARDMessageResultTypeUnknown:
839 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
840 code:kARDAppClientErrorUnknown
841 userInfo:@{
842 NSLocalizedDescriptionKey: @"Unknown error.",
843 }];
844 break;
845 case kARDMessageResultTypeInvalidClient:
846 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
847 code:kARDAppClientErrorInvalidClient
848 userInfo:@{
849 NSLocalizedDescriptionKey: @"Invalid client.",
850 }];
851 break;
852 case kARDMessageResultTypeInvalidRoom:
853 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
854 code:kARDAppClientErrorInvalidRoom
855 userInfo:@{
856 NSLocalizedDescriptionKey: @"Invalid room.",
857 }];
858 break;
859 }
860 return error;
861}
862
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000863@end