blob: e53222e60e8414aeac386a5a2e6788b5afd1af2b [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)
14#import "RTCAVFoundationVideoSource.h"
15#endif
Zeke Chin2d3b7e22015-07-14 12:55:44 -070016#import "RTCFileLogger.h"
Zeke Chin57cc74e2015-05-05 07:52:31 -070017#import "RTCICEServer.h"
tkchinc3f46a92015-07-23 12:50:55 -070018#import "RTCLogging.h"
Zeke Chin57cc74e2015-05-05 07:52:31 -070019#import "RTCMediaConstraints.h"
20#import "RTCMediaStream.h"
21#import "RTCPair.h"
Zeke Chinbc7dd7e2015-05-29 14:59:14 -070022#import "RTCPeerConnectionInterface.h"
Zeke Chin57cc74e2015-05-05 07:52:31 -070023#import "RTCVideoCapturer.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000024
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000025#import "ARDAppEngineClient.h"
26#import "ARDCEODTURNClient.h"
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +000027#import "ARDJoinResponse.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000028#import "ARDMessageResponse.h"
Zeke Chin71f6f442015-06-29 14:34:58 -070029#import "ARDSDPUtils.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000030#import "ARDSignalingMessage.h"
31#import "ARDUtilities.h"
32#import "ARDWebSocketChannel.h"
33#import "RTCICECandidate+JSON.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000034#import "RTCSessionDescription+JSON.h"
Zeke Chin57cc74e2015-05-05 07:52:31 -070035
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +000036static NSString * const kARDDefaultSTUNServerUrl =
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000037 @"stun:stun.l.google.com:19302";
38// TODO(tkchin): figure out a better username for CEOD statistics.
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +000039static NSString * const kARDTurnRequestUrl =
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000040 @"https://computeengineondemand.appspot.com"
41 @"/turn?username=iapprtc&key=4080218913";
42
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +000043static NSString * const kARDAppClientErrorDomain = @"ARDAppClient";
44static NSInteger const kARDAppClientErrorUnknown = -1;
45static NSInteger const kARDAppClientErrorRoomFull = -2;
46static NSInteger const kARDAppClientErrorCreateSDP = -3;
47static NSInteger const kARDAppClientErrorSetSDP = -4;
48static NSInteger const kARDAppClientErrorInvalidClient = -5;
49static NSInteger const kARDAppClientErrorInvalidRoom = -6;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000050
Zeke Chind3325802015-08-14 11:00:02 -070051// We need a proxy to NSTimer because it causes a strong retain cycle. When
52// using the proxy, |invalidate| must be called before it properly deallocs.
53@interface ARDTimerProxy : NSObject
54
55- (instancetype)initWithInterval:(NSTimeInterval)interval
56 repeats:(BOOL)repeats
57 timerHandler:(void (^)(void))timerHandler;
58- (void)invalidate;
59
60@end
61
62@implementation ARDTimerProxy {
63 NSTimer *_timer;
64 void (^_timerHandler)(void);
Zeke Chin2d3b7e22015-07-14 12:55:44 -070065}
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000066
Zeke Chind3325802015-08-14 11:00:02 -070067- (instancetype)initWithInterval:(NSTimeInterval)interval
68 repeats:(BOOL)repeats
69 timerHandler:(void (^)(void))timerHandler {
70 NSParameterAssert(timerHandler);
71 if (self = [super init]) {
72 _timerHandler = timerHandler;
73 _timer = [NSTimer scheduledTimerWithTimeInterval:interval
74 target:self
75 selector:@selector(timerDidFire:)
76 userInfo:nil
77 repeats:repeats];
78 }
79 return self;
80}
81
82- (void)invalidate {
83 [_timer invalidate];
84}
85
86- (void)timerDidFire:(NSTimer *)timer {
87 _timerHandler();
88}
89
90@end
91
92@implementation ARDAppClient {
93 RTCFileLogger *_fileLogger;
94 ARDTimerProxy *_statsTimer;
95}
96
97@synthesize shouldGetStats = _shouldGetStats;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000098@synthesize state = _state;
Zeke Chind3325802015-08-14 11:00:02 -070099@synthesize delegate = _delegate;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000100@synthesize roomServerClient = _roomServerClient;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000101@synthesize channel = _channel;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000102@synthesize turnClient = _turnClient;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000103@synthesize peerConnection = _peerConnection;
104@synthesize factory = _factory;
105@synthesize messageQueue = _messageQueue;
106@synthesize isTurnComplete = _isTurnComplete;
107@synthesize hasReceivedSdp = _hasReceivedSdp;
108@synthesize roomId = _roomId;
109@synthesize clientId = _clientId;
110@synthesize isInitiator = _isInitiator;
111@synthesize iceServers = _iceServers;
112@synthesize webSocketURL = _websocketURL;
113@synthesize webSocketRestURL = _websocketRestURL;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000114@synthesize defaultPeerConnectionConstraints =
115 _defaultPeerConnectionConstraints;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000116
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000117- (instancetype)init {
118 if (self = [super init]) {
119 _roomServerClient = [[ARDAppEngineClient alloc] init];
120 NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl];
121 _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL];
122 [self configure];
123 }
124 return self;
125}
126
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000127- (instancetype)initWithDelegate:(id<ARDAppClientDelegate>)delegate {
128 if (self = [super init]) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000129 _roomServerClient = [[ARDAppEngineClient alloc] init];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000130 _delegate = delegate;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000131 NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl];
132 _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL];
133 [self configure];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000134 }
135 return self;
136}
137
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000138// TODO(tkchin): Provide signaling channel factory interface so we can recreate
139// channel if we need to on network failure. Also, make this the default public
140// constructor.
141- (instancetype)initWithRoomServerClient:(id<ARDRoomServerClient>)rsClient
142 signalingChannel:(id<ARDSignalingChannel>)channel
143 turnClient:(id<ARDTURNClient>)turnClient
144 delegate:(id<ARDAppClientDelegate>)delegate {
145 NSParameterAssert(rsClient);
146 NSParameterAssert(channel);
147 NSParameterAssert(turnClient);
148 if (self = [super init]) {
149 _roomServerClient = rsClient;
150 _channel = channel;
151 _turnClient = turnClient;
152 _delegate = delegate;
153 [self configure];
154 }
155 return self;
156}
157
158- (void)configure {
159 _factory = [[RTCPeerConnectionFactory alloc] init];
160 _messageQueue = [NSMutableArray array];
161 _iceServers = [NSMutableArray arrayWithObject:[self defaultSTUNServer]];
Zeke Chin2d3b7e22015-07-14 12:55:44 -0700162 _fileLogger = [[RTCFileLogger alloc] init];
163 [_fileLogger start];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000164}
165
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000166- (void)dealloc {
Zeke Chind3325802015-08-14 11:00:02 -0700167 self.shouldGetStats = NO;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000168 [self disconnect];
169}
170
Zeke Chind3325802015-08-14 11:00:02 -0700171- (void)setShouldGetStats:(BOOL)shouldGetStats {
172 if (_shouldGetStats == shouldGetStats) {
173 return;
174 }
175 if (shouldGetStats) {
176 __weak ARDAppClient *weakSelf = self;
177 _statsTimer = [[ARDTimerProxy alloc] initWithInterval:1
178 repeats:YES
179 timerHandler:^{
180 ARDAppClient *strongSelf = weakSelf;
181 [strongSelf.peerConnection getStatsWithDelegate:strongSelf
182 mediaStreamTrack:nil
183 statsOutputLevel:RTCStatsOutputLevelDebug];
184 }];
185 } else {
186 [_statsTimer invalidate];
187 _statsTimer = nil;
188 }
189 _shouldGetStats = shouldGetStats;
190}
191
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000192- (void)setState:(ARDAppClientState)state {
193 if (_state == state) {
194 return;
195 }
196 _state = state;
197 [_delegate appClient:self didChangeState:_state];
198}
199
200- (void)connectToRoomWithId:(NSString *)roomId
201 options:(NSDictionary *)options {
202 NSParameterAssert(roomId.length);
203 NSParameterAssert(_state == kARDAppClientStateDisconnected);
204 self.state = kARDAppClientStateConnecting;
205
206 // Request TURN.
207 __weak ARDAppClient *weakSelf = self;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000208 [_turnClient requestServersWithCompletionHandler:^(NSArray *turnServers,
209 NSError *error) {
210 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700211 RTCLogError("Error retrieving TURN servers: %@",
212 error.localizedDescription);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000213 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000214 ARDAppClient *strongSelf = weakSelf;
215 [strongSelf.iceServers addObjectsFromArray:turnServers];
216 strongSelf.isTurnComplete = YES;
217 [strongSelf startSignalingIfReady];
218 }];
jiayl@webrtc.org27f53172014-12-31 00:26:20 +0000219
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000220 // Join room on room server.
221 [_roomServerClient joinRoomWithRoomId:roomId
222 completionHandler:^(ARDJoinResponse *response, NSError *error) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000223 ARDAppClient *strongSelf = weakSelf;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000224 if (error) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000225 [strongSelf.delegate appClient:strongSelf didError:error];
226 return;
227 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000228 NSError *joinError =
229 [[strongSelf class] errorForJoinResultType:response.result];
230 if (joinError) {
tkchinc3f46a92015-07-23 12:50:55 -0700231 RTCLogError(@"Failed to join room:%@ on room server.", roomId);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000232 [strongSelf disconnect];
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000233 [strongSelf.delegate appClient:strongSelf didError:joinError];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000234 return;
235 }
tkchinc3f46a92015-07-23 12:50:55 -0700236 RTCLog(@"Joined room:%@ on room server.", roomId);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000237 strongSelf.roomId = response.roomId;
238 strongSelf.clientId = response.clientId;
239 strongSelf.isInitiator = response.isInitiator;
240 for (ARDSignalingMessage *message in response.messages) {
241 if (message.type == kARDSignalingMessageTypeOffer ||
242 message.type == kARDSignalingMessageTypeAnswer) {
243 strongSelf.hasReceivedSdp = YES;
244 [strongSelf.messageQueue insertObject:message atIndex:0];
245 } else {
246 [strongSelf.messageQueue addObject:message];
247 }
248 }
249 strongSelf.webSocketURL = response.webSocketURL;
250 strongSelf.webSocketRestURL = response.webSocketRestURL;
251 [strongSelf registerWithColliderIfReady];
252 [strongSelf startSignalingIfReady];
253 }];
254}
255
256- (void)disconnect {
257 if (_state == kARDAppClientStateDisconnected) {
258 return;
259 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000260 if (self.hasJoinedRoomServerRoom) {
261 [_roomServerClient leaveRoomWithRoomId:_roomId
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000262 clientId:_clientId
263 completionHandler:nil];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000264 }
265 if (_channel) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000266 if (_channel.state == kARDSignalingChannelStateRegistered) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000267 // Tell the other client we're hanging up.
268 ARDByeMessage *byeMessage = [[ARDByeMessage alloc] init];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000269 [_channel sendMessage:byeMessage];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000270 }
271 // Disconnect from collider.
272 _channel = nil;
273 }
274 _clientId = nil;
275 _roomId = nil;
276 _isInitiator = NO;
277 _hasReceivedSdp = NO;
278 _messageQueue = [NSMutableArray array];
279 _peerConnection = nil;
280 self.state = kARDAppClientStateDisconnected;
281}
282
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000283#pragma mark - ARDSignalingChannelDelegate
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000284
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000285- (void)channel:(id<ARDSignalingChannel>)channel
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000286 didReceiveMessage:(ARDSignalingMessage *)message {
287 switch (message.type) {
288 case kARDSignalingMessageTypeOffer:
289 case kARDSignalingMessageTypeAnswer:
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000290 // Offers and answers must be processed before any other message, so we
291 // place them at the front of the queue.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000292 _hasReceivedSdp = YES;
293 [_messageQueue insertObject:message atIndex:0];
294 break;
295 case kARDSignalingMessageTypeCandidate:
296 [_messageQueue addObject:message];
297 break;
298 case kARDSignalingMessageTypeBye:
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000299 // Disconnects can be processed immediately.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000300 [self processSignalingMessage:message];
301 return;
302 }
303 [self drainMessageQueueIfReady];
304}
305
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000306- (void)channel:(id<ARDSignalingChannel>)channel
307 didChangeState:(ARDSignalingChannelState)state {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000308 switch (state) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000309 case kARDSignalingChannelStateOpen:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000310 break;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000311 case kARDSignalingChannelStateRegistered:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000312 break;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000313 case kARDSignalingChannelStateClosed:
314 case kARDSignalingChannelStateError:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000315 // TODO(tkchin): reconnection scenarios. Right now we just disconnect
316 // completely if the websocket connection fails.
317 [self disconnect];
318 break;
319 }
320}
321
322#pragma mark - RTCPeerConnectionDelegate
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000323// Callbacks for this delegate occur on non-main thread and need to be
324// dispatched back to main queue as needed.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000325
326- (void)peerConnection:(RTCPeerConnection *)peerConnection
327 signalingStateChanged:(RTCSignalingState)stateChanged {
tkchinc3f46a92015-07-23 12:50:55 -0700328 RTCLog(@"Signaling state changed: %d", stateChanged);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000329}
330
331- (void)peerConnection:(RTCPeerConnection *)peerConnection
332 addedStream:(RTCMediaStream *)stream {
333 dispatch_async(dispatch_get_main_queue(), ^{
tkchinc3f46a92015-07-23 12:50:55 -0700334 RTCLog(@"Received %lu video tracks and %lu audio tracks",
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000335 (unsigned long)stream.videoTracks.count,
336 (unsigned long)stream.audioTracks.count);
337 if (stream.videoTracks.count) {
338 RTCVideoTrack *videoTrack = stream.videoTracks[0];
339 [_delegate appClient:self didReceiveRemoteVideoTrack:videoTrack];
340 }
341 });
342}
343
344- (void)peerConnection:(RTCPeerConnection *)peerConnection
345 removedStream:(RTCMediaStream *)stream {
tkchinc3f46a92015-07-23 12:50:55 -0700346 RTCLog(@"Stream was removed.");
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000347}
348
349- (void)peerConnectionOnRenegotiationNeeded:
350 (RTCPeerConnection *)peerConnection {
tkchinc3f46a92015-07-23 12:50:55 -0700351 RTCLog(@"WARNING: Renegotiation needed but unimplemented.");
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000352}
353
354- (void)peerConnection:(RTCPeerConnection *)peerConnection
355 iceConnectionChanged:(RTCICEConnectionState)newState {
tkchinc3f46a92015-07-23 12:50:55 -0700356 RTCLog(@"ICE state changed: %d", newState);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000357 dispatch_async(dispatch_get_main_queue(), ^{
358 [_delegate appClient:self didChangeConnectionState:newState];
359 });
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000360}
361
362- (void)peerConnection:(RTCPeerConnection *)peerConnection
363 iceGatheringChanged:(RTCICEGatheringState)newState {
tkchinc3f46a92015-07-23 12:50:55 -0700364 RTCLog(@"ICE gathering state changed: %d", newState);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000365}
366
367- (void)peerConnection:(RTCPeerConnection *)peerConnection
368 gotICECandidate:(RTCICECandidate *)candidate {
369 dispatch_async(dispatch_get_main_queue(), ^{
370 ARDICECandidateMessage *message =
371 [[ARDICECandidateMessage alloc] initWithCandidate:candidate];
372 [self sendSignalingMessage:message];
373 });
374}
375
Zeke Chind3325802015-08-14 11:00:02 -0700376- (void)peerConnection:(RTCPeerConnection *)peerConnection
377 didOpenDataChannel:(RTCDataChannel *)dataChannel {
378}
379
380#pragma mark - RTCStatsDelegate
381
382- (void)peerConnection:(RTCPeerConnection *)peerConnection
383 didGetStats:(NSArray *)stats {
384 dispatch_async(dispatch_get_main_queue(), ^{
385 [_delegate appClient:self didGetStats:stats];
386 });
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000387}
388
389#pragma mark - RTCSessionDescriptionDelegate
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000390// Callbacks for this delegate occur on non-main thread and need to be
391// dispatched back to main queue as needed.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000392
393- (void)peerConnection:(RTCPeerConnection *)peerConnection
394 didCreateSessionDescription:(RTCSessionDescription *)sdp
395 error:(NSError *)error {
396 dispatch_async(dispatch_get_main_queue(), ^{
397 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700398 RTCLogError(@"Failed to create session description. Error: %@", error);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000399 [self disconnect];
400 NSDictionary *userInfo = @{
401 NSLocalizedDescriptionKey: @"Failed to create session description.",
402 };
403 NSError *sdpError =
404 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
405 code:kARDAppClientErrorCreateSDP
406 userInfo:userInfo];
407 [_delegate appClient:self didError:sdpError];
408 return;
409 }
Zeke Chin71f6f442015-06-29 14:34:58 -0700410 // Prefer H264 if available.
411 RTCSessionDescription *sdpPreferringH264 =
412 [ARDSDPUtils descriptionForDescription:sdp
413 preferredVideoCodec:@"H264"];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000414 [_peerConnection setLocalDescriptionWithDelegate:self
Zeke Chin71f6f442015-06-29 14:34:58 -0700415 sessionDescription:sdpPreferringH264];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000416 ARDSessionDescriptionMessage *message =
Zeke Chin71f6f442015-06-29 14:34:58 -0700417 [[ARDSessionDescriptionMessage alloc]
418 initWithDescription:sdpPreferringH264];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000419 [self sendSignalingMessage:message];
420 });
421}
422
423- (void)peerConnection:(RTCPeerConnection *)peerConnection
424 didSetSessionDescriptionWithError:(NSError *)error {
425 dispatch_async(dispatch_get_main_queue(), ^{
426 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700427 RTCLogError(@"Failed to set session description. Error: %@", error);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000428 [self disconnect];
429 NSDictionary *userInfo = @{
430 NSLocalizedDescriptionKey: @"Failed to set session description.",
431 };
432 NSError *sdpError =
433 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
434 code:kARDAppClientErrorSetSDP
435 userInfo:userInfo];
436 [_delegate appClient:self didError:sdpError];
437 return;
438 }
439 // If we're answering and we've just set the remote offer we need to create
440 // an answer and set the local description.
441 if (!_isInitiator && !_peerConnection.localDescription) {
442 RTCMediaConstraints *constraints = [self defaultAnswerConstraints];
443 [_peerConnection createAnswerWithDelegate:self
444 constraints:constraints];
445
446 }
447 });
448}
449
450#pragma mark - Private
451
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000452- (BOOL)hasJoinedRoomServerRoom {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000453 return _clientId.length;
454}
455
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000456// Begins the peer connection connection process if we have both joined a room
457// on the room server and tried to obtain a TURN server. Otherwise does nothing.
458// A peer connection object will be created with a stream that contains local
459// audio and video capture. If this client is the caller, an offer is created as
460// well, otherwise the client will wait for an offer to arrive.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000461- (void)startSignalingIfReady {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000462 if (!_isTurnComplete || !self.hasJoinedRoomServerRoom) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000463 return;
464 }
465 self.state = kARDAppClientStateConnected;
466
467 // Create peer connection.
468 RTCMediaConstraints *constraints = [self defaultPeerConnectionConstraints];
Zeke Chinbc7dd7e2015-05-29 14:59:14 -0700469 RTCConfiguration *config = [[RTCConfiguration alloc] init];
470 config.iceServers = _iceServers;
471 _peerConnection = [_factory peerConnectionWithConfiguration:config
472 constraints:constraints
473 delegate:self];
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000474 // Create AV media stream and add it to the peer connection.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000475 RTCMediaStream *localStream = [self createLocalMediaStream];
476 [_peerConnection addStream:localStream];
477 if (_isInitiator) {
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000478 // Send offer.
479 [_peerConnection createOfferWithDelegate:self
480 constraints:[self defaultOfferConstraints]];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000481 } else {
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000482 // Check if we've received an offer.
483 [self drainMessageQueueIfReady];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000484 }
485}
486
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000487// Processes the messages that we've received from the room server and the
488// signaling channel. The offer or answer message must be processed before other
489// signaling messages, however they can arrive out of order. Hence, this method
490// only processes pending messages if there is a peer connection object and
491// if we have received either an offer or answer.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000492- (void)drainMessageQueueIfReady {
493 if (!_peerConnection || !_hasReceivedSdp) {
494 return;
495 }
496 for (ARDSignalingMessage *message in _messageQueue) {
497 [self processSignalingMessage:message];
498 }
499 [_messageQueue removeAllObjects];
500}
501
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000502// Processes the given signaling message based on its type.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000503- (void)processSignalingMessage:(ARDSignalingMessage *)message {
504 NSParameterAssert(_peerConnection ||
505 message.type == kARDSignalingMessageTypeBye);
506 switch (message.type) {
507 case kARDSignalingMessageTypeOffer:
508 case kARDSignalingMessageTypeAnswer: {
509 ARDSessionDescriptionMessage *sdpMessage =
510 (ARDSessionDescriptionMessage *)message;
511 RTCSessionDescription *description = sdpMessage.sessionDescription;
Zeke Chin71f6f442015-06-29 14:34:58 -0700512 // Prefer H264 if available.
513 RTCSessionDescription *sdpPreferringH264 =
514 [ARDSDPUtils descriptionForDescription:description
515 preferredVideoCodec:@"H264"];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000516 [_peerConnection setRemoteDescriptionWithDelegate:self
Zeke Chin71f6f442015-06-29 14:34:58 -0700517 sessionDescription:sdpPreferringH264];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000518 break;
519 }
520 case kARDSignalingMessageTypeCandidate: {
521 ARDICECandidateMessage *candidateMessage =
522 (ARDICECandidateMessage *)message;
523 [_peerConnection addICECandidate:candidateMessage.candidate];
524 break;
525 }
526 case kARDSignalingMessageTypeBye:
527 // Other client disconnected.
528 // TODO(tkchin): support waiting in room for next client. For now just
529 // disconnect.
530 [self disconnect];
531 break;
532 }
533}
534
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000535// Sends a signaling message to the other client. The caller will send messages
536// through the room server, whereas the callee will send messages over the
537// signaling channel.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000538- (void)sendSignalingMessage:(ARDSignalingMessage *)message {
539 if (_isInitiator) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000540 __weak ARDAppClient *weakSelf = self;
541 [_roomServerClient sendMessage:message
542 forRoomId:_roomId
543 clientId:_clientId
544 completionHandler:^(ARDMessageResponse *response,
545 NSError *error) {
546 ARDAppClient *strongSelf = weakSelf;
547 if (error) {
548 [strongSelf.delegate appClient:strongSelf didError:error];
549 return;
550 }
551 NSError *messageError =
552 [[strongSelf class] errorForMessageResultType:response.result];
553 if (messageError) {
554 [strongSelf.delegate appClient:strongSelf didError:messageError];
555 return;
556 }
557 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000558 } else {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000559 [_channel sendMessage:message];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000560 }
561}
562
563- (RTCMediaStream *)createLocalMediaStream {
564 RTCMediaStream* localStream = [_factory mediaStreamWithLabel:@"ARDAMS"];
Zeke Chin57cc74e2015-05-05 07:52:31 -0700565 RTCVideoTrack* localVideoTrack = [self createLocalVideoTrack];
566 if (localVideoTrack) {
567 [localStream addVideoTrack:localVideoTrack];
568 [_delegate appClient:self didReceiveLocalVideoTrack:localVideoTrack];
569 }
570 [localStream addAudioTrack:[_factory audioTrackWithID:@"ARDAMSa0"]];
571 return localStream;
572}
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000573
Zeke Chin57cc74e2015-05-05 07:52:31 -0700574- (RTCVideoTrack *)createLocalVideoTrack {
575 RTCVideoTrack* localVideoTrack = nil;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000576 // The iOS simulator doesn't provide any sort of camera capture
577 // support or emulation (http://goo.gl/rHAnC1) so don't bother
578 // trying to open a local stream.
579 // TODO(tkchin): local video capture for OSX. See
580 // https://code.google.com/p/webrtc/issues/detail?id=3417.
581#if !TARGET_IPHONE_SIMULATOR && TARGET_OS_IPHONE
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000582 RTCMediaConstraints *mediaConstraints = [self defaultMediaStreamConstraints];
Zeke Chin57cc74e2015-05-05 07:52:31 -0700583 RTCAVFoundationVideoSource *source =
584 [[RTCAVFoundationVideoSource alloc] initWithFactory:_factory
585 constraints:mediaConstraints];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000586 localVideoTrack =
Zeke Chin57cc74e2015-05-05 07:52:31 -0700587 [[RTCVideoTrack alloc] initWithFactory:_factory
588 source:source
589 trackId:@"ARDAMSv0"];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000590#endif
Zeke Chin57cc74e2015-05-05 07:52:31 -0700591 return localVideoTrack;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000592}
593
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000594#pragma mark - Collider methods
595
596- (void)registerWithColliderIfReady {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000597 if (!self.hasJoinedRoomServerRoom) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000598 return;
599 }
600 // Open WebSocket connection.
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000601 if (!_channel) {
602 _channel =
603 [[ARDWebSocketChannel alloc] initWithURL:_websocketURL
604 restURL:_websocketRestURL
605 delegate:self];
606 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000607 [_channel registerForRoomId:_roomId clientId:_clientId];
608}
609
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000610#pragma mark - Defaults
611
612- (RTCMediaConstraints *)defaultMediaStreamConstraints {
613 RTCMediaConstraints* constraints =
614 [[RTCMediaConstraints alloc]
615 initWithMandatoryConstraints:nil
616 optionalConstraints:nil];
617 return constraints;
618}
619
620- (RTCMediaConstraints *)defaultAnswerConstraints {
621 return [self defaultOfferConstraints];
622}
623
624- (RTCMediaConstraints *)defaultOfferConstraints {
625 NSArray *mandatoryConstraints = @[
626 [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" value:@"true"],
627 [[RTCPair alloc] initWithKey:@"OfferToReceiveVideo" value:@"true"]
628 ];
629 RTCMediaConstraints* constraints =
630 [[RTCMediaConstraints alloc]
631 initWithMandatoryConstraints:mandatoryConstraints
632 optionalConstraints:nil];
633 return constraints;
634}
635
636- (RTCMediaConstraints *)defaultPeerConnectionConstraints {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000637 if (_defaultPeerConnectionConstraints) {
638 return _defaultPeerConnectionConstraints;
639 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000640 NSArray *optionalConstraints = @[
641 [[RTCPair alloc] initWithKey:@"DtlsSrtpKeyAgreement" value:@"true"]
642 ];
643 RTCMediaConstraints* constraints =
644 [[RTCMediaConstraints alloc]
645 initWithMandatoryConstraints:nil
646 optionalConstraints:optionalConstraints];
647 return constraints;
648}
649
650- (RTCICEServer *)defaultSTUNServer {
651 NSURL *defaultSTUNServerURL = [NSURL URLWithString:kARDDefaultSTUNServerUrl];
652 return [[RTCICEServer alloc] initWithURI:defaultSTUNServerURL
653 username:@""
654 password:@""];
655}
656
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000657#pragma mark - Errors
658
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000659+ (NSError *)errorForJoinResultType:(ARDJoinResultType)resultType {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000660 NSError *error = nil;
661 switch (resultType) {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000662 case kARDJoinResultTypeSuccess:
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000663 break;
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000664 case kARDJoinResultTypeUnknown: {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000665 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
666 code:kARDAppClientErrorUnknown
667 userInfo:@{
668 NSLocalizedDescriptionKey: @"Unknown error.",
669 }];
670 break;
671 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000672 case kARDJoinResultTypeFull: {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000673 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
674 code:kARDAppClientErrorRoomFull
675 userInfo:@{
676 NSLocalizedDescriptionKey: @"Room is full.",
677 }];
678 break;
679 }
680 }
681 return error;
682}
683
684+ (NSError *)errorForMessageResultType:(ARDMessageResultType)resultType {
685 NSError *error = nil;
686 switch (resultType) {
687 case kARDMessageResultTypeSuccess:
688 break;
689 case kARDMessageResultTypeUnknown:
690 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
691 code:kARDAppClientErrorUnknown
692 userInfo:@{
693 NSLocalizedDescriptionKey: @"Unknown error.",
694 }];
695 break;
696 case kARDMessageResultTypeInvalidClient:
697 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
698 code:kARDAppClientErrorInvalidClient
699 userInfo:@{
700 NSLocalizedDescriptionKey: @"Invalid client.",
701 }];
702 break;
703 case kARDMessageResultTypeInvalidRoom:
704 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
705 code:kARDAppClientErrorInvalidRoom
706 userInfo:@{
707 NSLocalizedDescriptionKey: @"Invalid room.",
708 }];
709 break;
710 }
711 return error;
712}
713
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000714@end