blob: 43eb6f4ad2fff30c4534ec9bea438b015c6d890e [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];
Ivo Creusenc43bf562016-06-30 09:31:52 +0200311#if defined(WEBRTC_IOS)
312 [_peerConnection stopRtcEventLog];
313#endif
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000314 _peerConnection = nil;
315 self.state = kARDAppClientStateDisconnected;
tkchind1fb26d2016-02-03 01:51:18 -0800316#if defined(WEBRTC_IOS)
317 RTCStopInternalCapture();
318#endif
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000319}
320
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000321#pragma mark - ARDSignalingChannelDelegate
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000322
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000323- (void)channel:(id<ARDSignalingChannel>)channel
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000324 didReceiveMessage:(ARDSignalingMessage *)message {
325 switch (message.type) {
326 case kARDSignalingMessageTypeOffer:
327 case kARDSignalingMessageTypeAnswer:
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000328 // Offers and answers must be processed before any other message, so we
329 // place them at the front of the queue.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000330 _hasReceivedSdp = YES;
331 [_messageQueue insertObject:message atIndex:0];
332 break;
333 case kARDSignalingMessageTypeCandidate:
Honghai Zhangda2ba4d2016-05-23 11:53:14 -0700334 case kARDSignalingMessageTypeCandidateRemoval:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000335 [_messageQueue addObject:message];
336 break;
337 case kARDSignalingMessageTypeBye:
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000338 // Disconnects can be processed immediately.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000339 [self processSignalingMessage:message];
340 return;
341 }
342 [self drainMessageQueueIfReady];
343}
344
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000345- (void)channel:(id<ARDSignalingChannel>)channel
346 didChangeState:(ARDSignalingChannelState)state {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000347 switch (state) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000348 case kARDSignalingChannelStateOpen:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000349 break;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000350 case kARDSignalingChannelStateRegistered:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000351 break;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000352 case kARDSignalingChannelStateClosed:
353 case kARDSignalingChannelStateError:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000354 // TODO(tkchin): reconnection scenarios. Right now we just disconnect
355 // completely if the websocket connection fails.
356 [self disconnect];
357 break;
358 }
359}
360
361#pragma mark - RTCPeerConnectionDelegate
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000362// Callbacks for this delegate occur on non-main thread and need to be
363// dispatched back to main queue as needed.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000364
365- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700366 didChangeSignalingState:(RTCSignalingState)stateChanged {
367 RTCLog(@"Signaling state changed: %ld", (long)stateChanged);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000368}
369
370- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700371 didAddStream:(RTCMediaStream *)stream {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000372 dispatch_async(dispatch_get_main_queue(), ^{
tkchinc3f46a92015-07-23 12:50:55 -0700373 RTCLog(@"Received %lu video tracks and %lu audio tracks",
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000374 (unsigned long)stream.videoTracks.count,
375 (unsigned long)stream.audioTracks.count);
376 if (stream.videoTracks.count) {
377 RTCVideoTrack *videoTrack = stream.videoTracks[0];
378 [_delegate appClient:self didReceiveRemoteVideoTrack:videoTrack];
379 }
380 });
381}
382
383- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700384 didRemoveStream:(RTCMediaStream *)stream {
tkchinc3f46a92015-07-23 12:50:55 -0700385 RTCLog(@"Stream was removed.");
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000386}
387
hjon79858f82016-03-13 22:08:26 -0700388- (void)peerConnectionShouldNegotiate:(RTCPeerConnection *)peerConnection {
tkchinc3f46a92015-07-23 12:50:55 -0700389 RTCLog(@"WARNING: Renegotiation needed but unimplemented.");
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000390}
391
392- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700393 didChangeIceConnectionState:(RTCIceConnectionState)newState {
394 RTCLog(@"ICE state changed: %ld", (long)newState);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000395 dispatch_async(dispatch_get_main_queue(), ^{
396 [_delegate appClient:self didChangeConnectionState:newState];
397 });
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000398}
399
400- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700401 didChangeIceGatheringState:(RTCIceGatheringState)newState {
402 RTCLog(@"ICE gathering state changed: %ld", (long)newState);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000403}
404
405- (void)peerConnection:(RTCPeerConnection *)peerConnection
hjon79858f82016-03-13 22:08:26 -0700406 didGenerateIceCandidate:(RTCIceCandidate *)candidate {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000407 dispatch_async(dispatch_get_main_queue(), ^{
408 ARDICECandidateMessage *message =
409 [[ARDICECandidateMessage alloc] initWithCandidate:candidate];
410 [self sendSignalingMessage:message];
411 });
412}
413
Zeke Chind3325802015-08-14 11:00:02 -0700414- (void)peerConnection:(RTCPeerConnection *)peerConnection
Honghai Zhangda2ba4d2016-05-23 11:53:14 -0700415 didRemoveIceCandidates:(NSArray<RTCIceCandidate *> *)candidates {
416 dispatch_async(dispatch_get_main_queue(), ^{
417 ARDICECandidateRemovalMessage *message =
418 [[ARDICECandidateRemovalMessage alloc]
419 initWithRemovedCandidates:candidates];
420 [self sendSignalingMessage:message];
421 });
422}
423
424- (void)peerConnection:(RTCPeerConnection *)peerConnection
Zeke Chind3325802015-08-14 11:00:02 -0700425 didOpenDataChannel:(RTCDataChannel *)dataChannel {
426}
427
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000428#pragma mark - RTCSessionDescriptionDelegate
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000429// Callbacks for this delegate occur on non-main thread and need to be
430// dispatched back to main queue as needed.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000431
432- (void)peerConnection:(RTCPeerConnection *)peerConnection
433 didCreateSessionDescription:(RTCSessionDescription *)sdp
434 error:(NSError *)error {
435 dispatch_async(dispatch_get_main_queue(), ^{
436 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700437 RTCLogError(@"Failed to create session description. Error: %@", error);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000438 [self disconnect];
439 NSDictionary *userInfo = @{
440 NSLocalizedDescriptionKey: @"Failed to create session description.",
441 };
442 NSError *sdpError =
443 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
444 code:kARDAppClientErrorCreateSDP
445 userInfo:userInfo];
446 [_delegate appClient:self didError:sdpError];
447 return;
448 }
Zeke Chin71f6f442015-06-29 14:34:58 -0700449 // Prefer H264 if available.
450 RTCSessionDescription *sdpPreferringH264 =
451 [ARDSDPUtils descriptionForDescription:sdp
452 preferredVideoCodec:@"H264"];
hjon79858f82016-03-13 22:08:26 -0700453 __weak ARDAppClient *weakSelf = self;
454 [_peerConnection setLocalDescription:sdpPreferringH264
455 completionHandler:^(NSError *error) {
456 ARDAppClient *strongSelf = weakSelf;
457 [strongSelf peerConnection:strongSelf.peerConnection
458 didSetSessionDescriptionWithError:error];
459 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000460 ARDSessionDescriptionMessage *message =
Zeke Chin71f6f442015-06-29 14:34:58 -0700461 [[ARDSessionDescriptionMessage alloc]
462 initWithDescription:sdpPreferringH264];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000463 [self sendSignalingMessage:message];
464 });
465}
466
467- (void)peerConnection:(RTCPeerConnection *)peerConnection
468 didSetSessionDescriptionWithError:(NSError *)error {
469 dispatch_async(dispatch_get_main_queue(), ^{
470 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700471 RTCLogError(@"Failed to set session description. Error: %@", error);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000472 [self disconnect];
473 NSDictionary *userInfo = @{
474 NSLocalizedDescriptionKey: @"Failed to set session description.",
475 };
476 NSError *sdpError =
477 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
478 code:kARDAppClientErrorSetSDP
479 userInfo:userInfo];
480 [_delegate appClient:self didError:sdpError];
481 return;
482 }
483 // If we're answering and we've just set the remote offer we need to create
484 // an answer and set the local description.
485 if (!_isInitiator && !_peerConnection.localDescription) {
486 RTCMediaConstraints *constraints = [self defaultAnswerConstraints];
hjon79858f82016-03-13 22:08:26 -0700487 __weak ARDAppClient *weakSelf = self;
488 [_peerConnection answerForConstraints:constraints
489 completionHandler:^(RTCSessionDescription *sdp,
490 NSError *error) {
491 ARDAppClient *strongSelf = weakSelf;
492 [strongSelf peerConnection:strongSelf.peerConnection
493 didCreateSessionDescription:sdp
494 error:error];
495 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000496 }
497 });
498}
499
500#pragma mark - Private
501
tkchin204177f2016-06-14 15:03:11 -0700502#if defined(WEBRTC_IOS)
503
504- (NSString *)documentsFilePathForFileName:(NSString *)fileName {
505 NSParameterAssert(fileName.length);
506 NSArray *paths = NSSearchPathForDirectoriesInDomains(
507 NSDocumentDirectory, NSUserDomainMask, YES);
508 NSString *documentsDirPath = paths.firstObject;
509 NSString *filePath =
510 [documentsDirPath stringByAppendingPathComponent:fileName];
511 return filePath;
512}
513
514#endif
515
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000516- (BOOL)hasJoinedRoomServerRoom {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000517 return _clientId.length;
518}
519
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000520// Begins the peer connection connection process if we have both joined a room
521// on the room server and tried to obtain a TURN server. Otherwise does nothing.
522// A peer connection object will be created with a stream that contains local
523// audio and video capture. If this client is the caller, an offer is created as
524// well, otherwise the client will wait for an offer to arrive.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000525- (void)startSignalingIfReady {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000526 if (!_isTurnComplete || !self.hasJoinedRoomServerRoom) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000527 return;
528 }
529 self.state = kARDAppClientStateConnected;
530
531 // Create peer connection.
532 RTCMediaConstraints *constraints = [self defaultPeerConnectionConstraints];
Zeke Chinbc7dd7e2015-05-29 14:59:14 -0700533 RTCConfiguration *config = [[RTCConfiguration alloc] init];
534 config.iceServers = _iceServers;
Tze Kwang Chinf3cb49f2016-03-22 10:57:40 -0700535 _peerConnection = [_factory peerConnectionWithConfiguration:config
536 constraints:constraints
537 delegate:self];
skvladf3569c82016-04-29 15:30:16 -0700538 // Create AV senders.
539 [self createAudioSender];
540 [self createVideoSender];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000541 if (_isInitiator) {
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000542 // Send offer.
hjon79858f82016-03-13 22:08:26 -0700543 __weak ARDAppClient *weakSelf = self;
544 [_peerConnection offerForConstraints:[self defaultOfferConstraints]
545 completionHandler:^(RTCSessionDescription *sdp,
546 NSError *error) {
547 ARDAppClient *strongSelf = weakSelf;
548 [strongSelf peerConnection:strongSelf.peerConnection
549 didCreateSessionDescription:sdp
550 error:error];
551 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000552 } else {
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000553 // Check if we've received an offer.
554 [self drainMessageQueueIfReady];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000555 }
Ivo Creusenc43bf562016-06-30 09:31:52 +0200556#if defined(WEBRTC_IOS)
557 // Start event log.
558 if (kARDAppClientEnableRtcEventLog) {
559 NSString *filePath = [self documentsFilePathForFileName:@"webrtc-rtceventlog"];
560 if (![_peerConnection startRtcEventLogWithFilePath:filePath
561 maxSizeInBytes:kARDAppClientRtcEventLogMaxSizeInBytes]) {
562 RTCLogError(@"Failed to start event logging.");
563 }
564 }
565#endif
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000566}
567
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000568// Processes the messages that we've received from the room server and the
569// signaling channel. The offer or answer message must be processed before other
570// signaling messages, however they can arrive out of order. Hence, this method
571// only processes pending messages if there is a peer connection object and
572// if we have received either an offer or answer.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000573- (void)drainMessageQueueIfReady {
574 if (!_peerConnection || !_hasReceivedSdp) {
575 return;
576 }
577 for (ARDSignalingMessage *message in _messageQueue) {
578 [self processSignalingMessage:message];
579 }
580 [_messageQueue removeAllObjects];
581}
582
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000583// Processes the given signaling message based on its type.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000584- (void)processSignalingMessage:(ARDSignalingMessage *)message {
585 NSParameterAssert(_peerConnection ||
586 message.type == kARDSignalingMessageTypeBye);
587 switch (message.type) {
588 case kARDSignalingMessageTypeOffer:
589 case kARDSignalingMessageTypeAnswer: {
590 ARDSessionDescriptionMessage *sdpMessage =
591 (ARDSessionDescriptionMessage *)message;
592 RTCSessionDescription *description = sdpMessage.sessionDescription;
Zeke Chin71f6f442015-06-29 14:34:58 -0700593 // Prefer H264 if available.
594 RTCSessionDescription *sdpPreferringH264 =
595 [ARDSDPUtils descriptionForDescription:description
596 preferredVideoCodec:@"H264"];
hjon79858f82016-03-13 22:08:26 -0700597 __weak ARDAppClient *weakSelf = self;
598 [_peerConnection setRemoteDescription:sdpPreferringH264
599 completionHandler:^(NSError *error) {
600 ARDAppClient *strongSelf = weakSelf;
601 [strongSelf peerConnection:strongSelf.peerConnection
602 didSetSessionDescriptionWithError:error];
603 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000604 break;
605 }
606 case kARDSignalingMessageTypeCandidate: {
607 ARDICECandidateMessage *candidateMessage =
608 (ARDICECandidateMessage *)message;
hjon79858f82016-03-13 22:08:26 -0700609 [_peerConnection addIceCandidate:candidateMessage.candidate];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000610 break;
611 }
Honghai Zhangda2ba4d2016-05-23 11:53:14 -0700612 case kARDSignalingMessageTypeCandidateRemoval: {
613 ARDICECandidateRemovalMessage *candidateMessage =
614 (ARDICECandidateRemovalMessage *)message;
615 [_peerConnection removeIceCandidates:candidateMessage.candidates];
616 break;
617 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000618 case kARDSignalingMessageTypeBye:
619 // Other client disconnected.
620 // TODO(tkchin): support waiting in room for next client. For now just
621 // disconnect.
622 [self disconnect];
623 break;
624 }
625}
626
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000627// Sends a signaling message to the other client. The caller will send messages
628// through the room server, whereas the callee will send messages over the
629// signaling channel.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000630- (void)sendSignalingMessage:(ARDSignalingMessage *)message {
631 if (_isInitiator) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000632 __weak ARDAppClient *weakSelf = self;
633 [_roomServerClient sendMessage:message
634 forRoomId:_roomId
635 clientId:_clientId
636 completionHandler:^(ARDMessageResponse *response,
637 NSError *error) {
638 ARDAppClient *strongSelf = weakSelf;
639 if (error) {
640 [strongSelf.delegate appClient:strongSelf didError:error];
641 return;
642 }
643 NSError *messageError =
644 [[strongSelf class] errorForMessageResultType:response.result];
645 if (messageError) {
646 [strongSelf.delegate appClient:strongSelf didError:messageError];
647 return;
648 }
649 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000650 } else {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000651 [_channel sendMessage:message];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000652 }
653}
654
skvladf3569c82016-04-29 15:30:16 -0700655- (RTCRtpSender *)createVideoSender {
656 RTCRtpSender *sender =
657 [_peerConnection senderWithKind:kRTCMediaStreamTrackKindVideo
658 streamId:kARDMediaStreamId];
659 RTCVideoTrack *track = [self createLocalVideoTrack];
660 if (track) {
661 sender.track = track;
662 [_delegate appClient:self didReceiveLocalVideoTrack:track];
Zeke Chin57cc74e2015-05-05 07:52:31 -0700663 }
skvladf3569c82016-04-29 15:30:16 -0700664 return sender;
665}
666
667- (RTCRtpSender *)createAudioSender {
668 RTCRtpSender *sender =
669 [_peerConnection senderWithKind:kRTCMediaStreamTrackKindAudio
670 streamId:kARDMediaStreamId];
671 RTCAudioTrack *track = [_factory audioTrackWithTrackId:kARDAudioTrackId];
672 sender.track = track;
673 return sender;
Zeke Chin57cc74e2015-05-05 07:52:31 -0700674}
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000675
Zeke Chin57cc74e2015-05-05 07:52:31 -0700676- (RTCVideoTrack *)createLocalVideoTrack {
677 RTCVideoTrack* localVideoTrack = nil;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000678 // The iOS simulator doesn't provide any sort of camera capture
679 // support or emulation (http://goo.gl/rHAnC1) so don't bother
680 // trying to open a local stream.
681 // TODO(tkchin): local video capture for OSX. See
682 // https://code.google.com/p/webrtc/issues/detail?id=3417.
683#if !TARGET_IPHONE_SIMULATOR && TARGET_OS_IPHONE
haysc913e6452015-10-02 11:44:03 -0700684 if (!_isAudioOnly) {
685 RTCMediaConstraints *mediaConstraints =
686 [self defaultMediaStreamConstraints];
687 RTCAVFoundationVideoSource *source =
Tze Kwang Chinf3cb49f2016-03-22 10:57:40 -0700688 [_factory avFoundationVideoSourceWithConstraints:mediaConstraints];
haysc913e6452015-10-02 11:44:03 -0700689 localVideoTrack =
Tze Kwang Chinf3cb49f2016-03-22 10:57:40 -0700690 [_factory videoTrackWithSource:source
skvladf3569c82016-04-29 15:30:16 -0700691 trackId:kARDVideoTrackId];
haysc913e6452015-10-02 11:44:03 -0700692 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000693#endif
Zeke Chin57cc74e2015-05-05 07:52:31 -0700694 return localVideoTrack;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000695}
696
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000697#pragma mark - Collider methods
698
699- (void)registerWithColliderIfReady {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000700 if (!self.hasJoinedRoomServerRoom) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000701 return;
702 }
703 // Open WebSocket connection.
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000704 if (!_channel) {
705 _channel =
706 [[ARDWebSocketChannel alloc] initWithURL:_websocketURL
707 restURL:_websocketRestURL
708 delegate:self];
haysc913e6452015-10-02 11:44:03 -0700709 if (_isLoopback) {
710 _loopbackChannel =
711 [[ARDLoopbackWebSocketChannel alloc] initWithURL:_websocketURL
712 restURL:_websocketRestURL];
713 }
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000714 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000715 [_channel registerForRoomId:_roomId clientId:_clientId];
haysc913e6452015-10-02 11:44:03 -0700716 if (_isLoopback) {
717 [_loopbackChannel registerForRoomId:_roomId clientId:@"LOOPBACK_CLIENT_ID"];
718 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000719}
720
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000721#pragma mark - Defaults
722
723- (RTCMediaConstraints *)defaultMediaStreamConstraints {
724 RTCMediaConstraints* constraints =
725 [[RTCMediaConstraints alloc]
726 initWithMandatoryConstraints:nil
727 optionalConstraints:nil];
728 return constraints;
729}
730
731- (RTCMediaConstraints *)defaultAnswerConstraints {
732 return [self defaultOfferConstraints];
733}
734
735- (RTCMediaConstraints *)defaultOfferConstraints {
hjon79858f82016-03-13 22:08:26 -0700736 NSDictionary *mandatoryConstraints = @{
737 @"OfferToReceiveAudio" : @"true",
738 @"OfferToReceiveVideo" : @"true"
739 };
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000740 RTCMediaConstraints* constraints =
741 [[RTCMediaConstraints alloc]
742 initWithMandatoryConstraints:mandatoryConstraints
743 optionalConstraints:nil];
744 return constraints;
745}
746
747- (RTCMediaConstraints *)defaultPeerConnectionConstraints {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000748 if (_defaultPeerConnectionConstraints) {
749 return _defaultPeerConnectionConstraints;
750 }
haysc913e6452015-10-02 11:44:03 -0700751 NSString *value = _isLoopback ? @"false" : @"true";
hjon79858f82016-03-13 22:08:26 -0700752 NSDictionary *optionalConstraints = @{ @"DtlsSrtpKeyAgreement" : value };
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000753 RTCMediaConstraints* constraints =
754 [[RTCMediaConstraints alloc]
755 initWithMandatoryConstraints:nil
756 optionalConstraints:optionalConstraints];
757 return constraints;
758}
759
hjon79858f82016-03-13 22:08:26 -0700760- (RTCIceServer *)defaultSTUNServer {
761 return [[RTCIceServer alloc] initWithURLStrings:@[kARDDefaultSTUNServerUrl]
762 username:@""
763 credential:@""];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000764}
765
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000766#pragma mark - Errors
767
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000768+ (NSError *)errorForJoinResultType:(ARDJoinResultType)resultType {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000769 NSError *error = nil;
770 switch (resultType) {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000771 case kARDJoinResultTypeSuccess:
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000772 break;
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000773 case kARDJoinResultTypeUnknown: {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000774 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
775 code:kARDAppClientErrorUnknown
776 userInfo:@{
777 NSLocalizedDescriptionKey: @"Unknown error.",
778 }];
779 break;
780 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000781 case kARDJoinResultTypeFull: {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000782 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
783 code:kARDAppClientErrorRoomFull
784 userInfo:@{
785 NSLocalizedDescriptionKey: @"Room is full.",
786 }];
787 break;
788 }
789 }
790 return error;
791}
792
793+ (NSError *)errorForMessageResultType:(ARDMessageResultType)resultType {
794 NSError *error = nil;
795 switch (resultType) {
796 case kARDMessageResultTypeSuccess:
797 break;
798 case kARDMessageResultTypeUnknown:
799 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
800 code:kARDAppClientErrorUnknown
801 userInfo:@{
802 NSLocalizedDescriptionKey: @"Unknown error.",
803 }];
804 break;
805 case kARDMessageResultTypeInvalidClient:
806 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
807 code:kARDAppClientErrorInvalidClient
808 userInfo:@{
809 NSLocalizedDescriptionKey: @"Invalid client.",
810 }];
811 break;
812 case kARDMessageResultTypeInvalidRoom:
813 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
814 code:kARDAppClientErrorInvalidRoom
815 userInfo:@{
816 NSLocalizedDescriptionKey: @"Invalid room.",
817 }];
818 break;
819 }
820 return error;
821}
822
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000823@end