blob: 88431c6562d18682891377aef9d34f628965cb78 [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"
Zeke Chin57cc74e2015-05-05 07:52:31 -070015#endif
tkchin9eeb6242016-04-27 01:54:20 -070016#import "WebRTC/RTCAudioTrack.h"
17#import "WebRTC/RTCConfiguration.h"
18#import "WebRTC/RTCFileLogger.h"
19#import "WebRTC/RTCIceServer.h"
20#import "WebRTC/RTCLogging.h"
21#import "WebRTC/RTCMediaConstraints.h"
22#import "WebRTC/RTCMediaStream.h"
23#import "WebRTC/RTCPeerConnectionFactory.h"
skvladf3569c82016-04-29 15:30:16 -070024#import "WebRTC/RTCRtpSender.h"
tkchin204177f2016-06-14 15:03:11 -070025#import "WebRTC/RTCTracing.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)
tkchin204177f2016-06-14 15:03:11 -070058// TODO(tkchin): Add these as UI options.
tkchind1fb26d2016-02-03 01:51:18 -080059static BOOL const kARDAppClientEnableTracing = NO;
tkchin204177f2016-06-14 15:03:11 -070060static BOOL const kARDAppClientEnableRtcEventLog = YES;
61static int64_t const kARDAppClientRtcEventLogMaxSizeInBytes = 5e6; // 5 MB.
tkchind1fb26d2016-02-03 01:51:18 -080062#endif
63
Zeke Chind3325802015-08-14 11:00:02 -070064// We need a proxy to NSTimer because it causes a strong retain cycle. When
65// using the proxy, |invalidate| must be called before it properly deallocs.
66@interface ARDTimerProxy : NSObject
67
68- (instancetype)initWithInterval:(NSTimeInterval)interval
69 repeats:(BOOL)repeats
70 timerHandler:(void (^)(void))timerHandler;
71- (void)invalidate;
72
73@end
74
75@implementation ARDTimerProxy {
76 NSTimer *_timer;
77 void (^_timerHandler)(void);
Zeke Chin2d3b7e22015-07-14 12:55:44 -070078}
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000079
Zeke Chind3325802015-08-14 11:00:02 -070080- (instancetype)initWithInterval:(NSTimeInterval)interval
81 repeats:(BOOL)repeats
82 timerHandler:(void (^)(void))timerHandler {
83 NSParameterAssert(timerHandler);
84 if (self = [super init]) {
85 _timerHandler = timerHandler;
86 _timer = [NSTimer scheduledTimerWithTimeInterval:interval
87 target:self
88 selector:@selector(timerDidFire:)
89 userInfo:nil
90 repeats:repeats];
91 }
92 return self;
93}
94
95- (void)invalidate {
96 [_timer invalidate];
97}
98
99- (void)timerDidFire:(NSTimer *)timer {
100 _timerHandler();
101}
102
103@end
104
105@implementation ARDAppClient {
106 RTCFileLogger *_fileLogger;
107 ARDTimerProxy *_statsTimer;
108}
109
110@synthesize shouldGetStats = _shouldGetStats;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000111@synthesize state = _state;
Zeke Chind3325802015-08-14 11:00:02 -0700112@synthesize delegate = _delegate;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000113@synthesize roomServerClient = _roomServerClient;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000114@synthesize channel = _channel;
haysc913e6452015-10-02 11:44:03 -0700115@synthesize loopbackChannel = _loopbackChannel;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000116@synthesize turnClient = _turnClient;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000117@synthesize peerConnection = _peerConnection;
118@synthesize factory = _factory;
119@synthesize messageQueue = _messageQueue;
120@synthesize isTurnComplete = _isTurnComplete;
121@synthesize hasReceivedSdp = _hasReceivedSdp;
122@synthesize roomId = _roomId;
123@synthesize clientId = _clientId;
124@synthesize isInitiator = _isInitiator;
125@synthesize iceServers = _iceServers;
126@synthesize webSocketURL = _websocketURL;
127@synthesize webSocketRestURL = _websocketRestURL;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000128@synthesize defaultPeerConnectionConstraints =
129 _defaultPeerConnectionConstraints;
haysc913e6452015-10-02 11:44:03 -0700130@synthesize isLoopback = _isLoopback;
131@synthesize isAudioOnly = _isAudioOnly;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000132
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000133- (instancetype)init {
134 if (self = [super init]) {
135 _roomServerClient = [[ARDAppEngineClient alloc] init];
136 NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl];
137 _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL];
138 [self configure];
139 }
140 return self;
141}
142
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000143- (instancetype)initWithDelegate:(id<ARDAppClientDelegate>)delegate {
144 if (self = [super init]) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000145 _roomServerClient = [[ARDAppEngineClient alloc] init];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000146 _delegate = delegate;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000147 NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl];
148 _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL];
149 [self configure];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000150 }
151 return self;
152}
153
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000154// TODO(tkchin): Provide signaling channel factory interface so we can recreate
155// channel if we need to on network failure. Also, make this the default public
156// constructor.
157- (instancetype)initWithRoomServerClient:(id<ARDRoomServerClient>)rsClient
158 signalingChannel:(id<ARDSignalingChannel>)channel
159 turnClient:(id<ARDTURNClient>)turnClient
160 delegate:(id<ARDAppClientDelegate>)delegate {
161 NSParameterAssert(rsClient);
162 NSParameterAssert(channel);
163 NSParameterAssert(turnClient);
164 if (self = [super init]) {
165 _roomServerClient = rsClient;
166 _channel = channel;
167 _turnClient = turnClient;
168 _delegate = delegate;
169 [self configure];
170 }
171 return self;
172}
173
174- (void)configure {
175 _factory = [[RTCPeerConnectionFactory alloc] init];
176 _messageQueue = [NSMutableArray array];
177 _iceServers = [NSMutableArray arrayWithObject:[self defaultSTUNServer]];
Zeke Chin2d3b7e22015-07-14 12:55:44 -0700178 _fileLogger = [[RTCFileLogger alloc] init];
179 [_fileLogger start];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000180}
181
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000182- (void)dealloc {
Zeke Chind3325802015-08-14 11:00:02 -0700183 self.shouldGetStats = NO;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000184 [self disconnect];
185}
186
Zeke Chind3325802015-08-14 11:00:02 -0700187- (void)setShouldGetStats:(BOOL)shouldGetStats {
188 if (_shouldGetStats == shouldGetStats) {
189 return;
190 }
191 if (shouldGetStats) {
192 __weak ARDAppClient *weakSelf = self;
193 _statsTimer = [[ARDTimerProxy alloc] initWithInterval:1
194 repeats:YES
195 timerHandler:^{
196 ARDAppClient *strongSelf = weakSelf;
hjon79858f82016-03-13 22:08:26 -0700197 [strongSelf.peerConnection statsForTrack:nil
198 statsOutputLevel:RTCStatsOutputLevelDebug
199 completionHandler:^(NSArray *stats) {
200 dispatch_async(dispatch_get_main_queue(), ^{
201 ARDAppClient *strongSelf = weakSelf;
202 [strongSelf.delegate appClient:strongSelf didGetStats:stats];
203 });
204 }];
Zeke Chind3325802015-08-14 11:00:02 -0700205 }];
206 } else {
207 [_statsTimer invalidate];
208 _statsTimer = nil;
209 }
210 _shouldGetStats = shouldGetStats;
211}
212
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000213- (void)setState:(ARDAppClientState)state {
214 if (_state == state) {
215 return;
216 }
217 _state = state;
218 [_delegate appClient:self didChangeState:_state];
219}
220
221- (void)connectToRoomWithId:(NSString *)roomId
haysc913e6452015-10-02 11:44:03 -0700222 isLoopback:(BOOL)isLoopback
223 isAudioOnly:(BOOL)isAudioOnly {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000224 NSParameterAssert(roomId.length);
225 NSParameterAssert(_state == kARDAppClientStateDisconnected);
haysc913e6452015-10-02 11:44:03 -0700226 _isLoopback = isLoopback;
227 _isAudioOnly = isAudioOnly;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000228 self.state = kARDAppClientStateConnecting;
229
tkchind1fb26d2016-02-03 01:51:18 -0800230#if defined(WEBRTC_IOS)
231 if (kARDAppClientEnableTracing) {
tkchin204177f2016-06-14 15:03:11 -0700232 NSString *filePath = [self documentsFilePathForFileName:@"webrtc-trace.txt"];
tkchind1fb26d2016-02-03 01:51:18 -0800233 RTCStartInternalCapture(filePath);
234 }
235#endif
236
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000237 // Request TURN.
238 __weak ARDAppClient *weakSelf = self;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000239 [_turnClient requestServersWithCompletionHandler:^(NSArray *turnServers,
240 NSError *error) {
241 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700242 RTCLogError("Error retrieving TURN servers: %@",
243 error.localizedDescription);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000244 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000245 ARDAppClient *strongSelf = weakSelf;
246 [strongSelf.iceServers addObjectsFromArray:turnServers];
247 strongSelf.isTurnComplete = YES;
248 [strongSelf startSignalingIfReady];
249 }];
jiayl@webrtc.org27f53172014-12-31 00:26:20 +0000250
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000251 // Join room on room server.
252 [_roomServerClient joinRoomWithRoomId:roomId
haysc913e6452015-10-02 11:44:03 -0700253 isLoopback:isLoopback
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000254 completionHandler:^(ARDJoinResponse *response, NSError *error) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000255 ARDAppClient *strongSelf = weakSelf;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000256 if (error) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000257 [strongSelf.delegate appClient:strongSelf didError:error];
258 return;
259 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000260 NSError *joinError =
261 [[strongSelf class] errorForJoinResultType:response.result];
262 if (joinError) {
tkchinc3f46a92015-07-23 12:50:55 -0700263 RTCLogError(@"Failed to join room:%@ on room server.", roomId);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000264 [strongSelf disconnect];
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000265 [strongSelf.delegate appClient:strongSelf didError:joinError];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000266 return;
267 }
tkchinc3f46a92015-07-23 12:50:55 -0700268 RTCLog(@"Joined room:%@ on room server.", roomId);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000269 strongSelf.roomId = response.roomId;
270 strongSelf.clientId = response.clientId;
271 strongSelf.isInitiator = response.isInitiator;
272 for (ARDSignalingMessage *message in response.messages) {
273 if (message.type == kARDSignalingMessageTypeOffer ||
274 message.type == kARDSignalingMessageTypeAnswer) {
275 strongSelf.hasReceivedSdp = YES;
276 [strongSelf.messageQueue insertObject:message atIndex:0];
277 } else {
278 [strongSelf.messageQueue addObject:message];
279 }
280 }
281 strongSelf.webSocketURL = response.webSocketURL;
282 strongSelf.webSocketRestURL = response.webSocketRestURL;
283 [strongSelf registerWithColliderIfReady];
284 [strongSelf startSignalingIfReady];
285 }];
286}
287
288- (void)disconnect {
289 if (_state == kARDAppClientStateDisconnected) {
290 return;
291 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000292 if (self.hasJoinedRoomServerRoom) {
293 [_roomServerClient leaveRoomWithRoomId:_roomId
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000294 clientId:_clientId
295 completionHandler:nil];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000296 }
297 if (_channel) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000298 if (_channel.state == kARDSignalingChannelStateRegistered) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000299 // Tell the other client we're hanging up.
300 ARDByeMessage *byeMessage = [[ARDByeMessage alloc] init];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000301 [_channel sendMessage:byeMessage];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000302 }
303 // Disconnect from collider.
304 _channel = nil;
305 }
306 _clientId = nil;
307 _roomId = nil;
308 _isInitiator = NO;
309 _hasReceivedSdp = NO;
310 _messageQueue = [NSMutableArray array];
311 _peerConnection = nil;
312 self.state = kARDAppClientStateDisconnected;
tkchind1fb26d2016-02-03 01:51:18 -0800313#if defined(WEBRTC_IOS)
314 RTCStopInternalCapture();
tkchin204177f2016-06-14 15:03:11 -0700315 [_factory stopRtcEventLog];
tkchind1fb26d2016-02-03 01:51:18 -0800316#endif
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000317}
318
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000319#pragma mark - ARDSignalingChannelDelegate
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000320
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000321- (void)channel:(id<ARDSignalingChannel>)channel
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000322 didReceiveMessage:(ARDSignalingMessage *)message {
323 switch (message.type) {
324 case kARDSignalingMessageTypeOffer:
325 case kARDSignalingMessageTypeAnswer:
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000326 // Offers and answers must be processed before any other message, so we
327 // place them at the front of the queue.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000328 _hasReceivedSdp = YES;
329 [_messageQueue insertObject:message atIndex:0];
330 break;
331 case kARDSignalingMessageTypeCandidate:
Honghai Zhangda2ba4d2016-05-23 11:53:14 -0700332 case kARDSignalingMessageTypeCandidateRemoval:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000333 [_messageQueue addObject:message];
334 break;
335 case kARDSignalingMessageTypeBye:
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000336 // Disconnects can be processed immediately.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000337 [self processSignalingMessage:message];
338 return;
339 }
340 [self drainMessageQueueIfReady];
341}
342
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000343- (void)channel:(id<ARDSignalingChannel>)channel
344 didChangeState:(ARDSignalingChannelState)state {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000345 switch (state) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000346 case kARDSignalingChannelStateOpen:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000347 break;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000348 case kARDSignalingChannelStateRegistered:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000349 break;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000350 case kARDSignalingChannelStateClosed:
351 case kARDSignalingChannelStateError:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000352 // TODO(tkchin): reconnection scenarios. Right now we just disconnect
353 // completely if the websocket connection fails.
354 [self disconnect];
355 break;
356 }
357}
358
359#pragma mark - RTCPeerConnectionDelegate
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000360// Callbacks for this delegate occur on non-main thread and need to be
361// dispatched back to main queue as needed.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000362
363- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700364 didChangeSignalingState:(RTCSignalingState)stateChanged {
365 RTCLog(@"Signaling state changed: %ld", (long)stateChanged);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000366}
367
368- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700369 didAddStream:(RTCMediaStream *)stream {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000370 dispatch_async(dispatch_get_main_queue(), ^{
tkchinc3f46a92015-07-23 12:50:55 -0700371 RTCLog(@"Received %lu video tracks and %lu audio tracks",
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000372 (unsigned long)stream.videoTracks.count,
373 (unsigned long)stream.audioTracks.count);
374 if (stream.videoTracks.count) {
375 RTCVideoTrack *videoTrack = stream.videoTracks[0];
376 [_delegate appClient:self didReceiveRemoteVideoTrack:videoTrack];
377 }
378 });
379}
380
381- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700382 didRemoveStream:(RTCMediaStream *)stream {
tkchinc3f46a92015-07-23 12:50:55 -0700383 RTCLog(@"Stream was removed.");
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000384}
385
hjon79858f82016-03-13 22:08:26 -0700386- (void)peerConnectionShouldNegotiate:(RTCPeerConnection *)peerConnection {
tkchinc3f46a92015-07-23 12:50:55 -0700387 RTCLog(@"WARNING: Renegotiation needed but unimplemented.");
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000388}
389
390- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700391 didChangeIceConnectionState:(RTCIceConnectionState)newState {
392 RTCLog(@"ICE state changed: %ld", (long)newState);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000393 dispatch_async(dispatch_get_main_queue(), ^{
394 [_delegate appClient:self didChangeConnectionState:newState];
395 });
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000396}
397
398- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700399 didChangeIceGatheringState:(RTCIceGatheringState)newState {
400 RTCLog(@"ICE gathering state changed: %ld", (long)newState);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000401}
402
403- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700404 didGenerateIceCandidate:(RTCIceCandidate *)candidate {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000405 dispatch_async(dispatch_get_main_queue(), ^{
406 ARDICECandidateMessage *message =
407 [[ARDICECandidateMessage alloc] initWithCandidate:candidate];
408 [self sendSignalingMessage:message];
409 });
410}
411
Zeke Chind3325802015-08-14 11:00:02 -0700412- (void)peerConnection:(RTCPeerConnection *)peerConnection
Honghai Zhangda2ba4d2016-05-23 11:53:14 -0700413 didRemoveIceCandidates:(NSArray<RTCIceCandidate *> *)candidates {
414 dispatch_async(dispatch_get_main_queue(), ^{
415 ARDICECandidateRemovalMessage *message =
416 [[ARDICECandidateRemovalMessage alloc]
417 initWithRemovedCandidates:candidates];
418 [self sendSignalingMessage:message];
419 });
420}
421
422- (void)peerConnection:(RTCPeerConnection *)peerConnection
Zeke Chind3325802015-08-14 11:00:02 -0700423 didOpenDataChannel:(RTCDataChannel *)dataChannel {
424}
425
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000426#pragma mark - RTCSessionDescriptionDelegate
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000427// Callbacks for this delegate occur on non-main thread and need to be
428// dispatched back to main queue as needed.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000429
430- (void)peerConnection:(RTCPeerConnection *)peerConnection
431 didCreateSessionDescription:(RTCSessionDescription *)sdp
432 error:(NSError *)error {
433 dispatch_async(dispatch_get_main_queue(), ^{
434 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700435 RTCLogError(@"Failed to create session description. Error: %@", error);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000436 [self disconnect];
437 NSDictionary *userInfo = @{
438 NSLocalizedDescriptionKey: @"Failed to create session description.",
439 };
440 NSError *sdpError =
441 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
442 code:kARDAppClientErrorCreateSDP
443 userInfo:userInfo];
444 [_delegate appClient:self didError:sdpError];
445 return;
446 }
Zeke Chin71f6f442015-06-29 14:34:58 -0700447 // Prefer H264 if available.
448 RTCSessionDescription *sdpPreferringH264 =
449 [ARDSDPUtils descriptionForDescription:sdp
450 preferredVideoCodec:@"H264"];
hjon79858f82016-03-13 22:08:26 -0700451 __weak ARDAppClient *weakSelf = self;
452 [_peerConnection setLocalDescription:sdpPreferringH264
453 completionHandler:^(NSError *error) {
454 ARDAppClient *strongSelf = weakSelf;
455 [strongSelf peerConnection:strongSelf.peerConnection
456 didSetSessionDescriptionWithError:error];
457 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000458 ARDSessionDescriptionMessage *message =
Zeke Chin71f6f442015-06-29 14:34:58 -0700459 [[ARDSessionDescriptionMessage alloc]
460 initWithDescription:sdpPreferringH264];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000461 [self sendSignalingMessage:message];
462 });
463}
464
465- (void)peerConnection:(RTCPeerConnection *)peerConnection
466 didSetSessionDescriptionWithError:(NSError *)error {
467 dispatch_async(dispatch_get_main_queue(), ^{
468 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700469 RTCLogError(@"Failed to set session description. Error: %@", error);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000470 [self disconnect];
471 NSDictionary *userInfo = @{
472 NSLocalizedDescriptionKey: @"Failed to set session description.",
473 };
474 NSError *sdpError =
475 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
476 code:kARDAppClientErrorSetSDP
477 userInfo:userInfo];
478 [_delegate appClient:self didError:sdpError];
479 return;
480 }
481 // If we're answering and we've just set the remote offer we need to create
482 // an answer and set the local description.
483 if (!_isInitiator && !_peerConnection.localDescription) {
484 RTCMediaConstraints *constraints = [self defaultAnswerConstraints];
hjon79858f82016-03-13 22:08:26 -0700485 __weak ARDAppClient *weakSelf = self;
486 [_peerConnection answerForConstraints:constraints
487 completionHandler:^(RTCSessionDescription *sdp,
488 NSError *error) {
489 ARDAppClient *strongSelf = weakSelf;
490 [strongSelf peerConnection:strongSelf.peerConnection
491 didCreateSessionDescription:sdp
492 error:error];
493 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000494 }
495 });
496}
497
498#pragma mark - Private
499
tkchin204177f2016-06-14 15:03:11 -0700500#if defined(WEBRTC_IOS)
501
502- (NSString *)documentsFilePathForFileName:(NSString *)fileName {
503 NSParameterAssert(fileName.length);
504 NSArray *paths = NSSearchPathForDirectoriesInDomains(
505 NSDocumentDirectory, NSUserDomainMask, YES);
506 NSString *documentsDirPath = paths.firstObject;
507 NSString *filePath =
508 [documentsDirPath stringByAppendingPathComponent:fileName];
509 return filePath;
510}
511
512#endif
513
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000514- (BOOL)hasJoinedRoomServerRoom {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000515 return _clientId.length;
516}
517
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000518// Begins the peer connection connection process if we have both joined a room
519// on the room server and tried to obtain a TURN server. Otherwise does nothing.
520// A peer connection object will be created with a stream that contains local
521// audio and video capture. If this client is the caller, an offer is created as
522// well, otherwise the client will wait for an offer to arrive.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000523- (void)startSignalingIfReady {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000524 if (!_isTurnComplete || !self.hasJoinedRoomServerRoom) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000525 return;
526 }
527 self.state = kARDAppClientStateConnected;
528
tkchin204177f2016-06-14 15:03:11 -0700529#if defined(WEBRTC_IOS)
530 // Start event log.
531 if (kARDAppClientEnableRtcEventLog) {
532 NSString *filePath = [self documentsFilePathForFileName:@"webrtc-rtceventlog"];
533 if (![_factory startRtcEventLogWithFilePath:filePath
534 maxSizeInBytes:kARDAppClientRtcEventLogMaxSizeInBytes]) {
535 RTCLogError(@"Failed to start event logging.");
536 }
537 }
538#endif
539
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000540 // Create peer connection.
541 RTCMediaConstraints *constraints = [self defaultPeerConnectionConstraints];
Zeke Chinbc7dd7e2015-05-29 14:59:14 -0700542 RTCConfiguration *config = [[RTCConfiguration alloc] init];
543 config.iceServers = _iceServers;
Tze Kwang Chinf3cb49f2016-03-22 10:57:40 -0700544 _peerConnection = [_factory peerConnectionWithConfiguration:config
545 constraints:constraints
546 delegate:self];
skvladf3569c82016-04-29 15:30:16 -0700547 // Create AV senders.
548 [self createAudioSender];
549 [self createVideoSender];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000550 if (_isInitiator) {
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000551 // Send offer.
hjon79858f82016-03-13 22:08:26 -0700552 __weak ARDAppClient *weakSelf = self;
553 [_peerConnection offerForConstraints:[self defaultOfferConstraints]
554 completionHandler:^(RTCSessionDescription *sdp,
555 NSError *error) {
556 ARDAppClient *strongSelf = weakSelf;
557 [strongSelf peerConnection:strongSelf.peerConnection
558 didCreateSessionDescription:sdp
559 error:error];
560 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000561 } else {
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000562 // Check if we've received an offer.
563 [self drainMessageQueueIfReady];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000564 }
565}
566
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000567// Processes the messages that we've received from the room server and the
568// signaling channel. The offer or answer message must be processed before other
569// signaling messages, however they can arrive out of order. Hence, this method
570// only processes pending messages if there is a peer connection object and
571// if we have received either an offer or answer.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000572- (void)drainMessageQueueIfReady {
573 if (!_peerConnection || !_hasReceivedSdp) {
574 return;
575 }
576 for (ARDSignalingMessage *message in _messageQueue) {
577 [self processSignalingMessage:message];
578 }
579 [_messageQueue removeAllObjects];
580}
581
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000582// Processes the given signaling message based on its type.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000583- (void)processSignalingMessage:(ARDSignalingMessage *)message {
584 NSParameterAssert(_peerConnection ||
585 message.type == kARDSignalingMessageTypeBye);
586 switch (message.type) {
587 case kARDSignalingMessageTypeOffer:
588 case kARDSignalingMessageTypeAnswer: {
589 ARDSessionDescriptionMessage *sdpMessage =
590 (ARDSessionDescriptionMessage *)message;
591 RTCSessionDescription *description = sdpMessage.sessionDescription;
Zeke Chin71f6f442015-06-29 14:34:58 -0700592 // Prefer H264 if available.
593 RTCSessionDescription *sdpPreferringH264 =
594 [ARDSDPUtils descriptionForDescription:description
595 preferredVideoCodec:@"H264"];
hjon79858f82016-03-13 22:08:26 -0700596 __weak ARDAppClient *weakSelf = self;
597 [_peerConnection setRemoteDescription:sdpPreferringH264
598 completionHandler:^(NSError *error) {
599 ARDAppClient *strongSelf = weakSelf;
600 [strongSelf peerConnection:strongSelf.peerConnection
601 didSetSessionDescriptionWithError:error];
602 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000603 break;
604 }
605 case kARDSignalingMessageTypeCandidate: {
606 ARDICECandidateMessage *candidateMessage =
607 (ARDICECandidateMessage *)message;
hjon79858f82016-03-13 22:08:26 -0700608 [_peerConnection addIceCandidate:candidateMessage.candidate];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000609 break;
610 }
Honghai Zhangda2ba4d2016-05-23 11:53:14 -0700611 case kARDSignalingMessageTypeCandidateRemoval: {
612 ARDICECandidateRemovalMessage *candidateMessage =
613 (ARDICECandidateRemovalMessage *)message;
614 [_peerConnection removeIceCandidates:candidateMessage.candidates];
615 break;
616 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000617 case kARDSignalingMessageTypeBye:
618 // Other client disconnected.
619 // TODO(tkchin): support waiting in room for next client. For now just
620 // disconnect.
621 [self disconnect];
622 break;
623 }
624}
625
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000626// Sends a signaling message to the other client. The caller will send messages
627// through the room server, whereas the callee will send messages over the
628// signaling channel.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000629- (void)sendSignalingMessage:(ARDSignalingMessage *)message {
630 if (_isInitiator) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000631 __weak ARDAppClient *weakSelf = self;
632 [_roomServerClient sendMessage:message
633 forRoomId:_roomId
634 clientId:_clientId
635 completionHandler:^(ARDMessageResponse *response,
636 NSError *error) {
637 ARDAppClient *strongSelf = weakSelf;
638 if (error) {
639 [strongSelf.delegate appClient:strongSelf didError:error];
640 return;
641 }
642 NSError *messageError =
643 [[strongSelf class] errorForMessageResultType:response.result];
644 if (messageError) {
645 [strongSelf.delegate appClient:strongSelf didError:messageError];
646 return;
647 }
648 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000649 } else {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000650 [_channel sendMessage:message];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000651 }
652}
653
skvladf3569c82016-04-29 15:30:16 -0700654- (RTCRtpSender *)createVideoSender {
655 RTCRtpSender *sender =
656 [_peerConnection senderWithKind:kRTCMediaStreamTrackKindVideo
657 streamId:kARDMediaStreamId];
658 RTCVideoTrack *track = [self createLocalVideoTrack];
659 if (track) {
660 sender.track = track;
661 [_delegate appClient:self didReceiveLocalVideoTrack:track];
Zeke Chin57cc74e2015-05-05 07:52:31 -0700662 }
skvladf3569c82016-04-29 15:30:16 -0700663 return sender;
664}
665
666- (RTCRtpSender *)createAudioSender {
667 RTCRtpSender *sender =
668 [_peerConnection senderWithKind:kRTCMediaStreamTrackKindAudio
669 streamId:kARDMediaStreamId];
670 RTCAudioTrack *track = [_factory audioTrackWithTrackId:kARDAudioTrackId];
671 sender.track = track;
672 return sender;
Zeke Chin57cc74e2015-05-05 07:52:31 -0700673}
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000674
Zeke Chin57cc74e2015-05-05 07:52:31 -0700675- (RTCVideoTrack *)createLocalVideoTrack {
676 RTCVideoTrack* localVideoTrack = nil;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000677 // The iOS simulator doesn't provide any sort of camera capture
678 // support or emulation (http://goo.gl/rHAnC1) so don't bother
679 // trying to open a local stream.
680 // TODO(tkchin): local video capture for OSX. See
681 // https://code.google.com/p/webrtc/issues/detail?id=3417.
682#if !TARGET_IPHONE_SIMULATOR && TARGET_OS_IPHONE
haysc913e6452015-10-02 11:44:03 -0700683 if (!_isAudioOnly) {
684 RTCMediaConstraints *mediaConstraints =
685 [self defaultMediaStreamConstraints];
686 RTCAVFoundationVideoSource *source =
Tze Kwang Chinf3cb49f2016-03-22 10:57:40 -0700687 [_factory avFoundationVideoSourceWithConstraints:mediaConstraints];
haysc913e6452015-10-02 11:44:03 -0700688 localVideoTrack =
Tze Kwang Chinf3cb49f2016-03-22 10:57:40 -0700689 [_factory videoTrackWithSource:source
skvladf3569c82016-04-29 15:30:16 -0700690 trackId:kARDVideoTrackId];
haysc913e6452015-10-02 11:44:03 -0700691 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000692#endif
Zeke Chin57cc74e2015-05-05 07:52:31 -0700693 return localVideoTrack;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000694}
695
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000696#pragma mark - Collider methods
697
698- (void)registerWithColliderIfReady {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000699 if (!self.hasJoinedRoomServerRoom) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000700 return;
701 }
702 // Open WebSocket connection.
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000703 if (!_channel) {
704 _channel =
705 [[ARDWebSocketChannel alloc] initWithURL:_websocketURL
706 restURL:_websocketRestURL
707 delegate:self];
haysc913e6452015-10-02 11:44:03 -0700708 if (_isLoopback) {
709 _loopbackChannel =
710 [[ARDLoopbackWebSocketChannel alloc] initWithURL:_websocketURL
711 restURL:_websocketRestURL];
712 }
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000713 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000714 [_channel registerForRoomId:_roomId clientId:_clientId];
haysc913e6452015-10-02 11:44:03 -0700715 if (_isLoopback) {
716 [_loopbackChannel registerForRoomId:_roomId clientId:@"LOOPBACK_CLIENT_ID"];
717 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000718}
719
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000720#pragma mark - Defaults
721
722- (RTCMediaConstraints *)defaultMediaStreamConstraints {
723 RTCMediaConstraints* constraints =
724 [[RTCMediaConstraints alloc]
725 initWithMandatoryConstraints:nil
726 optionalConstraints:nil];
727 return constraints;
728}
729
730- (RTCMediaConstraints *)defaultAnswerConstraints {
731 return [self defaultOfferConstraints];
732}
733
734- (RTCMediaConstraints *)defaultOfferConstraints {
hjon79858f82016-03-13 22:08:26 -0700735 NSDictionary *mandatoryConstraints = @{
736 @"OfferToReceiveAudio" : @"true",
737 @"OfferToReceiveVideo" : @"true"
738 };
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000739 RTCMediaConstraints* constraints =
740 [[RTCMediaConstraints alloc]
741 initWithMandatoryConstraints:mandatoryConstraints
742 optionalConstraints:nil];
743 return constraints;
744}
745
746- (RTCMediaConstraints *)defaultPeerConnectionConstraints {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000747 if (_defaultPeerConnectionConstraints) {
748 return _defaultPeerConnectionConstraints;
749 }
haysc913e6452015-10-02 11:44:03 -0700750 NSString *value = _isLoopback ? @"false" : @"true";
hjon79858f82016-03-13 22:08:26 -0700751 NSDictionary *optionalConstraints = @{ @"DtlsSrtpKeyAgreement" : value };
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000752 RTCMediaConstraints* constraints =
753 [[RTCMediaConstraints alloc]
754 initWithMandatoryConstraints:nil
755 optionalConstraints:optionalConstraints];
756 return constraints;
757}
758
hjon79858f82016-03-13 22:08:26 -0700759- (RTCIceServer *)defaultSTUNServer {
760 return [[RTCIceServer alloc] initWithURLStrings:@[kARDDefaultSTUNServerUrl]
761 username:@""
762 credential:@""];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000763}
764
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000765#pragma mark - Errors
766
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000767+ (NSError *)errorForJoinResultType:(ARDJoinResultType)resultType {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000768 NSError *error = nil;
769 switch (resultType) {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000770 case kARDJoinResultTypeSuccess:
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000771 break;
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000772 case kARDJoinResultTypeUnknown: {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000773 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
774 code:kARDAppClientErrorUnknown
775 userInfo:@{
776 NSLocalizedDescriptionKey: @"Unknown error.",
777 }];
778 break;
779 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000780 case kARDJoinResultTypeFull: {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000781 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
782 code:kARDAppClientErrorRoomFull
783 userInfo:@{
784 NSLocalizedDescriptionKey: @"Room is full.",
785 }];
786 break;
787 }
788 }
789 return error;
790}
791
792+ (NSError *)errorForMessageResultType:(ARDMessageResultType)resultType {
793 NSError *error = nil;
794 switch (resultType) {
795 case kARDMessageResultTypeSuccess:
796 break;
797 case kARDMessageResultTypeUnknown:
798 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
799 code:kARDAppClientErrorUnknown
800 userInfo:@{
801 NSLocalizedDescriptionKey: @"Unknown error.",
802 }];
803 break;
804 case kARDMessageResultTypeInvalidClient:
805 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
806 code:kARDAppClientErrorInvalidClient
807 userInfo:@{
808 NSLocalizedDescriptionKey: @"Invalid client.",
809 }];
810 break;
811 case kARDMessageResultTypeInvalidRoom:
812 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
813 code:kARDAppClientErrorInvalidRoom
814 userInfo:@{
815 NSLocalizedDescriptionKey: @"Invalid room.",
816 }];
817 break;
818 }
819 return error;
820}
821
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000822@end