blob: 33e00ed443e346daf820d3710b02e77acbec5f27 [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;
haysc913e6452015-10-02 11:44:03 -0700102@synthesize loopbackChannel = _loopbackChannel;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000103@synthesize turnClient = _turnClient;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000104@synthesize peerConnection = _peerConnection;
105@synthesize factory = _factory;
106@synthesize messageQueue = _messageQueue;
107@synthesize isTurnComplete = _isTurnComplete;
108@synthesize hasReceivedSdp = _hasReceivedSdp;
109@synthesize roomId = _roomId;
110@synthesize clientId = _clientId;
111@synthesize isInitiator = _isInitiator;
112@synthesize iceServers = _iceServers;
113@synthesize webSocketURL = _websocketURL;
114@synthesize webSocketRestURL = _websocketRestURL;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000115@synthesize defaultPeerConnectionConstraints =
116 _defaultPeerConnectionConstraints;
haysc913e6452015-10-02 11:44:03 -0700117@synthesize isLoopback = _isLoopback;
118@synthesize isAudioOnly = _isAudioOnly;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000119
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000120- (instancetype)init {
121 if (self = [super init]) {
122 _roomServerClient = [[ARDAppEngineClient alloc] init];
123 NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl];
124 _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL];
125 [self configure];
126 }
127 return self;
128}
129
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000130- (instancetype)initWithDelegate:(id<ARDAppClientDelegate>)delegate {
131 if (self = [super init]) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000132 _roomServerClient = [[ARDAppEngineClient alloc] init];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000133 _delegate = delegate;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000134 NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl];
135 _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL];
136 [self configure];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000137 }
138 return self;
139}
140
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000141// TODO(tkchin): Provide signaling channel factory interface so we can recreate
142// channel if we need to on network failure. Also, make this the default public
143// constructor.
144- (instancetype)initWithRoomServerClient:(id<ARDRoomServerClient>)rsClient
145 signalingChannel:(id<ARDSignalingChannel>)channel
146 turnClient:(id<ARDTURNClient>)turnClient
147 delegate:(id<ARDAppClientDelegate>)delegate {
148 NSParameterAssert(rsClient);
149 NSParameterAssert(channel);
150 NSParameterAssert(turnClient);
151 if (self = [super init]) {
152 _roomServerClient = rsClient;
153 _channel = channel;
154 _turnClient = turnClient;
155 _delegate = delegate;
156 [self configure];
157 }
158 return self;
159}
160
161- (void)configure {
162 _factory = [[RTCPeerConnectionFactory alloc] init];
163 _messageQueue = [NSMutableArray array];
164 _iceServers = [NSMutableArray arrayWithObject:[self defaultSTUNServer]];
Zeke Chin2d3b7e22015-07-14 12:55:44 -0700165 _fileLogger = [[RTCFileLogger alloc] init];
166 [_fileLogger start];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000167}
168
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000169- (void)dealloc {
Zeke Chind3325802015-08-14 11:00:02 -0700170 self.shouldGetStats = NO;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000171 [self disconnect];
172}
173
Zeke Chind3325802015-08-14 11:00:02 -0700174- (void)setShouldGetStats:(BOOL)shouldGetStats {
175 if (_shouldGetStats == shouldGetStats) {
176 return;
177 }
178 if (shouldGetStats) {
179 __weak ARDAppClient *weakSelf = self;
180 _statsTimer = [[ARDTimerProxy alloc] initWithInterval:1
181 repeats:YES
182 timerHandler:^{
183 ARDAppClient *strongSelf = weakSelf;
184 [strongSelf.peerConnection getStatsWithDelegate:strongSelf
185 mediaStreamTrack:nil
186 statsOutputLevel:RTCStatsOutputLevelDebug];
187 }];
188 } else {
189 [_statsTimer invalidate];
190 _statsTimer = nil;
191 }
192 _shouldGetStats = shouldGetStats;
193}
194
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000195- (void)setState:(ARDAppClientState)state {
196 if (_state == state) {
197 return;
198 }
199 _state = state;
200 [_delegate appClient:self didChangeState:_state];
201}
202
203- (void)connectToRoomWithId:(NSString *)roomId
haysc913e6452015-10-02 11:44:03 -0700204 isLoopback:(BOOL)isLoopback
205 isAudioOnly:(BOOL)isAudioOnly {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000206 NSParameterAssert(roomId.length);
207 NSParameterAssert(_state == kARDAppClientStateDisconnected);
haysc913e6452015-10-02 11:44:03 -0700208 _isLoopback = isLoopback;
209 _isAudioOnly = isAudioOnly;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000210 self.state = kARDAppClientStateConnecting;
211
212 // Request TURN.
213 __weak ARDAppClient *weakSelf = self;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000214 [_turnClient requestServersWithCompletionHandler:^(NSArray *turnServers,
215 NSError *error) {
216 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700217 RTCLogError("Error retrieving TURN servers: %@",
218 error.localizedDescription);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000219 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000220 ARDAppClient *strongSelf = weakSelf;
221 [strongSelf.iceServers addObjectsFromArray:turnServers];
222 strongSelf.isTurnComplete = YES;
223 [strongSelf startSignalingIfReady];
224 }];
jiayl@webrtc.org27f53172014-12-31 00:26:20 +0000225
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000226 // Join room on room server.
227 [_roomServerClient joinRoomWithRoomId:roomId
haysc913e6452015-10-02 11:44:03 -0700228 isLoopback:isLoopback
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000229 completionHandler:^(ARDJoinResponse *response, NSError *error) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000230 ARDAppClient *strongSelf = weakSelf;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000231 if (error) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000232 [strongSelf.delegate appClient:strongSelf didError:error];
233 return;
234 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000235 NSError *joinError =
236 [[strongSelf class] errorForJoinResultType:response.result];
237 if (joinError) {
tkchinc3f46a92015-07-23 12:50:55 -0700238 RTCLogError(@"Failed to join room:%@ on room server.", roomId);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000239 [strongSelf disconnect];
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000240 [strongSelf.delegate appClient:strongSelf didError:joinError];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000241 return;
242 }
tkchinc3f46a92015-07-23 12:50:55 -0700243 RTCLog(@"Joined room:%@ on room server.", roomId);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000244 strongSelf.roomId = response.roomId;
245 strongSelf.clientId = response.clientId;
246 strongSelf.isInitiator = response.isInitiator;
247 for (ARDSignalingMessage *message in response.messages) {
248 if (message.type == kARDSignalingMessageTypeOffer ||
249 message.type == kARDSignalingMessageTypeAnswer) {
250 strongSelf.hasReceivedSdp = YES;
251 [strongSelf.messageQueue insertObject:message atIndex:0];
252 } else {
253 [strongSelf.messageQueue addObject:message];
254 }
255 }
256 strongSelf.webSocketURL = response.webSocketURL;
257 strongSelf.webSocketRestURL = response.webSocketRestURL;
258 [strongSelf registerWithColliderIfReady];
259 [strongSelf startSignalingIfReady];
260 }];
261}
262
263- (void)disconnect {
264 if (_state == kARDAppClientStateDisconnected) {
265 return;
266 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000267 if (self.hasJoinedRoomServerRoom) {
268 [_roomServerClient leaveRoomWithRoomId:_roomId
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000269 clientId:_clientId
270 completionHandler:nil];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000271 }
272 if (_channel) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000273 if (_channel.state == kARDSignalingChannelStateRegistered) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000274 // Tell the other client we're hanging up.
275 ARDByeMessage *byeMessage = [[ARDByeMessage alloc] init];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000276 [_channel sendMessage:byeMessage];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000277 }
278 // Disconnect from collider.
279 _channel = nil;
280 }
281 _clientId = nil;
282 _roomId = nil;
283 _isInitiator = NO;
284 _hasReceivedSdp = NO;
285 _messageQueue = [NSMutableArray array];
286 _peerConnection = nil;
287 self.state = kARDAppClientStateDisconnected;
288}
289
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000290#pragma mark - ARDSignalingChannelDelegate
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000291
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000292- (void)channel:(id<ARDSignalingChannel>)channel
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000293 didReceiveMessage:(ARDSignalingMessage *)message {
294 switch (message.type) {
295 case kARDSignalingMessageTypeOffer:
296 case kARDSignalingMessageTypeAnswer:
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000297 // Offers and answers must be processed before any other message, so we
298 // place them at the front of the queue.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000299 _hasReceivedSdp = YES;
300 [_messageQueue insertObject:message atIndex:0];
301 break;
302 case kARDSignalingMessageTypeCandidate:
303 [_messageQueue addObject:message];
304 break;
305 case kARDSignalingMessageTypeBye:
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000306 // Disconnects can be processed immediately.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000307 [self processSignalingMessage:message];
308 return;
309 }
310 [self drainMessageQueueIfReady];
311}
312
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000313- (void)channel:(id<ARDSignalingChannel>)channel
314 didChangeState:(ARDSignalingChannelState)state {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000315 switch (state) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000316 case kARDSignalingChannelStateOpen:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000317 break;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000318 case kARDSignalingChannelStateRegistered:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000319 break;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000320 case kARDSignalingChannelStateClosed:
321 case kARDSignalingChannelStateError:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000322 // TODO(tkchin): reconnection scenarios. Right now we just disconnect
323 // completely if the websocket connection fails.
324 [self disconnect];
325 break;
326 }
327}
328
329#pragma mark - RTCPeerConnectionDelegate
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000330// Callbacks for this delegate occur on non-main thread and need to be
331// dispatched back to main queue as needed.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000332
333- (void)peerConnection:(RTCPeerConnection *)peerConnection
334 signalingStateChanged:(RTCSignalingState)stateChanged {
tkchinc3f46a92015-07-23 12:50:55 -0700335 RTCLog(@"Signaling state changed: %d", stateChanged);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000336}
337
338- (void)peerConnection:(RTCPeerConnection *)peerConnection
339 addedStream:(RTCMediaStream *)stream {
340 dispatch_async(dispatch_get_main_queue(), ^{
tkchinc3f46a92015-07-23 12:50:55 -0700341 RTCLog(@"Received %lu video tracks and %lu audio tracks",
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000342 (unsigned long)stream.videoTracks.count,
343 (unsigned long)stream.audioTracks.count);
344 if (stream.videoTracks.count) {
345 RTCVideoTrack *videoTrack = stream.videoTracks[0];
346 [_delegate appClient:self didReceiveRemoteVideoTrack:videoTrack];
347 }
348 });
349}
350
351- (void)peerConnection:(RTCPeerConnection *)peerConnection
352 removedStream:(RTCMediaStream *)stream {
tkchinc3f46a92015-07-23 12:50:55 -0700353 RTCLog(@"Stream was removed.");
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000354}
355
356- (void)peerConnectionOnRenegotiationNeeded:
357 (RTCPeerConnection *)peerConnection {
tkchinc3f46a92015-07-23 12:50:55 -0700358 RTCLog(@"WARNING: Renegotiation needed but unimplemented.");
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000359}
360
361- (void)peerConnection:(RTCPeerConnection *)peerConnection
362 iceConnectionChanged:(RTCICEConnectionState)newState {
tkchinc3f46a92015-07-23 12:50:55 -0700363 RTCLog(@"ICE state changed: %d", newState);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000364 dispatch_async(dispatch_get_main_queue(), ^{
365 [_delegate appClient:self didChangeConnectionState:newState];
366 });
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000367}
368
369- (void)peerConnection:(RTCPeerConnection *)peerConnection
370 iceGatheringChanged:(RTCICEGatheringState)newState {
tkchinc3f46a92015-07-23 12:50:55 -0700371 RTCLog(@"ICE gathering state changed: %d", newState);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000372}
373
374- (void)peerConnection:(RTCPeerConnection *)peerConnection
375 gotICECandidate:(RTCICECandidate *)candidate {
376 dispatch_async(dispatch_get_main_queue(), ^{
377 ARDICECandidateMessage *message =
378 [[ARDICECandidateMessage alloc] initWithCandidate:candidate];
379 [self sendSignalingMessage:message];
380 });
381}
382
Zeke Chind3325802015-08-14 11:00:02 -0700383- (void)peerConnection:(RTCPeerConnection *)peerConnection
384 didOpenDataChannel:(RTCDataChannel *)dataChannel {
385}
386
387#pragma mark - RTCStatsDelegate
388
389- (void)peerConnection:(RTCPeerConnection *)peerConnection
390 didGetStats:(NSArray *)stats {
391 dispatch_async(dispatch_get_main_queue(), ^{
392 [_delegate appClient:self didGetStats:stats];
393 });
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000394}
395
396#pragma mark - RTCSessionDescriptionDelegate
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000397// Callbacks for this delegate occur on non-main thread and need to be
398// dispatched back to main queue as needed.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000399
400- (void)peerConnection:(RTCPeerConnection *)peerConnection
401 didCreateSessionDescription:(RTCSessionDescription *)sdp
402 error:(NSError *)error {
403 dispatch_async(dispatch_get_main_queue(), ^{
404 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700405 RTCLogError(@"Failed to create session description. Error: %@", error);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000406 [self disconnect];
407 NSDictionary *userInfo = @{
408 NSLocalizedDescriptionKey: @"Failed to create session description.",
409 };
410 NSError *sdpError =
411 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
412 code:kARDAppClientErrorCreateSDP
413 userInfo:userInfo];
414 [_delegate appClient:self didError:sdpError];
415 return;
416 }
Zeke Chin71f6f442015-06-29 14:34:58 -0700417 // Prefer H264 if available.
418 RTCSessionDescription *sdpPreferringH264 =
419 [ARDSDPUtils descriptionForDescription:sdp
420 preferredVideoCodec:@"H264"];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000421 [_peerConnection setLocalDescriptionWithDelegate:self
Zeke Chin71f6f442015-06-29 14:34:58 -0700422 sessionDescription:sdpPreferringH264];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000423 ARDSessionDescriptionMessage *message =
Zeke Chin71f6f442015-06-29 14:34:58 -0700424 [[ARDSessionDescriptionMessage alloc]
425 initWithDescription:sdpPreferringH264];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000426 [self sendSignalingMessage:message];
427 });
428}
429
430- (void)peerConnection:(RTCPeerConnection *)peerConnection
431 didSetSessionDescriptionWithError:(NSError *)error {
432 dispatch_async(dispatch_get_main_queue(), ^{
433 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700434 RTCLogError(@"Failed to set session description. Error: %@", error);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000435 [self disconnect];
436 NSDictionary *userInfo = @{
437 NSLocalizedDescriptionKey: @"Failed to set session description.",
438 };
439 NSError *sdpError =
440 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
441 code:kARDAppClientErrorSetSDP
442 userInfo:userInfo];
443 [_delegate appClient:self didError:sdpError];
444 return;
445 }
446 // If we're answering and we've just set the remote offer we need to create
447 // an answer and set the local description.
448 if (!_isInitiator && !_peerConnection.localDescription) {
449 RTCMediaConstraints *constraints = [self defaultAnswerConstraints];
450 [_peerConnection createAnswerWithDelegate:self
451 constraints:constraints];
452
453 }
454 });
455}
456
457#pragma mark - Private
458
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000459- (BOOL)hasJoinedRoomServerRoom {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000460 return _clientId.length;
461}
462
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000463// Begins the peer connection connection process if we have both joined a room
464// on the room server and tried to obtain a TURN server. Otherwise does nothing.
465// A peer connection object will be created with a stream that contains local
466// audio and video capture. If this client is the caller, an offer is created as
467// well, otherwise the client will wait for an offer to arrive.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000468- (void)startSignalingIfReady {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000469 if (!_isTurnComplete || !self.hasJoinedRoomServerRoom) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000470 return;
471 }
472 self.state = kARDAppClientStateConnected;
473
474 // Create peer connection.
475 RTCMediaConstraints *constraints = [self defaultPeerConnectionConstraints];
Zeke Chinbc7dd7e2015-05-29 14:59:14 -0700476 RTCConfiguration *config = [[RTCConfiguration alloc] init];
477 config.iceServers = _iceServers;
478 _peerConnection = [_factory peerConnectionWithConfiguration:config
479 constraints:constraints
480 delegate:self];
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000481 // Create AV media stream and add it to the peer connection.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000482 RTCMediaStream *localStream = [self createLocalMediaStream];
483 [_peerConnection addStream:localStream];
484 if (_isInitiator) {
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000485 // Send offer.
486 [_peerConnection createOfferWithDelegate:self
487 constraints:[self defaultOfferConstraints]];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000488 } else {
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000489 // Check if we've received an offer.
490 [self drainMessageQueueIfReady];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000491 }
492}
493
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000494// Processes the messages that we've received from the room server and the
495// signaling channel. The offer or answer message must be processed before other
496// signaling messages, however they can arrive out of order. Hence, this method
497// only processes pending messages if there is a peer connection object and
498// if we have received either an offer or answer.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000499- (void)drainMessageQueueIfReady {
500 if (!_peerConnection || !_hasReceivedSdp) {
501 return;
502 }
503 for (ARDSignalingMessage *message in _messageQueue) {
504 [self processSignalingMessage:message];
505 }
506 [_messageQueue removeAllObjects];
507}
508
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000509// Processes the given signaling message based on its type.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000510- (void)processSignalingMessage:(ARDSignalingMessage *)message {
511 NSParameterAssert(_peerConnection ||
512 message.type == kARDSignalingMessageTypeBye);
513 switch (message.type) {
514 case kARDSignalingMessageTypeOffer:
515 case kARDSignalingMessageTypeAnswer: {
516 ARDSessionDescriptionMessage *sdpMessage =
517 (ARDSessionDescriptionMessage *)message;
518 RTCSessionDescription *description = sdpMessage.sessionDescription;
Zeke Chin71f6f442015-06-29 14:34:58 -0700519 // Prefer H264 if available.
520 RTCSessionDescription *sdpPreferringH264 =
521 [ARDSDPUtils descriptionForDescription:description
522 preferredVideoCodec:@"H264"];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000523 [_peerConnection setRemoteDescriptionWithDelegate:self
Zeke Chin71f6f442015-06-29 14:34:58 -0700524 sessionDescription:sdpPreferringH264];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000525 break;
526 }
527 case kARDSignalingMessageTypeCandidate: {
528 ARDICECandidateMessage *candidateMessage =
529 (ARDICECandidateMessage *)message;
530 [_peerConnection addICECandidate:candidateMessage.candidate];
531 break;
532 }
533 case kARDSignalingMessageTypeBye:
534 // Other client disconnected.
535 // TODO(tkchin): support waiting in room for next client. For now just
536 // disconnect.
537 [self disconnect];
538 break;
539 }
540}
541
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000542// Sends a signaling message to the other client. The caller will send messages
543// through the room server, whereas the callee will send messages over the
544// signaling channel.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000545- (void)sendSignalingMessage:(ARDSignalingMessage *)message {
546 if (_isInitiator) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000547 __weak ARDAppClient *weakSelf = self;
548 [_roomServerClient sendMessage:message
549 forRoomId:_roomId
550 clientId:_clientId
551 completionHandler:^(ARDMessageResponse *response,
552 NSError *error) {
553 ARDAppClient *strongSelf = weakSelf;
554 if (error) {
555 [strongSelf.delegate appClient:strongSelf didError:error];
556 return;
557 }
558 NSError *messageError =
559 [[strongSelf class] errorForMessageResultType:response.result];
560 if (messageError) {
561 [strongSelf.delegate appClient:strongSelf didError:messageError];
562 return;
563 }
564 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000565 } else {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000566 [_channel sendMessage:message];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000567 }
568}
569
570- (RTCMediaStream *)createLocalMediaStream {
571 RTCMediaStream* localStream = [_factory mediaStreamWithLabel:@"ARDAMS"];
Zeke Chin57cc74e2015-05-05 07:52:31 -0700572 RTCVideoTrack* localVideoTrack = [self createLocalVideoTrack];
573 if (localVideoTrack) {
574 [localStream addVideoTrack:localVideoTrack];
575 [_delegate appClient:self didReceiveLocalVideoTrack:localVideoTrack];
576 }
577 [localStream addAudioTrack:[_factory audioTrackWithID:@"ARDAMSa0"]];
578 return localStream;
579}
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000580
Zeke Chin57cc74e2015-05-05 07:52:31 -0700581- (RTCVideoTrack *)createLocalVideoTrack {
582 RTCVideoTrack* localVideoTrack = nil;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000583 // The iOS simulator doesn't provide any sort of camera capture
584 // support or emulation (http://goo.gl/rHAnC1) so don't bother
585 // trying to open a local stream.
586 // TODO(tkchin): local video capture for OSX. See
587 // https://code.google.com/p/webrtc/issues/detail?id=3417.
588#if !TARGET_IPHONE_SIMULATOR && TARGET_OS_IPHONE
haysc913e6452015-10-02 11:44:03 -0700589 if (!_isAudioOnly) {
590 RTCMediaConstraints *mediaConstraints =
591 [self defaultMediaStreamConstraints];
592 RTCAVFoundationVideoSource *source =
593 [[RTCAVFoundationVideoSource alloc] initWithFactory:_factory
594 constraints:mediaConstraints];
595 localVideoTrack =
596 [[RTCVideoTrack alloc] initWithFactory:_factory
597 source:source
598 trackId:@"ARDAMSv0"];
599 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000600#endif
Zeke Chin57cc74e2015-05-05 07:52:31 -0700601 return localVideoTrack;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000602}
603
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000604#pragma mark - Collider methods
605
606- (void)registerWithColliderIfReady {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000607 if (!self.hasJoinedRoomServerRoom) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000608 return;
609 }
610 // Open WebSocket connection.
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000611 if (!_channel) {
612 _channel =
613 [[ARDWebSocketChannel alloc] initWithURL:_websocketURL
614 restURL:_websocketRestURL
615 delegate:self];
haysc913e6452015-10-02 11:44:03 -0700616 if (_isLoopback) {
617 _loopbackChannel =
618 [[ARDLoopbackWebSocketChannel alloc] initWithURL:_websocketURL
619 restURL:_websocketRestURL];
620 }
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000621 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000622 [_channel registerForRoomId:_roomId clientId:_clientId];
haysc913e6452015-10-02 11:44:03 -0700623 if (_isLoopback) {
624 [_loopbackChannel registerForRoomId:_roomId clientId:@"LOOPBACK_CLIENT_ID"];
625 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000626}
627
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000628#pragma mark - Defaults
629
630- (RTCMediaConstraints *)defaultMediaStreamConstraints {
631 RTCMediaConstraints* constraints =
632 [[RTCMediaConstraints alloc]
633 initWithMandatoryConstraints:nil
634 optionalConstraints:nil];
635 return constraints;
636}
637
638- (RTCMediaConstraints *)defaultAnswerConstraints {
639 return [self defaultOfferConstraints];
640}
641
642- (RTCMediaConstraints *)defaultOfferConstraints {
643 NSArray *mandatoryConstraints = @[
644 [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" value:@"true"],
645 [[RTCPair alloc] initWithKey:@"OfferToReceiveVideo" value:@"true"]
646 ];
647 RTCMediaConstraints* constraints =
648 [[RTCMediaConstraints alloc]
649 initWithMandatoryConstraints:mandatoryConstraints
650 optionalConstraints:nil];
651 return constraints;
652}
653
654- (RTCMediaConstraints *)defaultPeerConnectionConstraints {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000655 if (_defaultPeerConnectionConstraints) {
656 return _defaultPeerConnectionConstraints;
657 }
haysc913e6452015-10-02 11:44:03 -0700658 NSString *value = _isLoopback ? @"false" : @"true";
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000659 NSArray *optionalConstraints = @[
haysc913e6452015-10-02 11:44:03 -0700660 [[RTCPair alloc] initWithKey:@"DtlsSrtpKeyAgreement" value:value]
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000661 ];
662 RTCMediaConstraints* constraints =
663 [[RTCMediaConstraints alloc]
664 initWithMandatoryConstraints:nil
665 optionalConstraints:optionalConstraints];
666 return constraints;
667}
668
669- (RTCICEServer *)defaultSTUNServer {
670 NSURL *defaultSTUNServerURL = [NSURL URLWithString:kARDDefaultSTUNServerUrl];
671 return [[RTCICEServer alloc] initWithURI:defaultSTUNServerURL
672 username:@""
673 password:@""];
674}
675
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000676#pragma mark - Errors
677
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000678+ (NSError *)errorForJoinResultType:(ARDJoinResultType)resultType {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000679 NSError *error = nil;
680 switch (resultType) {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000681 case kARDJoinResultTypeSuccess:
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000682 break;
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000683 case kARDJoinResultTypeUnknown: {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000684 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
685 code:kARDAppClientErrorUnknown
686 userInfo:@{
687 NSLocalizedDescriptionKey: @"Unknown error.",
688 }];
689 break;
690 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000691 case kARDJoinResultTypeFull: {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000692 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
693 code:kARDAppClientErrorRoomFull
694 userInfo:@{
695 NSLocalizedDescriptionKey: @"Room is full.",
696 }];
697 break;
698 }
699 }
700 return error;
701}
702
703+ (NSError *)errorForMessageResultType:(ARDMessageResultType)resultType {
704 NSError *error = nil;
705 switch (resultType) {
706 case kARDMessageResultTypeSuccess:
707 break;
708 case kARDMessageResultTypeUnknown:
709 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
710 code:kARDAppClientErrorUnknown
711 userInfo:@{
712 NSLocalizedDescriptionKey: @"Unknown error.",
713 }];
714 break;
715 case kARDMessageResultTypeInvalidClient:
716 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
717 code:kARDAppClientErrorInvalidClient
718 userInfo:@{
719 NSLocalizedDescriptionKey: @"Invalid client.",
720 }];
721 break;
722 case kARDMessageResultTypeInvalidRoom:
723 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
724 code:kARDAppClientErrorInvalidRoom
725 userInfo:@{
726 NSLocalizedDescriptionKey: @"Invalid room.",
727 }];
728 break;
729 }
730 return error;
731}
732
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000733@end