blob: 47252bef1dbc4a9483f6687ac894976910160249 [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)
tkchind1fb26d2016-02-03 01:51:18 -080014#import "webrtc/base/objc/RTCTracing.h"
Zeke Chin57cc74e2015-05-05 07:52:31 -070015#import "RTCAVFoundationVideoSource.h"
16#endif
Zeke Chin2d3b7e22015-07-14 12:55:44 -070017#import "RTCFileLogger.h"
Zeke Chin57cc74e2015-05-05 07:52:31 -070018#import "RTCICEServer.h"
tkchinc3f46a92015-07-23 12:50:55 -070019#import "RTCLogging.h"
Zeke Chin57cc74e2015-05-05 07:52:31 -070020#import "RTCMediaConstraints.h"
21#import "RTCMediaStream.h"
22#import "RTCPair.h"
Zeke Chinbc7dd7e2015-05-29 14:59:14 -070023#import "RTCPeerConnectionInterface.h"
Zeke Chin57cc74e2015-05-05 07:52:31 -070024#import "RTCVideoCapturer.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000025
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000026#import "ARDAppEngineClient.h"
27#import "ARDCEODTURNClient.h"
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +000028#import "ARDJoinResponse.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000029#import "ARDMessageResponse.h"
Zeke Chin71f6f442015-06-29 14:34:58 -070030#import "ARDSDPUtils.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000031#import "ARDSignalingMessage.h"
32#import "ARDUtilities.h"
33#import "ARDWebSocketChannel.h"
34#import "RTCICECandidate+JSON.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000035#import "RTCSessionDescription+JSON.h"
Zeke Chin57cc74e2015-05-05 07:52:31 -070036
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +000037static NSString * const kARDDefaultSTUNServerUrl =
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000038 @"stun:stun.l.google.com:19302";
39// TODO(tkchin): figure out a better username for CEOD statistics.
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +000040static NSString * const kARDTurnRequestUrl =
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000041 @"https://computeengineondemand.appspot.com"
42 @"/turn?username=iapprtc&key=4080218913";
43
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +000044static NSString * const kARDAppClientErrorDomain = @"ARDAppClient";
45static NSInteger const kARDAppClientErrorUnknown = -1;
46static NSInteger const kARDAppClientErrorRoomFull = -2;
47static NSInteger const kARDAppClientErrorCreateSDP = -3;
48static NSInteger const kARDAppClientErrorSetSDP = -4;
49static NSInteger const kARDAppClientErrorInvalidClient = -5;
50static NSInteger const kARDAppClientErrorInvalidRoom = -6;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000051
tkchind1fb26d2016-02-03 01:51:18 -080052// TODO(tkchin): Remove guard once rtc_base_objc compiles on Mac.
53#if defined(WEBRTC_IOS)
54// TODO(tkchin): Add this as a UI option.
55static BOOL const kARDAppClientEnableTracing = NO;
56#endif
57
Zeke Chind3325802015-08-14 11:00:02 -070058// We need a proxy to NSTimer because it causes a strong retain cycle. When
59// using the proxy, |invalidate| must be called before it properly deallocs.
60@interface ARDTimerProxy : NSObject
61
62- (instancetype)initWithInterval:(NSTimeInterval)interval
63 repeats:(BOOL)repeats
64 timerHandler:(void (^)(void))timerHandler;
65- (void)invalidate;
66
67@end
68
69@implementation ARDTimerProxy {
70 NSTimer *_timer;
71 void (^_timerHandler)(void);
Zeke Chin2d3b7e22015-07-14 12:55:44 -070072}
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000073
Zeke Chind3325802015-08-14 11:00:02 -070074- (instancetype)initWithInterval:(NSTimeInterval)interval
75 repeats:(BOOL)repeats
76 timerHandler:(void (^)(void))timerHandler {
77 NSParameterAssert(timerHandler);
78 if (self = [super init]) {
79 _timerHandler = timerHandler;
80 _timer = [NSTimer scheduledTimerWithTimeInterval:interval
81 target:self
82 selector:@selector(timerDidFire:)
83 userInfo:nil
84 repeats:repeats];
85 }
86 return self;
87}
88
89- (void)invalidate {
90 [_timer invalidate];
91}
92
93- (void)timerDidFire:(NSTimer *)timer {
94 _timerHandler();
95}
96
97@end
98
99@implementation ARDAppClient {
100 RTCFileLogger *_fileLogger;
101 ARDTimerProxy *_statsTimer;
102}
103
104@synthesize shouldGetStats = _shouldGetStats;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000105@synthesize state = _state;
Zeke Chind3325802015-08-14 11:00:02 -0700106@synthesize delegate = _delegate;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000107@synthesize roomServerClient = _roomServerClient;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000108@synthesize channel = _channel;
haysc913e6452015-10-02 11:44:03 -0700109@synthesize loopbackChannel = _loopbackChannel;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000110@synthesize turnClient = _turnClient;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000111@synthesize peerConnection = _peerConnection;
112@synthesize factory = _factory;
113@synthesize messageQueue = _messageQueue;
114@synthesize isTurnComplete = _isTurnComplete;
115@synthesize hasReceivedSdp = _hasReceivedSdp;
116@synthesize roomId = _roomId;
117@synthesize clientId = _clientId;
118@synthesize isInitiator = _isInitiator;
119@synthesize iceServers = _iceServers;
120@synthesize webSocketURL = _websocketURL;
121@synthesize webSocketRestURL = _websocketRestURL;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000122@synthesize defaultPeerConnectionConstraints =
123 _defaultPeerConnectionConstraints;
haysc913e6452015-10-02 11:44:03 -0700124@synthesize isLoopback = _isLoopback;
125@synthesize isAudioOnly = _isAudioOnly;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000126
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000127- (instancetype)init {
128 if (self = [super init]) {
129 _roomServerClient = [[ARDAppEngineClient alloc] init];
130 NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl];
131 _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL];
132 [self configure];
133 }
134 return self;
135}
136
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000137- (instancetype)initWithDelegate:(id<ARDAppClientDelegate>)delegate {
138 if (self = [super init]) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000139 _roomServerClient = [[ARDAppEngineClient alloc] init];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000140 _delegate = delegate;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000141 NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl];
142 _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL];
143 [self configure];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000144 }
145 return self;
146}
147
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000148// TODO(tkchin): Provide signaling channel factory interface so we can recreate
149// channel if we need to on network failure. Also, make this the default public
150// constructor.
151- (instancetype)initWithRoomServerClient:(id<ARDRoomServerClient>)rsClient
152 signalingChannel:(id<ARDSignalingChannel>)channel
153 turnClient:(id<ARDTURNClient>)turnClient
154 delegate:(id<ARDAppClientDelegate>)delegate {
155 NSParameterAssert(rsClient);
156 NSParameterAssert(channel);
157 NSParameterAssert(turnClient);
158 if (self = [super init]) {
159 _roomServerClient = rsClient;
160 _channel = channel;
161 _turnClient = turnClient;
162 _delegate = delegate;
163 [self configure];
164 }
165 return self;
166}
167
168- (void)configure {
169 _factory = [[RTCPeerConnectionFactory alloc] init];
170 _messageQueue = [NSMutableArray array];
171 _iceServers = [NSMutableArray arrayWithObject:[self defaultSTUNServer]];
Zeke Chin2d3b7e22015-07-14 12:55:44 -0700172 _fileLogger = [[RTCFileLogger alloc] init];
173 [_fileLogger start];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000174}
175
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000176- (void)dealloc {
Zeke Chind3325802015-08-14 11:00:02 -0700177 self.shouldGetStats = NO;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000178 [self disconnect];
179}
180
Zeke Chind3325802015-08-14 11:00:02 -0700181- (void)setShouldGetStats:(BOOL)shouldGetStats {
182 if (_shouldGetStats == shouldGetStats) {
183 return;
184 }
185 if (shouldGetStats) {
186 __weak ARDAppClient *weakSelf = self;
187 _statsTimer = [[ARDTimerProxy alloc] initWithInterval:1
188 repeats:YES
189 timerHandler:^{
190 ARDAppClient *strongSelf = weakSelf;
191 [strongSelf.peerConnection getStatsWithDelegate:strongSelf
192 mediaStreamTrack:nil
193 statsOutputLevel:RTCStatsOutputLevelDebug];
194 }];
195 } else {
196 [_statsTimer invalidate];
197 _statsTimer = nil;
198 }
199 _shouldGetStats = shouldGetStats;
200}
201
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000202- (void)setState:(ARDAppClientState)state {
203 if (_state == state) {
204 return;
205 }
206 _state = state;
207 [_delegate appClient:self didChangeState:_state];
208}
209
210- (void)connectToRoomWithId:(NSString *)roomId
haysc913e6452015-10-02 11:44:03 -0700211 isLoopback:(BOOL)isLoopback
212 isAudioOnly:(BOOL)isAudioOnly {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000213 NSParameterAssert(roomId.length);
214 NSParameterAssert(_state == kARDAppClientStateDisconnected);
haysc913e6452015-10-02 11:44:03 -0700215 _isLoopback = isLoopback;
216 _isAudioOnly = isAudioOnly;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000217 self.state = kARDAppClientStateConnecting;
218
tkchind1fb26d2016-02-03 01:51:18 -0800219#if defined(WEBRTC_IOS)
220 if (kARDAppClientEnableTracing) {
221 NSArray *paths = NSSearchPathForDirectoriesInDomains(
222 NSDocumentDirectory, NSUserDomainMask, YES);
223 NSString *documentsDirPath = paths.firstObject;
224 NSString *filePath =
225 [documentsDirPath stringByAppendingPathComponent:@"webrtc-trace.txt"];
226 RTCStartInternalCapture(filePath);
227 }
228#endif
229
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000230 // Request TURN.
231 __weak ARDAppClient *weakSelf = self;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000232 [_turnClient requestServersWithCompletionHandler:^(NSArray *turnServers,
233 NSError *error) {
234 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700235 RTCLogError("Error retrieving TURN servers: %@",
236 error.localizedDescription);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000237 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000238 ARDAppClient *strongSelf = weakSelf;
239 [strongSelf.iceServers addObjectsFromArray:turnServers];
240 strongSelf.isTurnComplete = YES;
241 [strongSelf startSignalingIfReady];
242 }];
jiayl@webrtc.org27f53172014-12-31 00:26:20 +0000243
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000244 // Join room on room server.
245 [_roomServerClient joinRoomWithRoomId:roomId
haysc913e6452015-10-02 11:44:03 -0700246 isLoopback:isLoopback
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000247 completionHandler:^(ARDJoinResponse *response, NSError *error) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000248 ARDAppClient *strongSelf = weakSelf;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000249 if (error) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000250 [strongSelf.delegate appClient:strongSelf didError:error];
251 return;
252 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000253 NSError *joinError =
254 [[strongSelf class] errorForJoinResultType:response.result];
255 if (joinError) {
tkchinc3f46a92015-07-23 12:50:55 -0700256 RTCLogError(@"Failed to join room:%@ on room server.", roomId);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000257 [strongSelf disconnect];
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000258 [strongSelf.delegate appClient:strongSelf didError:joinError];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000259 return;
260 }
tkchinc3f46a92015-07-23 12:50:55 -0700261 RTCLog(@"Joined room:%@ on room server.", roomId);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000262 strongSelf.roomId = response.roomId;
263 strongSelf.clientId = response.clientId;
264 strongSelf.isInitiator = response.isInitiator;
265 for (ARDSignalingMessage *message in response.messages) {
266 if (message.type == kARDSignalingMessageTypeOffer ||
267 message.type == kARDSignalingMessageTypeAnswer) {
268 strongSelf.hasReceivedSdp = YES;
269 [strongSelf.messageQueue insertObject:message atIndex:0];
270 } else {
271 [strongSelf.messageQueue addObject:message];
272 }
273 }
274 strongSelf.webSocketURL = response.webSocketURL;
275 strongSelf.webSocketRestURL = response.webSocketRestURL;
276 [strongSelf registerWithColliderIfReady];
277 [strongSelf startSignalingIfReady];
278 }];
279}
280
281- (void)disconnect {
282 if (_state == kARDAppClientStateDisconnected) {
283 return;
284 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000285 if (self.hasJoinedRoomServerRoom) {
286 [_roomServerClient leaveRoomWithRoomId:_roomId
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000287 clientId:_clientId
288 completionHandler:nil];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000289 }
290 if (_channel) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000291 if (_channel.state == kARDSignalingChannelStateRegistered) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000292 // Tell the other client we're hanging up.
293 ARDByeMessage *byeMessage = [[ARDByeMessage alloc] init];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000294 [_channel sendMessage:byeMessage];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000295 }
296 // Disconnect from collider.
297 _channel = nil;
298 }
299 _clientId = nil;
300 _roomId = nil;
301 _isInitiator = NO;
302 _hasReceivedSdp = NO;
303 _messageQueue = [NSMutableArray array];
304 _peerConnection = nil;
305 self.state = kARDAppClientStateDisconnected;
tkchind1fb26d2016-02-03 01:51:18 -0800306#if defined(WEBRTC_IOS)
307 RTCStopInternalCapture();
308#endif
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000309}
310
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000311#pragma mark - ARDSignalingChannelDelegate
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000312
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000313- (void)channel:(id<ARDSignalingChannel>)channel
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000314 didReceiveMessage:(ARDSignalingMessage *)message {
315 switch (message.type) {
316 case kARDSignalingMessageTypeOffer:
317 case kARDSignalingMessageTypeAnswer:
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000318 // Offers and answers must be processed before any other message, so we
319 // place them at the front of the queue.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000320 _hasReceivedSdp = YES;
321 [_messageQueue insertObject:message atIndex:0];
322 break;
323 case kARDSignalingMessageTypeCandidate:
324 [_messageQueue addObject:message];
325 break;
326 case kARDSignalingMessageTypeBye:
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000327 // Disconnects can be processed immediately.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000328 [self processSignalingMessage:message];
329 return;
330 }
331 [self drainMessageQueueIfReady];
332}
333
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000334- (void)channel:(id<ARDSignalingChannel>)channel
335 didChangeState:(ARDSignalingChannelState)state {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000336 switch (state) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000337 case kARDSignalingChannelStateOpen:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000338 break;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000339 case kARDSignalingChannelStateRegistered:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000340 break;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000341 case kARDSignalingChannelStateClosed:
342 case kARDSignalingChannelStateError:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000343 // TODO(tkchin): reconnection scenarios. Right now we just disconnect
344 // completely if the websocket connection fails.
345 [self disconnect];
346 break;
347 }
348}
349
350#pragma mark - RTCPeerConnectionDelegate
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000351// Callbacks for this delegate occur on non-main thread and need to be
352// dispatched back to main queue as needed.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000353
354- (void)peerConnection:(RTCPeerConnection *)peerConnection
355 signalingStateChanged:(RTCSignalingState)stateChanged {
tkchinc3f46a92015-07-23 12:50:55 -0700356 RTCLog(@"Signaling state changed: %d", stateChanged);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000357}
358
359- (void)peerConnection:(RTCPeerConnection *)peerConnection
360 addedStream:(RTCMediaStream *)stream {
361 dispatch_async(dispatch_get_main_queue(), ^{
tkchinc3f46a92015-07-23 12:50:55 -0700362 RTCLog(@"Received %lu video tracks and %lu audio tracks",
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000363 (unsigned long)stream.videoTracks.count,
364 (unsigned long)stream.audioTracks.count);
365 if (stream.videoTracks.count) {
366 RTCVideoTrack *videoTrack = stream.videoTracks[0];
367 [_delegate appClient:self didReceiveRemoteVideoTrack:videoTrack];
368 }
369 });
370}
371
372- (void)peerConnection:(RTCPeerConnection *)peerConnection
373 removedStream:(RTCMediaStream *)stream {
tkchinc3f46a92015-07-23 12:50:55 -0700374 RTCLog(@"Stream was removed.");
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000375}
376
377- (void)peerConnectionOnRenegotiationNeeded:
378 (RTCPeerConnection *)peerConnection {
tkchinc3f46a92015-07-23 12:50:55 -0700379 RTCLog(@"WARNING: Renegotiation needed but unimplemented.");
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000380}
381
382- (void)peerConnection:(RTCPeerConnection *)peerConnection
383 iceConnectionChanged:(RTCICEConnectionState)newState {
tkchinc3f46a92015-07-23 12:50:55 -0700384 RTCLog(@"ICE state changed: %d", newState);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000385 dispatch_async(dispatch_get_main_queue(), ^{
386 [_delegate appClient:self didChangeConnectionState:newState];
387 });
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000388}
389
390- (void)peerConnection:(RTCPeerConnection *)peerConnection
391 iceGatheringChanged:(RTCICEGatheringState)newState {
tkchinc3f46a92015-07-23 12:50:55 -0700392 RTCLog(@"ICE gathering state changed: %d", newState);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000393}
394
395- (void)peerConnection:(RTCPeerConnection *)peerConnection
396 gotICECandidate:(RTCICECandidate *)candidate {
397 dispatch_async(dispatch_get_main_queue(), ^{
398 ARDICECandidateMessage *message =
399 [[ARDICECandidateMessage alloc] initWithCandidate:candidate];
400 [self sendSignalingMessage:message];
401 });
402}
403
Zeke Chind3325802015-08-14 11:00:02 -0700404- (void)peerConnection:(RTCPeerConnection *)peerConnection
405 didOpenDataChannel:(RTCDataChannel *)dataChannel {
406}
407
408#pragma mark - RTCStatsDelegate
409
410- (void)peerConnection:(RTCPeerConnection *)peerConnection
411 didGetStats:(NSArray *)stats {
412 dispatch_async(dispatch_get_main_queue(), ^{
413 [_delegate appClient:self didGetStats:stats];
414 });
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000415}
416
417#pragma mark - RTCSessionDescriptionDelegate
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000418// Callbacks for this delegate occur on non-main thread and need to be
419// dispatched back to main queue as needed.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000420
421- (void)peerConnection:(RTCPeerConnection *)peerConnection
422 didCreateSessionDescription:(RTCSessionDescription *)sdp
423 error:(NSError *)error {
424 dispatch_async(dispatch_get_main_queue(), ^{
425 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700426 RTCLogError(@"Failed to create session description. Error: %@", error);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000427 [self disconnect];
428 NSDictionary *userInfo = @{
429 NSLocalizedDescriptionKey: @"Failed to create session description.",
430 };
431 NSError *sdpError =
432 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
433 code:kARDAppClientErrorCreateSDP
434 userInfo:userInfo];
435 [_delegate appClient:self didError:sdpError];
436 return;
437 }
Zeke Chin71f6f442015-06-29 14:34:58 -0700438 // Prefer H264 if available.
439 RTCSessionDescription *sdpPreferringH264 =
440 [ARDSDPUtils descriptionForDescription:sdp
441 preferredVideoCodec:@"H264"];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000442 [_peerConnection setLocalDescriptionWithDelegate:self
Zeke Chin71f6f442015-06-29 14:34:58 -0700443 sessionDescription:sdpPreferringH264];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000444 ARDSessionDescriptionMessage *message =
Zeke Chin71f6f442015-06-29 14:34:58 -0700445 [[ARDSessionDescriptionMessage alloc]
446 initWithDescription:sdpPreferringH264];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000447 [self sendSignalingMessage:message];
448 });
449}
450
451- (void)peerConnection:(RTCPeerConnection *)peerConnection
452 didSetSessionDescriptionWithError:(NSError *)error {
453 dispatch_async(dispatch_get_main_queue(), ^{
454 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700455 RTCLogError(@"Failed to set session description. Error: %@", error);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000456 [self disconnect];
457 NSDictionary *userInfo = @{
458 NSLocalizedDescriptionKey: @"Failed to set session description.",
459 };
460 NSError *sdpError =
461 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
462 code:kARDAppClientErrorSetSDP
463 userInfo:userInfo];
464 [_delegate appClient:self didError:sdpError];
465 return;
466 }
467 // If we're answering and we've just set the remote offer we need to create
468 // an answer and set the local description.
469 if (!_isInitiator && !_peerConnection.localDescription) {
470 RTCMediaConstraints *constraints = [self defaultAnswerConstraints];
471 [_peerConnection createAnswerWithDelegate:self
472 constraints:constraints];
473
474 }
475 });
476}
477
478#pragma mark - Private
479
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000480- (BOOL)hasJoinedRoomServerRoom {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000481 return _clientId.length;
482}
483
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000484// Begins the peer connection connection process if we have both joined a room
485// on the room server and tried to obtain a TURN server. Otherwise does nothing.
486// A peer connection object will be created with a stream that contains local
487// audio and video capture. If this client is the caller, an offer is created as
488// well, otherwise the client will wait for an offer to arrive.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000489- (void)startSignalingIfReady {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000490 if (!_isTurnComplete || !self.hasJoinedRoomServerRoom) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000491 return;
492 }
493 self.state = kARDAppClientStateConnected;
494
495 // Create peer connection.
496 RTCMediaConstraints *constraints = [self defaultPeerConnectionConstraints];
Zeke Chinbc7dd7e2015-05-29 14:59:14 -0700497 RTCConfiguration *config = [[RTCConfiguration alloc] init];
498 config.iceServers = _iceServers;
499 _peerConnection = [_factory peerConnectionWithConfiguration:config
500 constraints:constraints
501 delegate:self];
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000502 // Create AV media stream and add it to the peer connection.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000503 RTCMediaStream *localStream = [self createLocalMediaStream];
504 [_peerConnection addStream:localStream];
505 if (_isInitiator) {
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000506 // Send offer.
507 [_peerConnection createOfferWithDelegate:self
508 constraints:[self defaultOfferConstraints]];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000509 } else {
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000510 // Check if we've received an offer.
511 [self drainMessageQueueIfReady];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000512 }
513}
514
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000515// Processes the messages that we've received from the room server and the
516// signaling channel. The offer or answer message must be processed before other
517// signaling messages, however they can arrive out of order. Hence, this method
518// only processes pending messages if there is a peer connection object and
519// if we have received either an offer or answer.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000520- (void)drainMessageQueueIfReady {
521 if (!_peerConnection || !_hasReceivedSdp) {
522 return;
523 }
524 for (ARDSignalingMessage *message in _messageQueue) {
525 [self processSignalingMessage:message];
526 }
527 [_messageQueue removeAllObjects];
528}
529
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000530// Processes the given signaling message based on its type.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000531- (void)processSignalingMessage:(ARDSignalingMessage *)message {
532 NSParameterAssert(_peerConnection ||
533 message.type == kARDSignalingMessageTypeBye);
534 switch (message.type) {
535 case kARDSignalingMessageTypeOffer:
536 case kARDSignalingMessageTypeAnswer: {
537 ARDSessionDescriptionMessage *sdpMessage =
538 (ARDSessionDescriptionMessage *)message;
539 RTCSessionDescription *description = sdpMessage.sessionDescription;
Zeke Chin71f6f442015-06-29 14:34:58 -0700540 // Prefer H264 if available.
541 RTCSessionDescription *sdpPreferringH264 =
542 [ARDSDPUtils descriptionForDescription:description
543 preferredVideoCodec:@"H264"];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000544 [_peerConnection setRemoteDescriptionWithDelegate:self
Zeke Chin71f6f442015-06-29 14:34:58 -0700545 sessionDescription:sdpPreferringH264];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000546 break;
547 }
548 case kARDSignalingMessageTypeCandidate: {
549 ARDICECandidateMessage *candidateMessage =
550 (ARDICECandidateMessage *)message;
551 [_peerConnection addICECandidate:candidateMessage.candidate];
552 break;
553 }
554 case kARDSignalingMessageTypeBye:
555 // Other client disconnected.
556 // TODO(tkchin): support waiting in room for next client. For now just
557 // disconnect.
558 [self disconnect];
559 break;
560 }
561}
562
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000563// Sends a signaling message to the other client. The caller will send messages
564// through the room server, whereas the callee will send messages over the
565// signaling channel.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000566- (void)sendSignalingMessage:(ARDSignalingMessage *)message {
567 if (_isInitiator) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000568 __weak ARDAppClient *weakSelf = self;
569 [_roomServerClient sendMessage:message
570 forRoomId:_roomId
571 clientId:_clientId
572 completionHandler:^(ARDMessageResponse *response,
573 NSError *error) {
574 ARDAppClient *strongSelf = weakSelf;
575 if (error) {
576 [strongSelf.delegate appClient:strongSelf didError:error];
577 return;
578 }
579 NSError *messageError =
580 [[strongSelf class] errorForMessageResultType:response.result];
581 if (messageError) {
582 [strongSelf.delegate appClient:strongSelf didError:messageError];
583 return;
584 }
585 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000586 } else {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000587 [_channel sendMessage:message];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000588 }
589}
590
591- (RTCMediaStream *)createLocalMediaStream {
592 RTCMediaStream* localStream = [_factory mediaStreamWithLabel:@"ARDAMS"];
Zeke Chin57cc74e2015-05-05 07:52:31 -0700593 RTCVideoTrack* localVideoTrack = [self createLocalVideoTrack];
594 if (localVideoTrack) {
595 [localStream addVideoTrack:localVideoTrack];
596 [_delegate appClient:self didReceiveLocalVideoTrack:localVideoTrack];
597 }
598 [localStream addAudioTrack:[_factory audioTrackWithID:@"ARDAMSa0"]];
599 return localStream;
600}
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000601
Zeke Chin57cc74e2015-05-05 07:52:31 -0700602- (RTCVideoTrack *)createLocalVideoTrack {
603 RTCVideoTrack* localVideoTrack = nil;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000604 // The iOS simulator doesn't provide any sort of camera capture
605 // support or emulation (http://goo.gl/rHAnC1) so don't bother
606 // trying to open a local stream.
607 // TODO(tkchin): local video capture for OSX. See
608 // https://code.google.com/p/webrtc/issues/detail?id=3417.
609#if !TARGET_IPHONE_SIMULATOR && TARGET_OS_IPHONE
haysc913e6452015-10-02 11:44:03 -0700610 if (!_isAudioOnly) {
611 RTCMediaConstraints *mediaConstraints =
612 [self defaultMediaStreamConstraints];
613 RTCAVFoundationVideoSource *source =
614 [[RTCAVFoundationVideoSource alloc] initWithFactory:_factory
615 constraints:mediaConstraints];
616 localVideoTrack =
617 [[RTCVideoTrack alloc] initWithFactory:_factory
618 source:source
619 trackId:@"ARDAMSv0"];
620 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000621#endif
Zeke Chin57cc74e2015-05-05 07:52:31 -0700622 return localVideoTrack;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000623}
624
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000625#pragma mark - Collider methods
626
627- (void)registerWithColliderIfReady {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000628 if (!self.hasJoinedRoomServerRoom) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000629 return;
630 }
631 // Open WebSocket connection.
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000632 if (!_channel) {
633 _channel =
634 [[ARDWebSocketChannel alloc] initWithURL:_websocketURL
635 restURL:_websocketRestURL
636 delegate:self];
haysc913e6452015-10-02 11:44:03 -0700637 if (_isLoopback) {
638 _loopbackChannel =
639 [[ARDLoopbackWebSocketChannel alloc] initWithURL:_websocketURL
640 restURL:_websocketRestURL];
641 }
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000642 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000643 [_channel registerForRoomId:_roomId clientId:_clientId];
haysc913e6452015-10-02 11:44:03 -0700644 if (_isLoopback) {
645 [_loopbackChannel registerForRoomId:_roomId clientId:@"LOOPBACK_CLIENT_ID"];
646 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000647}
648
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000649#pragma mark - Defaults
650
651- (RTCMediaConstraints *)defaultMediaStreamConstraints {
652 RTCMediaConstraints* constraints =
653 [[RTCMediaConstraints alloc]
654 initWithMandatoryConstraints:nil
655 optionalConstraints:nil];
656 return constraints;
657}
658
659- (RTCMediaConstraints *)defaultAnswerConstraints {
660 return [self defaultOfferConstraints];
661}
662
663- (RTCMediaConstraints *)defaultOfferConstraints {
664 NSArray *mandatoryConstraints = @[
665 [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" value:@"true"],
666 [[RTCPair alloc] initWithKey:@"OfferToReceiveVideo" value:@"true"]
667 ];
668 RTCMediaConstraints* constraints =
669 [[RTCMediaConstraints alloc]
670 initWithMandatoryConstraints:mandatoryConstraints
671 optionalConstraints:nil];
672 return constraints;
673}
674
675- (RTCMediaConstraints *)defaultPeerConnectionConstraints {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000676 if (_defaultPeerConnectionConstraints) {
677 return _defaultPeerConnectionConstraints;
678 }
haysc913e6452015-10-02 11:44:03 -0700679 NSString *value = _isLoopback ? @"false" : @"true";
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000680 NSArray *optionalConstraints = @[
haysc913e6452015-10-02 11:44:03 -0700681 [[RTCPair alloc] initWithKey:@"DtlsSrtpKeyAgreement" value:value]
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000682 ];
683 RTCMediaConstraints* constraints =
684 [[RTCMediaConstraints alloc]
685 initWithMandatoryConstraints:nil
686 optionalConstraints:optionalConstraints];
687 return constraints;
688}
689
690- (RTCICEServer *)defaultSTUNServer {
691 NSURL *defaultSTUNServerURL = [NSURL URLWithString:kARDDefaultSTUNServerUrl];
692 return [[RTCICEServer alloc] initWithURI:defaultSTUNServerURL
693 username:@""
694 password:@""];
695}
696
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000697#pragma mark - Errors
698
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000699+ (NSError *)errorForJoinResultType:(ARDJoinResultType)resultType {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000700 NSError *error = nil;
701 switch (resultType) {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000702 case kARDJoinResultTypeSuccess:
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000703 break;
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000704 case kARDJoinResultTypeUnknown: {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000705 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
706 code:kARDAppClientErrorUnknown
707 userInfo:@{
708 NSLocalizedDescriptionKey: @"Unknown error.",
709 }];
710 break;
711 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000712 case kARDJoinResultTypeFull: {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000713 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
714 code:kARDAppClientErrorRoomFull
715 userInfo:@{
716 NSLocalizedDescriptionKey: @"Room is full.",
717 }];
718 break;
719 }
720 }
721 return error;
722}
723
724+ (NSError *)errorForMessageResultType:(ARDMessageResultType)resultType {
725 NSError *error = nil;
726 switch (resultType) {
727 case kARDMessageResultTypeSuccess:
728 break;
729 case kARDMessageResultTypeUnknown:
730 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
731 code:kARDAppClientErrorUnknown
732 userInfo:@{
733 NSLocalizedDescriptionKey: @"Unknown error.",
734 }];
735 break;
736 case kARDMessageResultTypeInvalidClient:
737 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
738 code:kARDAppClientErrorInvalidClient
739 userInfo:@{
740 NSLocalizedDescriptionKey: @"Invalid client.",
741 }];
742 break;
743 case kARDMessageResultTypeInvalidRoom:
744 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
745 code:kARDAppClientErrorInvalidRoom
746 userInfo:@{
747 NSLocalizedDescriptionKey: @"Invalid room.",
748 }];
749 break;
750 }
751 return error;
752}
753
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000754@end