blob: a9dd8b15fbcd377e4099f7e53c6db92e0d81d119 [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;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000134
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000135- (instancetype)init {
136 if (self = [super init]) {
137 _roomServerClient = [[ARDAppEngineClient alloc] init];
138 NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl];
139 _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL];
140 [self configure];
141 }
142 return self;
143}
144
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000145- (instancetype)initWithDelegate:(id<ARDAppClientDelegate>)delegate {
146 if (self = [super init]) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000147 _roomServerClient = [[ARDAppEngineClient alloc] init];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000148 _delegate = delegate;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000149 NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl];
150 _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL];
151 [self configure];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000152 }
153 return self;
154}
155
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000156// TODO(tkchin): Provide signaling channel factory interface so we can recreate
157// channel if we need to on network failure. Also, make this the default public
158// constructor.
159- (instancetype)initWithRoomServerClient:(id<ARDRoomServerClient>)rsClient
160 signalingChannel:(id<ARDSignalingChannel>)channel
161 turnClient:(id<ARDTURNClient>)turnClient
162 delegate:(id<ARDAppClientDelegate>)delegate {
163 NSParameterAssert(rsClient);
164 NSParameterAssert(channel);
165 NSParameterAssert(turnClient);
166 if (self = [super init]) {
167 _roomServerClient = rsClient;
168 _channel = channel;
169 _turnClient = turnClient;
170 _delegate = delegate;
171 [self configure];
172 }
173 return self;
174}
175
176- (void)configure {
177 _factory = [[RTCPeerConnectionFactory alloc] init];
178 _messageQueue = [NSMutableArray array];
179 _iceServers = [NSMutableArray arrayWithObject:[self defaultSTUNServer]];
Zeke Chin2d3b7e22015-07-14 12:55:44 -0700180 _fileLogger = [[RTCFileLogger alloc] init];
181 [_fileLogger start];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000182}
183
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000184- (void)dealloc {
Zeke Chind3325802015-08-14 11:00:02 -0700185 self.shouldGetStats = NO;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000186 [self disconnect];
187}
188
Zeke Chind3325802015-08-14 11:00:02 -0700189- (void)setShouldGetStats:(BOOL)shouldGetStats {
190 if (_shouldGetStats == shouldGetStats) {
191 return;
192 }
193 if (shouldGetStats) {
194 __weak ARDAppClient *weakSelf = self;
195 _statsTimer = [[ARDTimerProxy alloc] initWithInterval:1
196 repeats:YES
197 timerHandler:^{
198 ARDAppClient *strongSelf = weakSelf;
hjon79858f82016-03-13 22:08:26 -0700199 [strongSelf.peerConnection statsForTrack:nil
200 statsOutputLevel:RTCStatsOutputLevelDebug
201 completionHandler:^(NSArray *stats) {
202 dispatch_async(dispatch_get_main_queue(), ^{
203 ARDAppClient *strongSelf = weakSelf;
204 [strongSelf.delegate appClient:strongSelf didGetStats:stats];
205 });
206 }];
Zeke Chind3325802015-08-14 11:00:02 -0700207 }];
208 } else {
209 [_statsTimer invalidate];
210 _statsTimer = nil;
211 }
212 _shouldGetStats = shouldGetStats;
213}
214
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000215- (void)setState:(ARDAppClientState)state {
216 if (_state == state) {
217 return;
218 }
219 _state = state;
220 [_delegate appClient:self didChangeState:_state];
221}
222
223- (void)connectToRoomWithId:(NSString *)roomId
haysc913e6452015-10-02 11:44:03 -0700224 isLoopback:(BOOL)isLoopback
peah5085b0c2016-08-25 22:15:14 -0700225 isAudioOnly:(BOOL)isAudioOnly
226 shouldMakeAecDump:(BOOL)shouldMakeAecDump {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000227 NSParameterAssert(roomId.length);
228 NSParameterAssert(_state == kARDAppClientStateDisconnected);
haysc913e6452015-10-02 11:44:03 -0700229 _isLoopback = isLoopback;
230 _isAudioOnly = isAudioOnly;
peah5085b0c2016-08-25 22:15:14 -0700231 _shouldMakeAecDump = shouldMakeAecDump;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000232 self.state = kARDAppClientStateConnecting;
233
tkchind1fb26d2016-02-03 01:51:18 -0800234#if defined(WEBRTC_IOS)
235 if (kARDAppClientEnableTracing) {
tkchin204177f2016-06-14 15:03:11 -0700236 NSString *filePath = [self documentsFilePathForFileName:@"webrtc-trace.txt"];
tkchind1fb26d2016-02-03 01:51:18 -0800237 RTCStartInternalCapture(filePath);
238 }
239#endif
240
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000241 // Request TURN.
242 __weak ARDAppClient *weakSelf = self;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000243 [_turnClient requestServersWithCompletionHandler:^(NSArray *turnServers,
244 NSError *error) {
245 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700246 RTCLogError("Error retrieving TURN servers: %@",
247 error.localizedDescription);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000248 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000249 ARDAppClient *strongSelf = weakSelf;
250 [strongSelf.iceServers addObjectsFromArray:turnServers];
251 strongSelf.isTurnComplete = YES;
252 [strongSelf startSignalingIfReady];
253 }];
jiayl@webrtc.org27f53172014-12-31 00:26:20 +0000254
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000255 // Join room on room server.
256 [_roomServerClient joinRoomWithRoomId:roomId
haysc913e6452015-10-02 11:44:03 -0700257 isLoopback:isLoopback
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000258 completionHandler:^(ARDJoinResponse *response, NSError *error) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000259 ARDAppClient *strongSelf = weakSelf;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000260 if (error) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000261 [strongSelf.delegate appClient:strongSelf didError:error];
262 return;
263 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000264 NSError *joinError =
265 [[strongSelf class] errorForJoinResultType:response.result];
266 if (joinError) {
tkchinc3f46a92015-07-23 12:50:55 -0700267 RTCLogError(@"Failed to join room:%@ on room server.", roomId);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000268 [strongSelf disconnect];
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000269 [strongSelf.delegate appClient:strongSelf didError:joinError];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000270 return;
271 }
tkchinc3f46a92015-07-23 12:50:55 -0700272 RTCLog(@"Joined room:%@ on room server.", roomId);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000273 strongSelf.roomId = response.roomId;
274 strongSelf.clientId = response.clientId;
275 strongSelf.isInitiator = response.isInitiator;
276 for (ARDSignalingMessage *message in response.messages) {
277 if (message.type == kARDSignalingMessageTypeOffer ||
278 message.type == kARDSignalingMessageTypeAnswer) {
279 strongSelf.hasReceivedSdp = YES;
280 [strongSelf.messageQueue insertObject:message atIndex:0];
281 } else {
282 [strongSelf.messageQueue addObject:message];
283 }
284 }
285 strongSelf.webSocketURL = response.webSocketURL;
286 strongSelf.webSocketRestURL = response.webSocketRestURL;
287 [strongSelf registerWithColliderIfReady];
288 [strongSelf startSignalingIfReady];
289 }];
290}
291
292- (void)disconnect {
293 if (_state == kARDAppClientStateDisconnected) {
294 return;
295 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000296 if (self.hasJoinedRoomServerRoom) {
297 [_roomServerClient leaveRoomWithRoomId:_roomId
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000298 clientId:_clientId
299 completionHandler:nil];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000300 }
301 if (_channel) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000302 if (_channel.state == kARDSignalingChannelStateRegistered) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000303 // Tell the other client we're hanging up.
304 ARDByeMessage *byeMessage = [[ARDByeMessage alloc] init];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000305 [_channel sendMessage:byeMessage];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000306 }
307 // Disconnect from collider.
308 _channel = nil;
309 }
310 _clientId = nil;
311 _roomId = nil;
312 _isInitiator = NO;
313 _hasReceivedSdp = NO;
314 _messageQueue = [NSMutableArray array];
ivoc14d5dbe2016-07-04 07:06:55 -0700315#if defined(WEBRTC_IOS)
peah5085b0c2016-08-25 22:15:14 -0700316 if (_isAecDumpActive) {
317 [_factory stopAecDump];
318 _isAecDumpActive = NO;
319 }
ivoc14d5dbe2016-07-04 07:06:55 -0700320 [_peerConnection stopRtcEventLog];
321#endif
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000322 _peerConnection = nil;
323 self.state = kARDAppClientStateDisconnected;
tkchind1fb26d2016-02-03 01:51:18 -0800324#if defined(WEBRTC_IOS)
325 RTCStopInternalCapture();
326#endif
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000327}
328
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000329#pragma mark - ARDSignalingChannelDelegate
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000330
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000331- (void)channel:(id<ARDSignalingChannel>)channel
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000332 didReceiveMessage:(ARDSignalingMessage *)message {
333 switch (message.type) {
334 case kARDSignalingMessageTypeOffer:
335 case kARDSignalingMessageTypeAnswer:
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000336 // Offers and answers must be processed before any other message, so we
337 // place them at the front of the queue.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000338 _hasReceivedSdp = YES;
339 [_messageQueue insertObject:message atIndex:0];
340 break;
341 case kARDSignalingMessageTypeCandidate:
Honghai Zhangda2ba4d2016-05-23 11:53:14 -0700342 case kARDSignalingMessageTypeCandidateRemoval:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000343 [_messageQueue addObject:message];
344 break;
345 case kARDSignalingMessageTypeBye:
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000346 // Disconnects can be processed immediately.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000347 [self processSignalingMessage:message];
348 return;
349 }
350 [self drainMessageQueueIfReady];
351}
352
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000353- (void)channel:(id<ARDSignalingChannel>)channel
354 didChangeState:(ARDSignalingChannelState)state {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000355 switch (state) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000356 case kARDSignalingChannelStateOpen:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000357 break;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000358 case kARDSignalingChannelStateRegistered:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000359 break;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000360 case kARDSignalingChannelStateClosed:
361 case kARDSignalingChannelStateError:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000362 // TODO(tkchin): reconnection scenarios. Right now we just disconnect
363 // completely if the websocket connection fails.
364 [self disconnect];
365 break;
366 }
367}
368
369#pragma mark - RTCPeerConnectionDelegate
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000370// Callbacks for this delegate occur on non-main thread and need to be
371// dispatched back to main queue as needed.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000372
373- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700374 didChangeSignalingState:(RTCSignalingState)stateChanged {
375 RTCLog(@"Signaling state changed: %ld", (long)stateChanged);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000376}
377
378- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700379 didAddStream:(RTCMediaStream *)stream {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000380 dispatch_async(dispatch_get_main_queue(), ^{
tkchinc3f46a92015-07-23 12:50:55 -0700381 RTCLog(@"Received %lu video tracks and %lu audio tracks",
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000382 (unsigned long)stream.videoTracks.count,
383 (unsigned long)stream.audioTracks.count);
384 if (stream.videoTracks.count) {
385 RTCVideoTrack *videoTrack = stream.videoTracks[0];
386 [_delegate appClient:self didReceiveRemoteVideoTrack:videoTrack];
387 }
388 });
389}
390
391- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700392 didRemoveStream:(RTCMediaStream *)stream {
tkchinc3f46a92015-07-23 12:50:55 -0700393 RTCLog(@"Stream was removed.");
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000394}
395
hjon79858f82016-03-13 22:08:26 -0700396- (void)peerConnectionShouldNegotiate:(RTCPeerConnection *)peerConnection {
tkchinc3f46a92015-07-23 12:50:55 -0700397 RTCLog(@"WARNING: Renegotiation needed but unimplemented.");
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000398}
399
400- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700401 didChangeIceConnectionState:(RTCIceConnectionState)newState {
402 RTCLog(@"ICE state changed: %ld", (long)newState);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000403 dispatch_async(dispatch_get_main_queue(), ^{
404 [_delegate appClient:self didChangeConnectionState:newState];
405 });
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000406}
407
408- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700409 didChangeIceGatheringState:(RTCIceGatheringState)newState {
410 RTCLog(@"ICE gathering state changed: %ld", (long)newState);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000411}
412
413- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700414 didGenerateIceCandidate:(RTCIceCandidate *)candidate {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000415 dispatch_async(dispatch_get_main_queue(), ^{
416 ARDICECandidateMessage *message =
417 [[ARDICECandidateMessage alloc] initWithCandidate:candidate];
418 [self sendSignalingMessage:message];
419 });
420}
421
Zeke Chind3325802015-08-14 11:00:02 -0700422- (void)peerConnection:(RTCPeerConnection *)peerConnection
Honghai Zhangda2ba4d2016-05-23 11:53:14 -0700423 didRemoveIceCandidates:(NSArray<RTCIceCandidate *> *)candidates {
424 dispatch_async(dispatch_get_main_queue(), ^{
425 ARDICECandidateRemovalMessage *message =
426 [[ARDICECandidateRemovalMessage alloc]
427 initWithRemovedCandidates:candidates];
428 [self sendSignalingMessage:message];
429 });
430}
431
432- (void)peerConnection:(RTCPeerConnection *)peerConnection
Zeke Chind3325802015-08-14 11:00:02 -0700433 didOpenDataChannel:(RTCDataChannel *)dataChannel {
434}
435
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000436#pragma mark - RTCSessionDescriptionDelegate
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000437// Callbacks for this delegate occur on non-main thread and need to be
438// dispatched back to main queue as needed.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000439
440- (void)peerConnection:(RTCPeerConnection *)peerConnection
441 didCreateSessionDescription:(RTCSessionDescription *)sdp
442 error:(NSError *)error {
443 dispatch_async(dispatch_get_main_queue(), ^{
444 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700445 RTCLogError(@"Failed to create session description. Error: %@", error);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000446 [self disconnect];
447 NSDictionary *userInfo = @{
448 NSLocalizedDescriptionKey: @"Failed to create session description.",
449 };
450 NSError *sdpError =
451 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
452 code:kARDAppClientErrorCreateSDP
453 userInfo:userInfo];
454 [_delegate appClient:self didError:sdpError];
455 return;
456 }
Zeke Chin71f6f442015-06-29 14:34:58 -0700457 // Prefer H264 if available.
458 RTCSessionDescription *sdpPreferringH264 =
459 [ARDSDPUtils descriptionForDescription:sdp
460 preferredVideoCodec:@"H264"];
hjon79858f82016-03-13 22:08:26 -0700461 __weak ARDAppClient *weakSelf = self;
462 [_peerConnection setLocalDescription:sdpPreferringH264
463 completionHandler:^(NSError *error) {
464 ARDAppClient *strongSelf = weakSelf;
465 [strongSelf peerConnection:strongSelf.peerConnection
466 didSetSessionDescriptionWithError:error];
467 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000468 ARDSessionDescriptionMessage *message =
Zeke Chin71f6f442015-06-29 14:34:58 -0700469 [[ARDSessionDescriptionMessage alloc]
470 initWithDescription:sdpPreferringH264];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000471 [self sendSignalingMessage:message];
472 });
473}
474
475- (void)peerConnection:(RTCPeerConnection *)peerConnection
476 didSetSessionDescriptionWithError:(NSError *)error {
477 dispatch_async(dispatch_get_main_queue(), ^{
478 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700479 RTCLogError(@"Failed to set session description. Error: %@", error);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000480 [self disconnect];
481 NSDictionary *userInfo = @{
482 NSLocalizedDescriptionKey: @"Failed to set session description.",
483 };
484 NSError *sdpError =
485 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
486 code:kARDAppClientErrorSetSDP
487 userInfo:userInfo];
488 [_delegate appClient:self didError:sdpError];
489 return;
490 }
491 // If we're answering and we've just set the remote offer we need to create
492 // an answer and set the local description.
493 if (!_isInitiator && !_peerConnection.localDescription) {
494 RTCMediaConstraints *constraints = [self defaultAnswerConstraints];
hjon79858f82016-03-13 22:08:26 -0700495 __weak ARDAppClient *weakSelf = self;
496 [_peerConnection answerForConstraints:constraints
497 completionHandler:^(RTCSessionDescription *sdp,
498 NSError *error) {
499 ARDAppClient *strongSelf = weakSelf;
500 [strongSelf peerConnection:strongSelf.peerConnection
501 didCreateSessionDescription:sdp
502 error:error];
503 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000504 }
505 });
506}
507
508#pragma mark - Private
509
tkchin204177f2016-06-14 15:03:11 -0700510#if defined(WEBRTC_IOS)
511
512- (NSString *)documentsFilePathForFileName:(NSString *)fileName {
513 NSParameterAssert(fileName.length);
514 NSArray *paths = NSSearchPathForDirectoriesInDomains(
515 NSDocumentDirectory, NSUserDomainMask, YES);
516 NSString *documentsDirPath = paths.firstObject;
517 NSString *filePath =
518 [documentsDirPath stringByAppendingPathComponent:fileName];
519 return filePath;
520}
521
522#endif
523
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000524- (BOOL)hasJoinedRoomServerRoom {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000525 return _clientId.length;
526}
527
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000528// Begins the peer connection connection process if we have both joined a room
529// on the room server and tried to obtain a TURN server. Otherwise does nothing.
530// A peer connection object will be created with a stream that contains local
531// audio and video capture. If this client is the caller, an offer is created as
532// well, otherwise the client will wait for an offer to arrive.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000533- (void)startSignalingIfReady {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000534 if (!_isTurnComplete || !self.hasJoinedRoomServerRoom) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000535 return;
536 }
537 self.state = kARDAppClientStateConnected;
538
539 // Create peer connection.
540 RTCMediaConstraints *constraints = [self defaultPeerConnectionConstraints];
Zeke Chinbc7dd7e2015-05-29 14:59:14 -0700541 RTCConfiguration *config = [[RTCConfiguration alloc] init];
542 config.iceServers = _iceServers;
Tze Kwang Chinf3cb49f2016-03-22 10:57:40 -0700543 _peerConnection = [_factory peerConnectionWithConfiguration:config
544 constraints:constraints
545 delegate:self];
skvladf3569c82016-04-29 15:30:16 -0700546 // Create AV senders.
547 [self createAudioSender];
548 [self createVideoSender];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000549 if (_isInitiator) {
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000550 // Send offer.
hjon79858f82016-03-13 22:08:26 -0700551 __weak ARDAppClient *weakSelf = self;
552 [_peerConnection offerForConstraints:[self defaultOfferConstraints]
553 completionHandler:^(RTCSessionDescription *sdp,
554 NSError *error) {
555 ARDAppClient *strongSelf = weakSelf;
556 [strongSelf peerConnection:strongSelf.peerConnection
557 didCreateSessionDescription:sdp
558 error:error];
559 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000560 } else {
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000561 // Check if we've received an offer.
562 [self drainMessageQueueIfReady];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000563 }
ivoc14d5dbe2016-07-04 07:06:55 -0700564#if defined(WEBRTC_IOS)
565 // Start event log.
566 if (kARDAppClientEnableRtcEventLog) {
567 NSString *filePath = [self documentsFilePathForFileName:@"webrtc-rtceventlog"];
568 if (![_peerConnection startRtcEventLogWithFilePath:filePath
569 maxSizeInBytes:kARDAppClientRtcEventLogMaxSizeInBytes]) {
570 RTCLogError(@"Failed to start event logging.");
571 }
572 }
peah5085b0c2016-08-25 22:15:14 -0700573
574 // Start aecdump diagnostic recording.
575 if (_shouldMakeAecDump) {
576 _isAecDumpActive = YES;
577 NSString *filePath = [self documentsFilePathForFileName:@"audio.aecdump"];
578 int fd = open(filePath.UTF8String, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
579 if (fd < 0) {
580 RTCLogError(@"Failed to create the aecdump file!");
581 _isAecDumpActive = NO;
582 } else {
583 if (![_factory startAecDumpWithFileDescriptor:fd maxFileSizeInBytes:-1]) {
584 RTCLogError(@"Failed to create aecdump.");
585 _isAecDumpActive = NO;
586 }
587 }
588 }
ivoc14d5dbe2016-07-04 07:06:55 -0700589#endif
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000590}
591
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000592// Processes the messages that we've received from the room server and the
593// signaling channel. The offer or answer message must be processed before other
594// signaling messages, however they can arrive out of order. Hence, this method
595// only processes pending messages if there is a peer connection object and
596// if we have received either an offer or answer.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000597- (void)drainMessageQueueIfReady {
598 if (!_peerConnection || !_hasReceivedSdp) {
599 return;
600 }
601 for (ARDSignalingMessage *message in _messageQueue) {
602 [self processSignalingMessage:message];
603 }
604 [_messageQueue removeAllObjects];
605}
606
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000607// Processes the given signaling message based on its type.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000608- (void)processSignalingMessage:(ARDSignalingMessage *)message {
609 NSParameterAssert(_peerConnection ||
610 message.type == kARDSignalingMessageTypeBye);
611 switch (message.type) {
612 case kARDSignalingMessageTypeOffer:
613 case kARDSignalingMessageTypeAnswer: {
614 ARDSessionDescriptionMessage *sdpMessage =
615 (ARDSessionDescriptionMessage *)message;
616 RTCSessionDescription *description = sdpMessage.sessionDescription;
Zeke Chin71f6f442015-06-29 14:34:58 -0700617 // Prefer H264 if available.
618 RTCSessionDescription *sdpPreferringH264 =
619 [ARDSDPUtils descriptionForDescription:description
620 preferredVideoCodec:@"H264"];
hjon79858f82016-03-13 22:08:26 -0700621 __weak ARDAppClient *weakSelf = self;
622 [_peerConnection setRemoteDescription:sdpPreferringH264
623 completionHandler:^(NSError *error) {
624 ARDAppClient *strongSelf = weakSelf;
625 [strongSelf peerConnection:strongSelf.peerConnection
626 didSetSessionDescriptionWithError:error];
627 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000628 break;
629 }
630 case kARDSignalingMessageTypeCandidate: {
631 ARDICECandidateMessage *candidateMessage =
632 (ARDICECandidateMessage *)message;
hjon79858f82016-03-13 22:08:26 -0700633 [_peerConnection addIceCandidate:candidateMessage.candidate];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000634 break;
635 }
Honghai Zhangda2ba4d2016-05-23 11:53:14 -0700636 case kARDSignalingMessageTypeCandidateRemoval: {
637 ARDICECandidateRemovalMessage *candidateMessage =
638 (ARDICECandidateRemovalMessage *)message;
639 [_peerConnection removeIceCandidates:candidateMessage.candidates];
640 break;
641 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000642 case kARDSignalingMessageTypeBye:
643 // Other client disconnected.
644 // TODO(tkchin): support waiting in room for next client. For now just
645 // disconnect.
646 [self disconnect];
647 break;
648 }
649}
650
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000651// Sends a signaling message to the other client. The caller will send messages
652// through the room server, whereas the callee will send messages over the
653// signaling channel.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000654- (void)sendSignalingMessage:(ARDSignalingMessage *)message {
655 if (_isInitiator) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000656 __weak ARDAppClient *weakSelf = self;
657 [_roomServerClient sendMessage:message
658 forRoomId:_roomId
659 clientId:_clientId
660 completionHandler:^(ARDMessageResponse *response,
661 NSError *error) {
662 ARDAppClient *strongSelf = weakSelf;
663 if (error) {
664 [strongSelf.delegate appClient:strongSelf didError:error];
665 return;
666 }
667 NSError *messageError =
668 [[strongSelf class] errorForMessageResultType:response.result];
669 if (messageError) {
670 [strongSelf.delegate appClient:strongSelf didError:messageError];
671 return;
672 }
673 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000674 } else {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000675 [_channel sendMessage:message];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000676 }
677}
678
skvladf3569c82016-04-29 15:30:16 -0700679- (RTCRtpSender *)createVideoSender {
680 RTCRtpSender *sender =
681 [_peerConnection senderWithKind:kRTCMediaStreamTrackKindVideo
682 streamId:kARDMediaStreamId];
683 RTCVideoTrack *track = [self createLocalVideoTrack];
684 if (track) {
685 sender.track = track;
686 [_delegate appClient:self didReceiveLocalVideoTrack:track];
Zeke Chin57cc74e2015-05-05 07:52:31 -0700687 }
skvladf3569c82016-04-29 15:30:16 -0700688 return sender;
689}
690
691- (RTCRtpSender *)createAudioSender {
692 RTCRtpSender *sender =
693 [_peerConnection senderWithKind:kRTCMediaStreamTrackKindAudio
694 streamId:kARDMediaStreamId];
695 RTCAudioTrack *track = [_factory audioTrackWithTrackId:kARDAudioTrackId];
696 sender.track = track;
697 return sender;
Zeke Chin57cc74e2015-05-05 07:52:31 -0700698}
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000699
Zeke Chin57cc74e2015-05-05 07:52:31 -0700700- (RTCVideoTrack *)createLocalVideoTrack {
701 RTCVideoTrack* localVideoTrack = nil;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000702 // The iOS simulator doesn't provide any sort of camera capture
703 // support or emulation (http://goo.gl/rHAnC1) so don't bother
704 // trying to open a local stream.
705 // TODO(tkchin): local video capture for OSX. See
706 // https://code.google.com/p/webrtc/issues/detail?id=3417.
707#if !TARGET_IPHONE_SIMULATOR && TARGET_OS_IPHONE
haysc913e6452015-10-02 11:44:03 -0700708 if (!_isAudioOnly) {
709 RTCMediaConstraints *mediaConstraints =
710 [self defaultMediaStreamConstraints];
711 RTCAVFoundationVideoSource *source =
Tze Kwang Chinf3cb49f2016-03-22 10:57:40 -0700712 [_factory avFoundationVideoSourceWithConstraints:mediaConstraints];
haysc913e6452015-10-02 11:44:03 -0700713 localVideoTrack =
Tze Kwang Chinf3cb49f2016-03-22 10:57:40 -0700714 [_factory videoTrackWithSource:source
skvladf3569c82016-04-29 15:30:16 -0700715 trackId:kARDVideoTrackId];
haysc913e6452015-10-02 11:44:03 -0700716 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000717#endif
Zeke Chin57cc74e2015-05-05 07:52:31 -0700718 return localVideoTrack;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000719}
720
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000721#pragma mark - Collider methods
722
723- (void)registerWithColliderIfReady {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000724 if (!self.hasJoinedRoomServerRoom) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000725 return;
726 }
727 // Open WebSocket connection.
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000728 if (!_channel) {
729 _channel =
730 [[ARDWebSocketChannel alloc] initWithURL:_websocketURL
731 restURL:_websocketRestURL
732 delegate:self];
haysc913e6452015-10-02 11:44:03 -0700733 if (_isLoopback) {
734 _loopbackChannel =
735 [[ARDLoopbackWebSocketChannel alloc] initWithURL:_websocketURL
736 restURL:_websocketRestURL];
737 }
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000738 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000739 [_channel registerForRoomId:_roomId clientId:_clientId];
haysc913e6452015-10-02 11:44:03 -0700740 if (_isLoopback) {
741 [_loopbackChannel registerForRoomId:_roomId clientId:@"LOOPBACK_CLIENT_ID"];
742 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000743}
744
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000745#pragma mark - Defaults
746
747- (RTCMediaConstraints *)defaultMediaStreamConstraints {
748 RTCMediaConstraints* constraints =
749 [[RTCMediaConstraints alloc]
750 initWithMandatoryConstraints:nil
751 optionalConstraints:nil];
752 return constraints;
753}
754
755- (RTCMediaConstraints *)defaultAnswerConstraints {
756 return [self defaultOfferConstraints];
757}
758
759- (RTCMediaConstraints *)defaultOfferConstraints {
hjon79858f82016-03-13 22:08:26 -0700760 NSDictionary *mandatoryConstraints = @{
761 @"OfferToReceiveAudio" : @"true",
762 @"OfferToReceiveVideo" : @"true"
763 };
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000764 RTCMediaConstraints* constraints =
765 [[RTCMediaConstraints alloc]
766 initWithMandatoryConstraints:mandatoryConstraints
767 optionalConstraints:nil];
768 return constraints;
769}
770
771- (RTCMediaConstraints *)defaultPeerConnectionConstraints {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000772 if (_defaultPeerConnectionConstraints) {
773 return _defaultPeerConnectionConstraints;
774 }
haysc913e6452015-10-02 11:44:03 -0700775 NSString *value = _isLoopback ? @"false" : @"true";
hjon79858f82016-03-13 22:08:26 -0700776 NSDictionary *optionalConstraints = @{ @"DtlsSrtpKeyAgreement" : value };
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000777 RTCMediaConstraints* constraints =
778 [[RTCMediaConstraints alloc]
779 initWithMandatoryConstraints:nil
780 optionalConstraints:optionalConstraints];
781 return constraints;
782}
783
hjon79858f82016-03-13 22:08:26 -0700784- (RTCIceServer *)defaultSTUNServer {
785 return [[RTCIceServer alloc] initWithURLStrings:@[kARDDefaultSTUNServerUrl]
786 username:@""
787 credential:@""];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000788}
789
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000790#pragma mark - Errors
791
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000792+ (NSError *)errorForJoinResultType:(ARDJoinResultType)resultType {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000793 NSError *error = nil;
794 switch (resultType) {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000795 case kARDJoinResultTypeSuccess:
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000796 break;
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000797 case kARDJoinResultTypeUnknown: {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000798 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
799 code:kARDAppClientErrorUnknown
800 userInfo:@{
801 NSLocalizedDescriptionKey: @"Unknown error.",
802 }];
803 break;
804 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000805 case kARDJoinResultTypeFull: {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000806 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
807 code:kARDAppClientErrorRoomFull
808 userInfo:@{
809 NSLocalizedDescriptionKey: @"Room is full.",
810 }];
811 break;
812 }
813 }
814 return error;
815}
816
817+ (NSError *)errorForMessageResultType:(ARDMessageResultType)resultType {
818 NSError *error = nil;
819 switch (resultType) {
820 case kARDMessageResultTypeSuccess:
821 break;
822 case kARDMessageResultTypeUnknown:
823 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
824 code:kARDAppClientErrorUnknown
825 userInfo:@{
826 NSLocalizedDescriptionKey: @"Unknown error.",
827 }];
828 break;
829 case kARDMessageResultTypeInvalidClient:
830 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
831 code:kARDAppClientErrorInvalidClient
832 userInfo:@{
833 NSLocalizedDescriptionKey: @"Invalid client.",
834 }];
835 break;
836 case kARDMessageResultTypeInvalidRoom:
837 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
838 code:kARDAppClientErrorInvalidRoom
839 userInfo:@{
840 NSLocalizedDescriptionKey: @"Invalid room.",
841 }];
842 break;
843 }
844 return error;
845}
846
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000847@end