blob: 8fb1841e391a16eb814235c21abd7a4dcc781eda [file] [log] [blame]
tkchin@webrtc.org87776a82014-12-09 19:32:35 +00001/*
Donald E Curtisa8736442015-08-05 15:48:13 -07002 * Copyright 2014 The WebRTC Project Authors. All rights reserved.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +00003 *
Donald E Curtisa8736442015-08-05 15:48:13 -07004 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +00009 */
10
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000011#import "ARDAppClient+Internal.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000012
Zeke Chin57cc74e2015-05-05 07:52:31 -070013#if defined(WEBRTC_IOS)
tkchin9eeb6242016-04-27 01:54:20 -070014#import "WebRTC/RTCAVFoundationVideoSource.h"
15#import "WebRTC/RTCTracing.h"
Zeke Chin57cc74e2015-05-05 07:52:31 -070016#endif
tkchin9eeb6242016-04-27 01:54:20 -070017#import "WebRTC/RTCAudioTrack.h"
18#import "WebRTC/RTCConfiguration.h"
19#import "WebRTC/RTCFileLogger.h"
20#import "WebRTC/RTCIceServer.h"
21#import "WebRTC/RTCLogging.h"
22#import "WebRTC/RTCMediaConstraints.h"
23#import "WebRTC/RTCMediaStream.h"
24#import "WebRTC/RTCPeerConnectionFactory.h"
skvladf3569c82016-04-29 15:30:16 -070025#import "WebRTC/RTCRtpSender.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000026
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000027#import "ARDAppEngineClient.h"
28#import "ARDCEODTURNClient.h"
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +000029#import "ARDJoinResponse.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000030#import "ARDMessageResponse.h"
Zeke Chin71f6f442015-06-29 14:34:58 -070031#import "ARDSDPUtils.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000032#import "ARDSignalingMessage.h"
33#import "ARDUtilities.h"
34#import "ARDWebSocketChannel.h"
hjon79858f82016-03-13 22:08:26 -070035#import "RTCIceCandidate+JSON.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000036#import "RTCSessionDescription+JSON.h"
Zeke Chin57cc74e2015-05-05 07:52:31 -070037
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +000038static NSString * const kARDDefaultSTUNServerUrl =
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000039 @"stun:stun.l.google.com:19302";
40// TODO(tkchin): figure out a better username for CEOD statistics.
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +000041static NSString * const kARDTurnRequestUrl =
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000042 @"https://computeengineondemand.appspot.com"
43 @"/turn?username=iapprtc&key=4080218913";
44
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +000045static NSString * const kARDAppClientErrorDomain = @"ARDAppClient";
46static NSInteger const kARDAppClientErrorUnknown = -1;
47static NSInteger const kARDAppClientErrorRoomFull = -2;
48static NSInteger const kARDAppClientErrorCreateSDP = -3;
49static NSInteger const kARDAppClientErrorSetSDP = -4;
50static NSInteger const kARDAppClientErrorInvalidClient = -5;
51static NSInteger const kARDAppClientErrorInvalidRoom = -6;
skvladf3569c82016-04-29 15:30:16 -070052static NSString * const kARDMediaStreamId = @"ARDAMS";
53static NSString * const kARDAudioTrackId = @"ARDAMSa0";
54static NSString * const kARDVideoTrackId = @"ARDAMSv0";
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000055
tkchin9eeb6242016-04-27 01:54:20 -070056// TODO(tkchin): Remove guard once rtc_sdk_common_objc compiles on Mac.
tkchind1fb26d2016-02-03 01:51:18 -080057#if defined(WEBRTC_IOS)
58// TODO(tkchin): Add this as a UI option.
59static BOOL const kARDAppClientEnableTracing = NO;
60#endif
61
Zeke Chind3325802015-08-14 11:00:02 -070062// We need a proxy to NSTimer because it causes a strong retain cycle. When
63// using the proxy, |invalidate| must be called before it properly deallocs.
64@interface ARDTimerProxy : NSObject
65
66- (instancetype)initWithInterval:(NSTimeInterval)interval
67 repeats:(BOOL)repeats
68 timerHandler:(void (^)(void))timerHandler;
69- (void)invalidate;
70
71@end
72
73@implementation ARDTimerProxy {
74 NSTimer *_timer;
75 void (^_timerHandler)(void);
Zeke Chin2d3b7e22015-07-14 12:55:44 -070076}
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000077
Zeke Chind3325802015-08-14 11:00:02 -070078- (instancetype)initWithInterval:(NSTimeInterval)interval
79 repeats:(BOOL)repeats
80 timerHandler:(void (^)(void))timerHandler {
81 NSParameterAssert(timerHandler);
82 if (self = [super init]) {
83 _timerHandler = timerHandler;
84 _timer = [NSTimer scheduledTimerWithTimeInterval:interval
85 target:self
86 selector:@selector(timerDidFire:)
87 userInfo:nil
88 repeats:repeats];
89 }
90 return self;
91}
92
93- (void)invalidate {
94 [_timer invalidate];
95}
96
97- (void)timerDidFire:(NSTimer *)timer {
98 _timerHandler();
99}
100
101@end
102
103@implementation ARDAppClient {
104 RTCFileLogger *_fileLogger;
105 ARDTimerProxy *_statsTimer;
106}
107
108@synthesize shouldGetStats = _shouldGetStats;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000109@synthesize state = _state;
Zeke Chind3325802015-08-14 11:00:02 -0700110@synthesize delegate = _delegate;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000111@synthesize roomServerClient = _roomServerClient;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000112@synthesize channel = _channel;
haysc913e6452015-10-02 11:44:03 -0700113@synthesize loopbackChannel = _loopbackChannel;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000114@synthesize turnClient = _turnClient;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000115@synthesize peerConnection = _peerConnection;
116@synthesize factory = _factory;
117@synthesize messageQueue = _messageQueue;
118@synthesize isTurnComplete = _isTurnComplete;
119@synthesize hasReceivedSdp = _hasReceivedSdp;
120@synthesize roomId = _roomId;
121@synthesize clientId = _clientId;
122@synthesize isInitiator = _isInitiator;
123@synthesize iceServers = _iceServers;
124@synthesize webSocketURL = _websocketURL;
125@synthesize webSocketRestURL = _websocketRestURL;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000126@synthesize defaultPeerConnectionConstraints =
127 _defaultPeerConnectionConstraints;
haysc913e6452015-10-02 11:44:03 -0700128@synthesize isLoopback = _isLoopback;
129@synthesize isAudioOnly = _isAudioOnly;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000130
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000131- (instancetype)init {
132 if (self = [super init]) {
133 _roomServerClient = [[ARDAppEngineClient alloc] init];
134 NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl];
135 _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL];
136 [self configure];
137 }
138 return self;
139}
140
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000141- (instancetype)initWithDelegate:(id<ARDAppClientDelegate>)delegate {
142 if (self = [super init]) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000143 _roomServerClient = [[ARDAppEngineClient alloc] init];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000144 _delegate = delegate;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000145 NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl];
146 _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL];
147 [self configure];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000148 }
149 return self;
150}
151
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000152// TODO(tkchin): Provide signaling channel factory interface so we can recreate
153// channel if we need to on network failure. Also, make this the default public
154// constructor.
155- (instancetype)initWithRoomServerClient:(id<ARDRoomServerClient>)rsClient
156 signalingChannel:(id<ARDSignalingChannel>)channel
157 turnClient:(id<ARDTURNClient>)turnClient
158 delegate:(id<ARDAppClientDelegate>)delegate {
159 NSParameterAssert(rsClient);
160 NSParameterAssert(channel);
161 NSParameterAssert(turnClient);
162 if (self = [super init]) {
163 _roomServerClient = rsClient;
164 _channel = channel;
165 _turnClient = turnClient;
166 _delegate = delegate;
167 [self configure];
168 }
169 return self;
170}
171
172- (void)configure {
173 _factory = [[RTCPeerConnectionFactory alloc] init];
174 _messageQueue = [NSMutableArray array];
175 _iceServers = [NSMutableArray arrayWithObject:[self defaultSTUNServer]];
Zeke Chin2d3b7e22015-07-14 12:55:44 -0700176 _fileLogger = [[RTCFileLogger alloc] init];
177 [_fileLogger start];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000178}
179
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000180- (void)dealloc {
Zeke Chind3325802015-08-14 11:00:02 -0700181 self.shouldGetStats = NO;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000182 [self disconnect];
183}
184
Zeke Chind3325802015-08-14 11:00:02 -0700185- (void)setShouldGetStats:(BOOL)shouldGetStats {
186 if (_shouldGetStats == shouldGetStats) {
187 return;
188 }
189 if (shouldGetStats) {
190 __weak ARDAppClient *weakSelf = self;
191 _statsTimer = [[ARDTimerProxy alloc] initWithInterval:1
192 repeats:YES
193 timerHandler:^{
194 ARDAppClient *strongSelf = weakSelf;
hjon79858f82016-03-13 22:08:26 -0700195 [strongSelf.peerConnection statsForTrack:nil
196 statsOutputLevel:RTCStatsOutputLevelDebug
197 completionHandler:^(NSArray *stats) {
198 dispatch_async(dispatch_get_main_queue(), ^{
199 ARDAppClient *strongSelf = weakSelf;
200 [strongSelf.delegate appClient:strongSelf didGetStats:stats];
201 });
202 }];
Zeke Chind3325802015-08-14 11:00:02 -0700203 }];
204 } else {
205 [_statsTimer invalidate];
206 _statsTimer = nil;
207 }
208 _shouldGetStats = shouldGetStats;
209}
210
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000211- (void)setState:(ARDAppClientState)state {
212 if (_state == state) {
213 return;
214 }
215 _state = state;
216 [_delegate appClient:self didChangeState:_state];
217}
218
219- (void)connectToRoomWithId:(NSString *)roomId
haysc913e6452015-10-02 11:44:03 -0700220 isLoopback:(BOOL)isLoopback
221 isAudioOnly:(BOOL)isAudioOnly {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000222 NSParameterAssert(roomId.length);
223 NSParameterAssert(_state == kARDAppClientStateDisconnected);
haysc913e6452015-10-02 11:44:03 -0700224 _isLoopback = isLoopback;
225 _isAudioOnly = isAudioOnly;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000226 self.state = kARDAppClientStateConnecting;
227
tkchind1fb26d2016-02-03 01:51:18 -0800228#if defined(WEBRTC_IOS)
229 if (kARDAppClientEnableTracing) {
230 NSArray *paths = NSSearchPathForDirectoriesInDomains(
231 NSDocumentDirectory, NSUserDomainMask, YES);
232 NSString *documentsDirPath = paths.firstObject;
233 NSString *filePath =
234 [documentsDirPath stringByAppendingPathComponent:@"webrtc-trace.txt"];
235 RTCStartInternalCapture(filePath);
236 }
237#endif
238
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000239 // Request TURN.
240 __weak ARDAppClient *weakSelf = self;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000241 [_turnClient requestServersWithCompletionHandler:^(NSArray *turnServers,
242 NSError *error) {
243 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700244 RTCLogError("Error retrieving TURN servers: %@",
245 error.localizedDescription);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000246 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000247 ARDAppClient *strongSelf = weakSelf;
248 [strongSelf.iceServers addObjectsFromArray:turnServers];
249 strongSelf.isTurnComplete = YES;
250 [strongSelf startSignalingIfReady];
251 }];
jiayl@webrtc.org27f53172014-12-31 00:26:20 +0000252
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000253 // Join room on room server.
254 [_roomServerClient joinRoomWithRoomId:roomId
haysc913e6452015-10-02 11:44:03 -0700255 isLoopback:isLoopback
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000256 completionHandler:^(ARDJoinResponse *response, NSError *error) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000257 ARDAppClient *strongSelf = weakSelf;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000258 if (error) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000259 [strongSelf.delegate appClient:strongSelf didError:error];
260 return;
261 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000262 NSError *joinError =
263 [[strongSelf class] errorForJoinResultType:response.result];
264 if (joinError) {
tkchinc3f46a92015-07-23 12:50:55 -0700265 RTCLogError(@"Failed to join room:%@ on room server.", roomId);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000266 [strongSelf disconnect];
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000267 [strongSelf.delegate appClient:strongSelf didError:joinError];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000268 return;
269 }
tkchinc3f46a92015-07-23 12:50:55 -0700270 RTCLog(@"Joined room:%@ on room server.", roomId);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000271 strongSelf.roomId = response.roomId;
272 strongSelf.clientId = response.clientId;
273 strongSelf.isInitiator = response.isInitiator;
274 for (ARDSignalingMessage *message in response.messages) {
275 if (message.type == kARDSignalingMessageTypeOffer ||
276 message.type == kARDSignalingMessageTypeAnswer) {
277 strongSelf.hasReceivedSdp = YES;
278 [strongSelf.messageQueue insertObject:message atIndex:0];
279 } else {
280 [strongSelf.messageQueue addObject:message];
281 }
282 }
283 strongSelf.webSocketURL = response.webSocketURL;
284 strongSelf.webSocketRestURL = response.webSocketRestURL;
285 [strongSelf registerWithColliderIfReady];
286 [strongSelf startSignalingIfReady];
287 }];
288}
289
290- (void)disconnect {
291 if (_state == kARDAppClientStateDisconnected) {
292 return;
293 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000294 if (self.hasJoinedRoomServerRoom) {
295 [_roomServerClient leaveRoomWithRoomId:_roomId
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000296 clientId:_clientId
297 completionHandler:nil];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000298 }
299 if (_channel) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000300 if (_channel.state == kARDSignalingChannelStateRegistered) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000301 // Tell the other client we're hanging up.
302 ARDByeMessage *byeMessage = [[ARDByeMessage alloc] init];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000303 [_channel sendMessage:byeMessage];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000304 }
305 // Disconnect from collider.
306 _channel = nil;
307 }
308 _clientId = nil;
309 _roomId = nil;
310 _isInitiator = NO;
311 _hasReceivedSdp = NO;
312 _messageQueue = [NSMutableArray array];
313 _peerConnection = nil;
314 self.state = kARDAppClientStateDisconnected;
tkchind1fb26d2016-02-03 01:51:18 -0800315#if defined(WEBRTC_IOS)
316 RTCStopInternalCapture();
317#endif
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000318}
319
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000320#pragma mark - ARDSignalingChannelDelegate
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000321
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000322- (void)channel:(id<ARDSignalingChannel>)channel
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000323 didReceiveMessage:(ARDSignalingMessage *)message {
324 switch (message.type) {
325 case kARDSignalingMessageTypeOffer:
326 case kARDSignalingMessageTypeAnswer:
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000327 // Offers and answers must be processed before any other message, so we
328 // place them at the front of the queue.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000329 _hasReceivedSdp = YES;
330 [_messageQueue insertObject:message atIndex:0];
331 break;
332 case kARDSignalingMessageTypeCandidate:
Honghai Zhangda2ba4d2016-05-23 11:53:14 -0700333 case kARDSignalingMessageTypeCandidateRemoval:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000334 [_messageQueue addObject:message];
335 break;
336 case kARDSignalingMessageTypeBye:
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000337 // Disconnects can be processed immediately.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000338 [self processSignalingMessage:message];
339 return;
340 }
341 [self drainMessageQueueIfReady];
342}
343
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000344- (void)channel:(id<ARDSignalingChannel>)channel
345 didChangeState:(ARDSignalingChannelState)state {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000346 switch (state) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000347 case kARDSignalingChannelStateOpen:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000348 break;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000349 case kARDSignalingChannelStateRegistered:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000350 break;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000351 case kARDSignalingChannelStateClosed:
352 case kARDSignalingChannelStateError:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000353 // TODO(tkchin): reconnection scenarios. Right now we just disconnect
354 // completely if the websocket connection fails.
355 [self disconnect];
356 break;
357 }
358}
359
360#pragma mark - RTCPeerConnectionDelegate
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000361// Callbacks for this delegate occur on non-main thread and need to be
362// dispatched back to main queue as needed.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000363
364- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700365 didChangeSignalingState:(RTCSignalingState)stateChanged {
366 RTCLog(@"Signaling state changed: %ld", (long)stateChanged);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000367}
368
369- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700370 didAddStream:(RTCMediaStream *)stream {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000371 dispatch_async(dispatch_get_main_queue(), ^{
tkchinc3f46a92015-07-23 12:50:55 -0700372 RTCLog(@"Received %lu video tracks and %lu audio tracks",
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000373 (unsigned long)stream.videoTracks.count,
374 (unsigned long)stream.audioTracks.count);
375 if (stream.videoTracks.count) {
376 RTCVideoTrack *videoTrack = stream.videoTracks[0];
377 [_delegate appClient:self didReceiveRemoteVideoTrack:videoTrack];
378 }
379 });
380}
381
382- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700383 didRemoveStream:(RTCMediaStream *)stream {
tkchinc3f46a92015-07-23 12:50:55 -0700384 RTCLog(@"Stream was removed.");
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000385}
386
hjon79858f82016-03-13 22:08:26 -0700387- (void)peerConnectionShouldNegotiate:(RTCPeerConnection *)peerConnection {
tkchinc3f46a92015-07-23 12:50:55 -0700388 RTCLog(@"WARNING: Renegotiation needed but unimplemented.");
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000389}
390
391- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700392 didChangeIceConnectionState:(RTCIceConnectionState)newState {
393 RTCLog(@"ICE state changed: %ld", (long)newState);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000394 dispatch_async(dispatch_get_main_queue(), ^{
395 [_delegate appClient:self didChangeConnectionState:newState];
396 });
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000397}
398
399- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700400 didChangeIceGatheringState:(RTCIceGatheringState)newState {
401 RTCLog(@"ICE gathering state changed: %ld", (long)newState);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000402}
403
404- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700405 didGenerateIceCandidate:(RTCIceCandidate *)candidate {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000406 dispatch_async(dispatch_get_main_queue(), ^{
407 ARDICECandidateMessage *message =
408 [[ARDICECandidateMessage alloc] initWithCandidate:candidate];
409 [self sendSignalingMessage:message];
410 });
411}
412
Zeke Chind3325802015-08-14 11:00:02 -0700413- (void)peerConnection:(RTCPeerConnection *)peerConnection
Honghai Zhangda2ba4d2016-05-23 11:53:14 -0700414 didRemoveIceCandidates:(NSArray<RTCIceCandidate *> *)candidates {
415 dispatch_async(dispatch_get_main_queue(), ^{
416 ARDICECandidateRemovalMessage *message =
417 [[ARDICECandidateRemovalMessage alloc]
418 initWithRemovedCandidates:candidates];
419 [self sendSignalingMessage:message];
420 });
421}
422
423- (void)peerConnection:(RTCPeerConnection *)peerConnection
Zeke Chind3325802015-08-14 11:00:02 -0700424 didOpenDataChannel:(RTCDataChannel *)dataChannel {
425}
426
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000427#pragma mark - RTCSessionDescriptionDelegate
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000428// Callbacks for this delegate occur on non-main thread and need to be
429// dispatched back to main queue as needed.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000430
431- (void)peerConnection:(RTCPeerConnection *)peerConnection
432 didCreateSessionDescription:(RTCSessionDescription *)sdp
433 error:(NSError *)error {
434 dispatch_async(dispatch_get_main_queue(), ^{
435 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700436 RTCLogError(@"Failed to create session description. Error: %@", error);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000437 [self disconnect];
438 NSDictionary *userInfo = @{
439 NSLocalizedDescriptionKey: @"Failed to create session description.",
440 };
441 NSError *sdpError =
442 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
443 code:kARDAppClientErrorCreateSDP
444 userInfo:userInfo];
445 [_delegate appClient:self didError:sdpError];
446 return;
447 }
Zeke Chin71f6f442015-06-29 14:34:58 -0700448 // Prefer H264 if available.
449 RTCSessionDescription *sdpPreferringH264 =
450 [ARDSDPUtils descriptionForDescription:sdp
451 preferredVideoCodec:@"H264"];
hjon79858f82016-03-13 22:08:26 -0700452 __weak ARDAppClient *weakSelf = self;
453 [_peerConnection setLocalDescription:sdpPreferringH264
454 completionHandler:^(NSError *error) {
455 ARDAppClient *strongSelf = weakSelf;
456 [strongSelf peerConnection:strongSelf.peerConnection
457 didSetSessionDescriptionWithError:error];
458 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000459 ARDSessionDescriptionMessage *message =
Zeke Chin71f6f442015-06-29 14:34:58 -0700460 [[ARDSessionDescriptionMessage alloc]
461 initWithDescription:sdpPreferringH264];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000462 [self sendSignalingMessage:message];
463 });
464}
465
466- (void)peerConnection:(RTCPeerConnection *)peerConnection
467 didSetSessionDescriptionWithError:(NSError *)error {
468 dispatch_async(dispatch_get_main_queue(), ^{
469 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700470 RTCLogError(@"Failed to set session description. Error: %@", error);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000471 [self disconnect];
472 NSDictionary *userInfo = @{
473 NSLocalizedDescriptionKey: @"Failed to set session description.",
474 };
475 NSError *sdpError =
476 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
477 code:kARDAppClientErrorSetSDP
478 userInfo:userInfo];
479 [_delegate appClient:self didError:sdpError];
480 return;
481 }
482 // If we're answering and we've just set the remote offer we need to create
483 // an answer and set the local description.
484 if (!_isInitiator && !_peerConnection.localDescription) {
485 RTCMediaConstraints *constraints = [self defaultAnswerConstraints];
hjon79858f82016-03-13 22:08:26 -0700486 __weak ARDAppClient *weakSelf = self;
487 [_peerConnection answerForConstraints:constraints
488 completionHandler:^(RTCSessionDescription *sdp,
489 NSError *error) {
490 ARDAppClient *strongSelf = weakSelf;
491 [strongSelf peerConnection:strongSelf.peerConnection
492 didCreateSessionDescription:sdp
493 error:error];
494 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000495 }
496 });
497}
498
499#pragma mark - Private
500
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000501- (BOOL)hasJoinedRoomServerRoom {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000502 return _clientId.length;
503}
504
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000505// Begins the peer connection connection process if we have both joined a room
506// on the room server and tried to obtain a TURN server. Otherwise does nothing.
507// A peer connection object will be created with a stream that contains local
508// audio and video capture. If this client is the caller, an offer is created as
509// well, otherwise the client will wait for an offer to arrive.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000510- (void)startSignalingIfReady {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000511 if (!_isTurnComplete || !self.hasJoinedRoomServerRoom) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000512 return;
513 }
514 self.state = kARDAppClientStateConnected;
515
516 // Create peer connection.
517 RTCMediaConstraints *constraints = [self defaultPeerConnectionConstraints];
Zeke Chinbc7dd7e2015-05-29 14:59:14 -0700518 RTCConfiguration *config = [[RTCConfiguration alloc] init];
519 config.iceServers = _iceServers;
Tze Kwang Chinf3cb49f2016-03-22 10:57:40 -0700520 _peerConnection = [_factory peerConnectionWithConfiguration:config
521 constraints:constraints
522 delegate:self];
skvladf3569c82016-04-29 15:30:16 -0700523 // Create AV senders.
524 [self createAudioSender];
525 [self createVideoSender];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000526 if (_isInitiator) {
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000527 // Send offer.
hjon79858f82016-03-13 22:08:26 -0700528 __weak ARDAppClient *weakSelf = self;
529 [_peerConnection offerForConstraints:[self defaultOfferConstraints]
530 completionHandler:^(RTCSessionDescription *sdp,
531 NSError *error) {
532 ARDAppClient *strongSelf = weakSelf;
533 [strongSelf peerConnection:strongSelf.peerConnection
534 didCreateSessionDescription:sdp
535 error:error];
536 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000537 } else {
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000538 // Check if we've received an offer.
539 [self drainMessageQueueIfReady];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000540 }
541}
542
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000543// Processes the messages that we've received from the room server and the
544// signaling channel. The offer or answer message must be processed before other
545// signaling messages, however they can arrive out of order. Hence, this method
546// only processes pending messages if there is a peer connection object and
547// if we have received either an offer or answer.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000548- (void)drainMessageQueueIfReady {
549 if (!_peerConnection || !_hasReceivedSdp) {
550 return;
551 }
552 for (ARDSignalingMessage *message in _messageQueue) {
553 [self processSignalingMessage:message];
554 }
555 [_messageQueue removeAllObjects];
556}
557
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000558// Processes the given signaling message based on its type.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000559- (void)processSignalingMessage:(ARDSignalingMessage *)message {
560 NSParameterAssert(_peerConnection ||
561 message.type == kARDSignalingMessageTypeBye);
562 switch (message.type) {
563 case kARDSignalingMessageTypeOffer:
564 case kARDSignalingMessageTypeAnswer: {
565 ARDSessionDescriptionMessage *sdpMessage =
566 (ARDSessionDescriptionMessage *)message;
567 RTCSessionDescription *description = sdpMessage.sessionDescription;
Zeke Chin71f6f442015-06-29 14:34:58 -0700568 // Prefer H264 if available.
569 RTCSessionDescription *sdpPreferringH264 =
570 [ARDSDPUtils descriptionForDescription:description
571 preferredVideoCodec:@"H264"];
hjon79858f82016-03-13 22:08:26 -0700572 __weak ARDAppClient *weakSelf = self;
573 [_peerConnection setRemoteDescription:sdpPreferringH264
574 completionHandler:^(NSError *error) {
575 ARDAppClient *strongSelf = weakSelf;
576 [strongSelf peerConnection:strongSelf.peerConnection
577 didSetSessionDescriptionWithError:error];
578 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000579 break;
580 }
581 case kARDSignalingMessageTypeCandidate: {
582 ARDICECandidateMessage *candidateMessage =
583 (ARDICECandidateMessage *)message;
hjon79858f82016-03-13 22:08:26 -0700584 [_peerConnection addIceCandidate:candidateMessage.candidate];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000585 break;
586 }
Honghai Zhangda2ba4d2016-05-23 11:53:14 -0700587 case kARDSignalingMessageTypeCandidateRemoval: {
588 ARDICECandidateRemovalMessage *candidateMessage =
589 (ARDICECandidateRemovalMessage *)message;
590 [_peerConnection removeIceCandidates:candidateMessage.candidates];
591 break;
592 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000593 case kARDSignalingMessageTypeBye:
594 // Other client disconnected.
595 // TODO(tkchin): support waiting in room for next client. For now just
596 // disconnect.
597 [self disconnect];
598 break;
599 }
600}
601
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000602// Sends a signaling message to the other client. The caller will send messages
603// through the room server, whereas the callee will send messages over the
604// signaling channel.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000605- (void)sendSignalingMessage:(ARDSignalingMessage *)message {
606 if (_isInitiator) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000607 __weak ARDAppClient *weakSelf = self;
608 [_roomServerClient sendMessage:message
609 forRoomId:_roomId
610 clientId:_clientId
611 completionHandler:^(ARDMessageResponse *response,
612 NSError *error) {
613 ARDAppClient *strongSelf = weakSelf;
614 if (error) {
615 [strongSelf.delegate appClient:strongSelf didError:error];
616 return;
617 }
618 NSError *messageError =
619 [[strongSelf class] errorForMessageResultType:response.result];
620 if (messageError) {
621 [strongSelf.delegate appClient:strongSelf didError:messageError];
622 return;
623 }
624 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000625 } else {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000626 [_channel sendMessage:message];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000627 }
628}
629
skvladf3569c82016-04-29 15:30:16 -0700630- (RTCRtpSender *)createVideoSender {
631 RTCRtpSender *sender =
632 [_peerConnection senderWithKind:kRTCMediaStreamTrackKindVideo
633 streamId:kARDMediaStreamId];
634 RTCVideoTrack *track = [self createLocalVideoTrack];
635 if (track) {
636 sender.track = track;
637 [_delegate appClient:self didReceiveLocalVideoTrack:track];
Zeke Chin57cc74e2015-05-05 07:52:31 -0700638 }
skvladf3569c82016-04-29 15:30:16 -0700639 return sender;
640}
641
642- (RTCRtpSender *)createAudioSender {
643 RTCRtpSender *sender =
644 [_peerConnection senderWithKind:kRTCMediaStreamTrackKindAudio
645 streamId:kARDMediaStreamId];
646 RTCAudioTrack *track = [_factory audioTrackWithTrackId:kARDAudioTrackId];
647 sender.track = track;
648 return sender;
Zeke Chin57cc74e2015-05-05 07:52:31 -0700649}
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000650
Zeke Chin57cc74e2015-05-05 07:52:31 -0700651- (RTCVideoTrack *)createLocalVideoTrack {
652 RTCVideoTrack* localVideoTrack = nil;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000653 // The iOS simulator doesn't provide any sort of camera capture
654 // support or emulation (http://goo.gl/rHAnC1) so don't bother
655 // trying to open a local stream.
656 // TODO(tkchin): local video capture for OSX. See
657 // https://code.google.com/p/webrtc/issues/detail?id=3417.
658#if !TARGET_IPHONE_SIMULATOR && TARGET_OS_IPHONE
haysc913e6452015-10-02 11:44:03 -0700659 if (!_isAudioOnly) {
660 RTCMediaConstraints *mediaConstraints =
661 [self defaultMediaStreamConstraints];
662 RTCAVFoundationVideoSource *source =
Tze Kwang Chinf3cb49f2016-03-22 10:57:40 -0700663 [_factory avFoundationVideoSourceWithConstraints:mediaConstraints];
haysc913e6452015-10-02 11:44:03 -0700664 localVideoTrack =
Tze Kwang Chinf3cb49f2016-03-22 10:57:40 -0700665 [_factory videoTrackWithSource:source
skvladf3569c82016-04-29 15:30:16 -0700666 trackId:kARDVideoTrackId];
haysc913e6452015-10-02 11:44:03 -0700667 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000668#endif
Zeke Chin57cc74e2015-05-05 07:52:31 -0700669 return localVideoTrack;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000670}
671
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000672#pragma mark - Collider methods
673
674- (void)registerWithColliderIfReady {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000675 if (!self.hasJoinedRoomServerRoom) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000676 return;
677 }
678 // Open WebSocket connection.
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000679 if (!_channel) {
680 _channel =
681 [[ARDWebSocketChannel alloc] initWithURL:_websocketURL
682 restURL:_websocketRestURL
683 delegate:self];
haysc913e6452015-10-02 11:44:03 -0700684 if (_isLoopback) {
685 _loopbackChannel =
686 [[ARDLoopbackWebSocketChannel alloc] initWithURL:_websocketURL
687 restURL:_websocketRestURL];
688 }
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000689 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000690 [_channel registerForRoomId:_roomId clientId:_clientId];
haysc913e6452015-10-02 11:44:03 -0700691 if (_isLoopback) {
692 [_loopbackChannel registerForRoomId:_roomId clientId:@"LOOPBACK_CLIENT_ID"];
693 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000694}
695
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000696#pragma mark - Defaults
697
698- (RTCMediaConstraints *)defaultMediaStreamConstraints {
699 RTCMediaConstraints* constraints =
700 [[RTCMediaConstraints alloc]
701 initWithMandatoryConstraints:nil
702 optionalConstraints:nil];
703 return constraints;
704}
705
706- (RTCMediaConstraints *)defaultAnswerConstraints {
707 return [self defaultOfferConstraints];
708}
709
710- (RTCMediaConstraints *)defaultOfferConstraints {
hjon79858f82016-03-13 22:08:26 -0700711 NSDictionary *mandatoryConstraints = @{
712 @"OfferToReceiveAudio" : @"true",
713 @"OfferToReceiveVideo" : @"true"
714 };
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000715 RTCMediaConstraints* constraints =
716 [[RTCMediaConstraints alloc]
717 initWithMandatoryConstraints:mandatoryConstraints
718 optionalConstraints:nil];
719 return constraints;
720}
721
722- (RTCMediaConstraints *)defaultPeerConnectionConstraints {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000723 if (_defaultPeerConnectionConstraints) {
724 return _defaultPeerConnectionConstraints;
725 }
haysc913e6452015-10-02 11:44:03 -0700726 NSString *value = _isLoopback ? @"false" : @"true";
hjon79858f82016-03-13 22:08:26 -0700727 NSDictionary *optionalConstraints = @{ @"DtlsSrtpKeyAgreement" : value };
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000728 RTCMediaConstraints* constraints =
729 [[RTCMediaConstraints alloc]
730 initWithMandatoryConstraints:nil
731 optionalConstraints:optionalConstraints];
732 return constraints;
733}
734
hjon79858f82016-03-13 22:08:26 -0700735- (RTCIceServer *)defaultSTUNServer {
736 return [[RTCIceServer alloc] initWithURLStrings:@[kARDDefaultSTUNServerUrl]
737 username:@""
738 credential:@""];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000739}
740
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000741#pragma mark - Errors
742
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000743+ (NSError *)errorForJoinResultType:(ARDJoinResultType)resultType {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000744 NSError *error = nil;
745 switch (resultType) {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000746 case kARDJoinResultTypeSuccess:
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000747 break;
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000748 case kARDJoinResultTypeUnknown: {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000749 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
750 code:kARDAppClientErrorUnknown
751 userInfo:@{
752 NSLocalizedDescriptionKey: @"Unknown error.",
753 }];
754 break;
755 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000756 case kARDJoinResultTypeFull: {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000757 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
758 code:kARDAppClientErrorRoomFull
759 userInfo:@{
760 NSLocalizedDescriptionKey: @"Room is full.",
761 }];
762 break;
763 }
764 }
765 return error;
766}
767
768+ (NSError *)errorForMessageResultType:(ARDMessageResultType)resultType {
769 NSError *error = nil;
770 switch (resultType) {
771 case kARDMessageResultTypeSuccess:
772 break;
773 case kARDMessageResultTypeUnknown:
774 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
775 code:kARDAppClientErrorUnknown
776 userInfo:@{
777 NSLocalizedDescriptionKey: @"Unknown error.",
778 }];
779 break;
780 case kARDMessageResultTypeInvalidClient:
781 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
782 code:kARDAppClientErrorInvalidClient
783 userInfo:@{
784 NSLocalizedDescriptionKey: @"Invalid client.",
785 }];
786 break;
787 case kARDMessageResultTypeInvalidRoom:
788 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
789 code:kARDAppClientErrorInvalidRoom
790 userInfo:@{
791 NSLocalizedDescriptionKey: @"Invalid room.",
792 }];
793 break;
794 }
795 return error;
796}
797
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000798@end