blob: d8dc7714248af61d4ff560d557682749a180433d [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"
hjon79858f82016-03-13 22:08:26 -070015#import "webrtc/api/objc/RTCAVFoundationVideoSource.h"
Zeke Chin57cc74e2015-05-05 07:52:31 -070016#endif
hjon79858f82016-03-13 22:08:26 -070017#import "webrtc/api/objc/RTCAudioTrack.h"
18#import "webrtc/api/objc/RTCConfiguration.h"
19#import "webrtc/api/objc/RTCIceServer.h"
20#import "webrtc/api/objc/RTCMediaConstraints.h"
21#import "webrtc/api/objc/RTCMediaStream.h"
22#import "webrtc/api/objc/RTCPeerConnectionFactory.h"
23#import "webrtc/base/objc/RTCFileLogger.h"
24#import "webrtc/base/objc/RTCLogging.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"
hjon79858f82016-03-13 22:08:26 -070034#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;
hjon79858f82016-03-13 22:08:26 -0700191 [strongSelf.peerConnection statsForTrack:nil
192 statsOutputLevel:RTCStatsOutputLevelDebug
193 completionHandler:^(NSArray *stats) {
194 dispatch_async(dispatch_get_main_queue(), ^{
195 ARDAppClient *strongSelf = weakSelf;
196 [strongSelf.delegate appClient:strongSelf didGetStats:stats];
197 });
198 }];
Zeke Chind3325802015-08-14 11:00:02 -0700199 }];
200 } else {
201 [_statsTimer invalidate];
202 _statsTimer = nil;
203 }
204 _shouldGetStats = shouldGetStats;
205}
206
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000207- (void)setState:(ARDAppClientState)state {
208 if (_state == state) {
209 return;
210 }
211 _state = state;
212 [_delegate appClient:self didChangeState:_state];
213}
214
215- (void)connectToRoomWithId:(NSString *)roomId
haysc913e6452015-10-02 11:44:03 -0700216 isLoopback:(BOOL)isLoopback
217 isAudioOnly:(BOOL)isAudioOnly {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000218 NSParameterAssert(roomId.length);
219 NSParameterAssert(_state == kARDAppClientStateDisconnected);
haysc913e6452015-10-02 11:44:03 -0700220 _isLoopback = isLoopback;
221 _isAudioOnly = isAudioOnly;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000222 self.state = kARDAppClientStateConnecting;
223
tkchind1fb26d2016-02-03 01:51:18 -0800224#if defined(WEBRTC_IOS)
225 if (kARDAppClientEnableTracing) {
226 NSArray *paths = NSSearchPathForDirectoriesInDomains(
227 NSDocumentDirectory, NSUserDomainMask, YES);
228 NSString *documentsDirPath = paths.firstObject;
229 NSString *filePath =
230 [documentsDirPath stringByAppendingPathComponent:@"webrtc-trace.txt"];
231 RTCStartInternalCapture(filePath);
232 }
233#endif
234
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000235 // Request TURN.
236 __weak ARDAppClient *weakSelf = self;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000237 [_turnClient requestServersWithCompletionHandler:^(NSArray *turnServers,
238 NSError *error) {
239 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700240 RTCLogError("Error retrieving TURN servers: %@",
241 error.localizedDescription);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000242 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000243 ARDAppClient *strongSelf = weakSelf;
244 [strongSelf.iceServers addObjectsFromArray:turnServers];
245 strongSelf.isTurnComplete = YES;
246 [strongSelf startSignalingIfReady];
247 }];
jiayl@webrtc.org27f53172014-12-31 00:26:20 +0000248
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000249 // Join room on room server.
250 [_roomServerClient joinRoomWithRoomId:roomId
haysc913e6452015-10-02 11:44:03 -0700251 isLoopback:isLoopback
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000252 completionHandler:^(ARDJoinResponse *response, NSError *error) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000253 ARDAppClient *strongSelf = weakSelf;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000254 if (error) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000255 [strongSelf.delegate appClient:strongSelf didError:error];
256 return;
257 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000258 NSError *joinError =
259 [[strongSelf class] errorForJoinResultType:response.result];
260 if (joinError) {
tkchinc3f46a92015-07-23 12:50:55 -0700261 RTCLogError(@"Failed to join room:%@ on room server.", roomId);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000262 [strongSelf disconnect];
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000263 [strongSelf.delegate appClient:strongSelf didError:joinError];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000264 return;
265 }
tkchinc3f46a92015-07-23 12:50:55 -0700266 RTCLog(@"Joined room:%@ on room server.", roomId);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000267 strongSelf.roomId = response.roomId;
268 strongSelf.clientId = response.clientId;
269 strongSelf.isInitiator = response.isInitiator;
270 for (ARDSignalingMessage *message in response.messages) {
271 if (message.type == kARDSignalingMessageTypeOffer ||
272 message.type == kARDSignalingMessageTypeAnswer) {
273 strongSelf.hasReceivedSdp = YES;
274 [strongSelf.messageQueue insertObject:message atIndex:0];
275 } else {
276 [strongSelf.messageQueue addObject:message];
277 }
278 }
279 strongSelf.webSocketURL = response.webSocketURL;
280 strongSelf.webSocketRestURL = response.webSocketRestURL;
281 [strongSelf registerWithColliderIfReady];
282 [strongSelf startSignalingIfReady];
283 }];
284}
285
286- (void)disconnect {
287 if (_state == kARDAppClientStateDisconnected) {
288 return;
289 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000290 if (self.hasJoinedRoomServerRoom) {
291 [_roomServerClient leaveRoomWithRoomId:_roomId
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000292 clientId:_clientId
293 completionHandler:nil];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000294 }
295 if (_channel) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000296 if (_channel.state == kARDSignalingChannelStateRegistered) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000297 // Tell the other client we're hanging up.
298 ARDByeMessage *byeMessage = [[ARDByeMessage alloc] init];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000299 [_channel sendMessage:byeMessage];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000300 }
301 // Disconnect from collider.
302 _channel = nil;
303 }
304 _clientId = nil;
305 _roomId = nil;
306 _isInitiator = NO;
307 _hasReceivedSdp = NO;
308 _messageQueue = [NSMutableArray array];
309 _peerConnection = nil;
310 self.state = kARDAppClientStateDisconnected;
tkchind1fb26d2016-02-03 01:51:18 -0800311#if defined(WEBRTC_IOS)
312 RTCStopInternalCapture();
313#endif
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000314}
315
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000316#pragma mark - ARDSignalingChannelDelegate
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000317
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000318- (void)channel:(id<ARDSignalingChannel>)channel
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000319 didReceiveMessage:(ARDSignalingMessage *)message {
320 switch (message.type) {
321 case kARDSignalingMessageTypeOffer:
322 case kARDSignalingMessageTypeAnswer:
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000323 // Offers and answers must be processed before any other message, so we
324 // place them at the front of the queue.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000325 _hasReceivedSdp = YES;
326 [_messageQueue insertObject:message atIndex:0];
327 break;
328 case kARDSignalingMessageTypeCandidate:
329 [_messageQueue addObject:message];
330 break;
331 case kARDSignalingMessageTypeBye:
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000332 // Disconnects can be processed immediately.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000333 [self processSignalingMessage:message];
334 return;
335 }
336 [self drainMessageQueueIfReady];
337}
338
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000339- (void)channel:(id<ARDSignalingChannel>)channel
340 didChangeState:(ARDSignalingChannelState)state {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000341 switch (state) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000342 case kARDSignalingChannelStateOpen:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000343 break;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000344 case kARDSignalingChannelStateRegistered:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000345 break;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000346 case kARDSignalingChannelStateClosed:
347 case kARDSignalingChannelStateError:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000348 // TODO(tkchin): reconnection scenarios. Right now we just disconnect
349 // completely if the websocket connection fails.
350 [self disconnect];
351 break;
352 }
353}
354
355#pragma mark - RTCPeerConnectionDelegate
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000356// Callbacks for this delegate occur on non-main thread and need to be
357// dispatched back to main queue as needed.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000358
359- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700360 didChangeSignalingState:(RTCSignalingState)stateChanged {
361 RTCLog(@"Signaling state changed: %ld", (long)stateChanged);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000362}
363
364- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700365 didAddStream:(RTCMediaStream *)stream {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000366 dispatch_async(dispatch_get_main_queue(), ^{
tkchinc3f46a92015-07-23 12:50:55 -0700367 RTCLog(@"Received %lu video tracks and %lu audio tracks",
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000368 (unsigned long)stream.videoTracks.count,
369 (unsigned long)stream.audioTracks.count);
370 if (stream.videoTracks.count) {
371 RTCVideoTrack *videoTrack = stream.videoTracks[0];
372 [_delegate appClient:self didReceiveRemoteVideoTrack:videoTrack];
373 }
374 });
375}
376
377- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700378 didRemoveStream:(RTCMediaStream *)stream {
tkchinc3f46a92015-07-23 12:50:55 -0700379 RTCLog(@"Stream was removed.");
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000380}
381
hjon79858f82016-03-13 22:08:26 -0700382- (void)peerConnectionShouldNegotiate:(RTCPeerConnection *)peerConnection {
tkchinc3f46a92015-07-23 12:50:55 -0700383 RTCLog(@"WARNING: Renegotiation needed but unimplemented.");
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000384}
385
386- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700387 didChangeIceConnectionState:(RTCIceConnectionState)newState {
388 RTCLog(@"ICE state changed: %ld", (long)newState);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000389 dispatch_async(dispatch_get_main_queue(), ^{
390 [_delegate appClient:self didChangeConnectionState:newState];
391 });
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000392}
393
394- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700395 didChangeIceGatheringState:(RTCIceGatheringState)newState {
396 RTCLog(@"ICE gathering state changed: %ld", (long)newState);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000397}
398
399- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700400 didGenerateIceCandidate:(RTCIceCandidate *)candidate {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000401 dispatch_async(dispatch_get_main_queue(), ^{
402 ARDICECandidateMessage *message =
403 [[ARDICECandidateMessage alloc] initWithCandidate:candidate];
404 [self sendSignalingMessage:message];
405 });
406}
407
Zeke Chind3325802015-08-14 11:00:02 -0700408- (void)peerConnection:(RTCPeerConnection *)peerConnection
409 didOpenDataChannel:(RTCDataChannel *)dataChannel {
410}
411
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000412#pragma mark - RTCSessionDescriptionDelegate
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000413// Callbacks for this delegate occur on non-main thread and need to be
414// dispatched back to main queue as needed.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000415
416- (void)peerConnection:(RTCPeerConnection *)peerConnection
417 didCreateSessionDescription:(RTCSessionDescription *)sdp
418 error:(NSError *)error {
419 dispatch_async(dispatch_get_main_queue(), ^{
420 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700421 RTCLogError(@"Failed to create session description. Error: %@", error);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000422 [self disconnect];
423 NSDictionary *userInfo = @{
424 NSLocalizedDescriptionKey: @"Failed to create session description.",
425 };
426 NSError *sdpError =
427 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
428 code:kARDAppClientErrorCreateSDP
429 userInfo:userInfo];
430 [_delegate appClient:self didError:sdpError];
431 return;
432 }
Zeke Chin71f6f442015-06-29 14:34:58 -0700433 // Prefer H264 if available.
434 RTCSessionDescription *sdpPreferringH264 =
435 [ARDSDPUtils descriptionForDescription:sdp
436 preferredVideoCodec:@"H264"];
hjon79858f82016-03-13 22:08:26 -0700437 __weak ARDAppClient *weakSelf = self;
438 [_peerConnection setLocalDescription:sdpPreferringH264
439 completionHandler:^(NSError *error) {
440 ARDAppClient *strongSelf = weakSelf;
441 [strongSelf peerConnection:strongSelf.peerConnection
442 didSetSessionDescriptionWithError:error];
443 }];
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];
hjon79858f82016-03-13 22:08:26 -0700471 __weak ARDAppClient *weakSelf = self;
472 [_peerConnection answerForConstraints:constraints
473 completionHandler:^(RTCSessionDescription *sdp,
474 NSError *error) {
475 ARDAppClient *strongSelf = weakSelf;
476 [strongSelf peerConnection:strongSelf.peerConnection
477 didCreateSessionDescription:sdp
478 error:error];
479 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000480 }
481 });
482}
483
484#pragma mark - Private
485
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000486- (BOOL)hasJoinedRoomServerRoom {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000487 return _clientId.length;
488}
489
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000490// Begins the peer connection connection process if we have both joined a room
491// on the room server and tried to obtain a TURN server. Otherwise does nothing.
492// A peer connection object will be created with a stream that contains local
493// audio and video capture. If this client is the caller, an offer is created as
494// well, otherwise the client will wait for an offer to arrive.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000495- (void)startSignalingIfReady {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000496 if (!_isTurnComplete || !self.hasJoinedRoomServerRoom) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000497 return;
498 }
499 self.state = kARDAppClientStateConnected;
500
501 // Create peer connection.
502 RTCMediaConstraints *constraints = [self defaultPeerConnectionConstraints];
Zeke Chinbc7dd7e2015-05-29 14:59:14 -0700503 RTCConfiguration *config = [[RTCConfiguration alloc] init];
504 config.iceServers = _iceServers;
Tze Kwang Chinf3cb49f2016-03-22 10:57:40 -0700505 _peerConnection = [_factory peerConnectionWithConfiguration:config
506 constraints:constraints
507 delegate:self];
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000508 // Create AV media stream and add it to the peer connection.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000509 RTCMediaStream *localStream = [self createLocalMediaStream];
510 [_peerConnection addStream:localStream];
511 if (_isInitiator) {
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000512 // Send offer.
hjon79858f82016-03-13 22:08:26 -0700513 __weak ARDAppClient *weakSelf = self;
514 [_peerConnection offerForConstraints:[self defaultOfferConstraints]
515 completionHandler:^(RTCSessionDescription *sdp,
516 NSError *error) {
517 ARDAppClient *strongSelf = weakSelf;
518 [strongSelf peerConnection:strongSelf.peerConnection
519 didCreateSessionDescription:sdp
520 error:error];
521 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000522 } else {
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000523 // Check if we've received an offer.
524 [self drainMessageQueueIfReady];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000525 }
526}
527
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000528// Processes the messages that we've received from the room server and the
529// signaling channel. The offer or answer message must be processed before other
530// signaling messages, however they can arrive out of order. Hence, this method
531// only processes pending messages if there is a peer connection object and
532// if we have received either an offer or answer.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000533- (void)drainMessageQueueIfReady {
534 if (!_peerConnection || !_hasReceivedSdp) {
535 return;
536 }
537 for (ARDSignalingMessage *message in _messageQueue) {
538 [self processSignalingMessage:message];
539 }
540 [_messageQueue removeAllObjects];
541}
542
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000543// Processes the given signaling message based on its type.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000544- (void)processSignalingMessage:(ARDSignalingMessage *)message {
545 NSParameterAssert(_peerConnection ||
546 message.type == kARDSignalingMessageTypeBye);
547 switch (message.type) {
548 case kARDSignalingMessageTypeOffer:
549 case kARDSignalingMessageTypeAnswer: {
550 ARDSessionDescriptionMessage *sdpMessage =
551 (ARDSessionDescriptionMessage *)message;
552 RTCSessionDescription *description = sdpMessage.sessionDescription;
Zeke Chin71f6f442015-06-29 14:34:58 -0700553 // Prefer H264 if available.
554 RTCSessionDescription *sdpPreferringH264 =
555 [ARDSDPUtils descriptionForDescription:description
556 preferredVideoCodec:@"H264"];
hjon79858f82016-03-13 22:08:26 -0700557 __weak ARDAppClient *weakSelf = self;
558 [_peerConnection setRemoteDescription:sdpPreferringH264
559 completionHandler:^(NSError *error) {
560 ARDAppClient *strongSelf = weakSelf;
561 [strongSelf peerConnection:strongSelf.peerConnection
562 didSetSessionDescriptionWithError:error];
563 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000564 break;
565 }
566 case kARDSignalingMessageTypeCandidate: {
567 ARDICECandidateMessage *candidateMessage =
568 (ARDICECandidateMessage *)message;
hjon79858f82016-03-13 22:08:26 -0700569 [_peerConnection addIceCandidate:candidateMessage.candidate];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000570 break;
571 }
572 case kARDSignalingMessageTypeBye:
573 // Other client disconnected.
574 // TODO(tkchin): support waiting in room for next client. For now just
575 // disconnect.
576 [self disconnect];
577 break;
578 }
579}
580
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000581// Sends a signaling message to the other client. The caller will send messages
582// through the room server, whereas the callee will send messages over the
583// signaling channel.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000584- (void)sendSignalingMessage:(ARDSignalingMessage *)message {
585 if (_isInitiator) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000586 __weak ARDAppClient *weakSelf = self;
587 [_roomServerClient sendMessage:message
588 forRoomId:_roomId
589 clientId:_clientId
590 completionHandler:^(ARDMessageResponse *response,
591 NSError *error) {
592 ARDAppClient *strongSelf = weakSelf;
593 if (error) {
594 [strongSelf.delegate appClient:strongSelf didError:error];
595 return;
596 }
597 NSError *messageError =
598 [[strongSelf class] errorForMessageResultType:response.result];
599 if (messageError) {
600 [strongSelf.delegate appClient:strongSelf didError:messageError];
601 return;
602 }
603 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000604 } else {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000605 [_channel sendMessage:message];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000606 }
607}
608
609- (RTCMediaStream *)createLocalMediaStream {
Tze Kwang Chinf3cb49f2016-03-22 10:57:40 -0700610 RTCMediaStream *localStream = [_factory mediaStreamWithStreamId:@"ARDAMS"];
hjon79858f82016-03-13 22:08:26 -0700611 RTCVideoTrack *localVideoTrack = [self createLocalVideoTrack];
Zeke Chin57cc74e2015-05-05 07:52:31 -0700612 if (localVideoTrack) {
613 [localStream addVideoTrack:localVideoTrack];
614 [_delegate appClient:self didReceiveLocalVideoTrack:localVideoTrack];
615 }
hjon79858f82016-03-13 22:08:26 -0700616 RTCAudioTrack *localAudioTrack =
Tze Kwang Chinf3cb49f2016-03-22 10:57:40 -0700617 [_factory audioTrackWithTrackId:@"ARDAMSa0"];
hjon79858f82016-03-13 22:08:26 -0700618 [localStream addAudioTrack:localAudioTrack];
Zeke Chin57cc74e2015-05-05 07:52:31 -0700619 return localStream;
620}
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000621
Zeke Chin57cc74e2015-05-05 07:52:31 -0700622- (RTCVideoTrack *)createLocalVideoTrack {
623 RTCVideoTrack* localVideoTrack = nil;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000624 // The iOS simulator doesn't provide any sort of camera capture
625 // support or emulation (http://goo.gl/rHAnC1) so don't bother
626 // trying to open a local stream.
627 // TODO(tkchin): local video capture for OSX. See
628 // https://code.google.com/p/webrtc/issues/detail?id=3417.
629#if !TARGET_IPHONE_SIMULATOR && TARGET_OS_IPHONE
haysc913e6452015-10-02 11:44:03 -0700630 if (!_isAudioOnly) {
631 RTCMediaConstraints *mediaConstraints =
632 [self defaultMediaStreamConstraints];
633 RTCAVFoundationVideoSource *source =
Tze Kwang Chinf3cb49f2016-03-22 10:57:40 -0700634 [_factory avFoundationVideoSourceWithConstraints:mediaConstraints];
haysc913e6452015-10-02 11:44:03 -0700635 localVideoTrack =
Tze Kwang Chinf3cb49f2016-03-22 10:57:40 -0700636 [_factory videoTrackWithSource:source
637 trackId:@"ARDAMSv0"];
haysc913e6452015-10-02 11:44:03 -0700638 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000639#endif
Zeke Chin57cc74e2015-05-05 07:52:31 -0700640 return localVideoTrack;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000641}
642
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000643#pragma mark - Collider methods
644
645- (void)registerWithColliderIfReady {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000646 if (!self.hasJoinedRoomServerRoom) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000647 return;
648 }
649 // Open WebSocket connection.
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000650 if (!_channel) {
651 _channel =
652 [[ARDWebSocketChannel alloc] initWithURL:_websocketURL
653 restURL:_websocketRestURL
654 delegate:self];
haysc913e6452015-10-02 11:44:03 -0700655 if (_isLoopback) {
656 _loopbackChannel =
657 [[ARDLoopbackWebSocketChannel alloc] initWithURL:_websocketURL
658 restURL:_websocketRestURL];
659 }
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000660 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000661 [_channel registerForRoomId:_roomId clientId:_clientId];
haysc913e6452015-10-02 11:44:03 -0700662 if (_isLoopback) {
663 [_loopbackChannel registerForRoomId:_roomId clientId:@"LOOPBACK_CLIENT_ID"];
664 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000665}
666
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000667#pragma mark - Defaults
668
669- (RTCMediaConstraints *)defaultMediaStreamConstraints {
670 RTCMediaConstraints* constraints =
671 [[RTCMediaConstraints alloc]
672 initWithMandatoryConstraints:nil
673 optionalConstraints:nil];
674 return constraints;
675}
676
677- (RTCMediaConstraints *)defaultAnswerConstraints {
678 return [self defaultOfferConstraints];
679}
680
681- (RTCMediaConstraints *)defaultOfferConstraints {
hjon79858f82016-03-13 22:08:26 -0700682 NSDictionary *mandatoryConstraints = @{
683 @"OfferToReceiveAudio" : @"true",
684 @"OfferToReceiveVideo" : @"true"
685 };
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000686 RTCMediaConstraints* constraints =
687 [[RTCMediaConstraints alloc]
688 initWithMandatoryConstraints:mandatoryConstraints
689 optionalConstraints:nil];
690 return constraints;
691}
692
693- (RTCMediaConstraints *)defaultPeerConnectionConstraints {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000694 if (_defaultPeerConnectionConstraints) {
695 return _defaultPeerConnectionConstraints;
696 }
haysc913e6452015-10-02 11:44:03 -0700697 NSString *value = _isLoopback ? @"false" : @"true";
hjon79858f82016-03-13 22:08:26 -0700698 NSDictionary *optionalConstraints = @{ @"DtlsSrtpKeyAgreement" : value };
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000699 RTCMediaConstraints* constraints =
700 [[RTCMediaConstraints alloc]
701 initWithMandatoryConstraints:nil
702 optionalConstraints:optionalConstraints];
703 return constraints;
704}
705
hjon79858f82016-03-13 22:08:26 -0700706- (RTCIceServer *)defaultSTUNServer {
707 return [[RTCIceServer alloc] initWithURLStrings:@[kARDDefaultSTUNServerUrl]
708 username:@""
709 credential:@""];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000710}
711
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000712#pragma mark - Errors
713
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000714+ (NSError *)errorForJoinResultType:(ARDJoinResultType)resultType {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000715 NSError *error = nil;
716 switch (resultType) {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000717 case kARDJoinResultTypeSuccess:
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000718 break;
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000719 case kARDJoinResultTypeUnknown: {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000720 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
721 code:kARDAppClientErrorUnknown
722 userInfo:@{
723 NSLocalizedDescriptionKey: @"Unknown error.",
724 }];
725 break;
726 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000727 case kARDJoinResultTypeFull: {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000728 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
729 code:kARDAppClientErrorRoomFull
730 userInfo:@{
731 NSLocalizedDescriptionKey: @"Room is full.",
732 }];
733 break;
734 }
735 }
736 return error;
737}
738
739+ (NSError *)errorForMessageResultType:(ARDMessageResultType)resultType {
740 NSError *error = nil;
741 switch (resultType) {
742 case kARDMessageResultTypeSuccess:
743 break;
744 case kARDMessageResultTypeUnknown:
745 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
746 code:kARDAppClientErrorUnknown
747 userInfo:@{
748 NSLocalizedDescriptionKey: @"Unknown error.",
749 }];
750 break;
751 case kARDMessageResultTypeInvalidClient:
752 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
753 code:kARDAppClientErrorInvalidClient
754 userInfo:@{
755 NSLocalizedDescriptionKey: @"Invalid client.",
756 }];
757 break;
758 case kARDMessageResultTypeInvalidRoom:
759 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
760 code:kARDAppClientErrorInvalidRoom
761 userInfo:@{
762 NSLocalizedDescriptionKey: @"Invalid room.",
763 }];
764 break;
765 }
766 return error;
767}
768
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000769@end