blob: bcc746050a4dbe95b0fc1888c509d1ad97aa9db7 [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)
14#import "RTCAVFoundationVideoSource.h"
15#endif
Zeke Chin2d3b7e22015-07-14 12:55:44 -070016#import "RTCFileLogger.h"
Zeke Chin57cc74e2015-05-05 07:52:31 -070017#import "RTCICEServer.h"
tkchinc3f46a92015-07-23 12:50:55 -070018#import "RTCLogging.h"
Zeke Chin57cc74e2015-05-05 07:52:31 -070019#import "RTCMediaConstraints.h"
20#import "RTCMediaStream.h"
21#import "RTCPair.h"
Zeke Chinbc7dd7e2015-05-29 14:59:14 -070022#import "RTCPeerConnectionInterface.h"
Zeke Chin57cc74e2015-05-05 07:52:31 -070023#import "RTCVideoCapturer.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000024
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000025#import "ARDAppEngineClient.h"
26#import "ARDCEODTURNClient.h"
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +000027#import "ARDJoinResponse.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000028#import "ARDMessageResponse.h"
Zeke Chin71f6f442015-06-29 14:34:58 -070029#import "ARDSDPUtils.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000030#import "ARDSignalingMessage.h"
31#import "ARDUtilities.h"
32#import "ARDWebSocketChannel.h"
33#import "RTCICECandidate+JSON.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000034#import "RTCSessionDescription+JSON.h"
Zeke Chin57cc74e2015-05-05 07:52:31 -070035
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000036
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +000037static NSString * const kARDDefaultSTUNServerUrl =
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000038 @"stun:stun.l.google.com:19302";
39// TODO(tkchin): figure out a better username for CEOD statistics.
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +000040static NSString * const kARDTurnRequestUrl =
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000041 @"https://computeengineondemand.appspot.com"
42 @"/turn?username=iapprtc&key=4080218913";
43
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +000044static NSString * const kARDAppClientErrorDomain = @"ARDAppClient";
45static NSInteger const kARDAppClientErrorUnknown = -1;
46static NSInteger const kARDAppClientErrorRoomFull = -2;
47static NSInteger const kARDAppClientErrorCreateSDP = -3;
48static NSInteger const kARDAppClientErrorSetSDP = -4;
49static NSInteger const kARDAppClientErrorInvalidClient = -5;
50static NSInteger const kARDAppClientErrorInvalidRoom = -6;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000051
Zeke Chin2d3b7e22015-07-14 12:55:44 -070052@implementation ARDAppClient {
53 RTCFileLogger *_fileLogger;
54}
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000055
56@synthesize delegate = _delegate;
57@synthesize state = _state;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000058@synthesize roomServerClient = _roomServerClient;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000059@synthesize channel = _channel;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000060@synthesize turnClient = _turnClient;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000061@synthesize peerConnection = _peerConnection;
62@synthesize factory = _factory;
63@synthesize messageQueue = _messageQueue;
64@synthesize isTurnComplete = _isTurnComplete;
65@synthesize hasReceivedSdp = _hasReceivedSdp;
66@synthesize roomId = _roomId;
67@synthesize clientId = _clientId;
68@synthesize isInitiator = _isInitiator;
69@synthesize iceServers = _iceServers;
70@synthesize webSocketURL = _websocketURL;
71@synthesize webSocketRestURL = _websocketRestURL;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000072@synthesize defaultPeerConnectionConstraints =
73 _defaultPeerConnectionConstraints;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000074
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +000075- (instancetype)init {
76 if (self = [super init]) {
77 _roomServerClient = [[ARDAppEngineClient alloc] init];
78 NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl];
79 _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL];
80 [self configure];
81 }
82 return self;
83}
84
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000085- (instancetype)initWithDelegate:(id<ARDAppClientDelegate>)delegate {
86 if (self = [super init]) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000087 _roomServerClient = [[ARDAppEngineClient alloc] init];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000088 _delegate = delegate;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000089 NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl];
90 _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL];
91 [self configure];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000092 }
93 return self;
94}
95
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000096// TODO(tkchin): Provide signaling channel factory interface so we can recreate
97// channel if we need to on network failure. Also, make this the default public
98// constructor.
99- (instancetype)initWithRoomServerClient:(id<ARDRoomServerClient>)rsClient
100 signalingChannel:(id<ARDSignalingChannel>)channel
101 turnClient:(id<ARDTURNClient>)turnClient
102 delegate:(id<ARDAppClientDelegate>)delegate {
103 NSParameterAssert(rsClient);
104 NSParameterAssert(channel);
105 NSParameterAssert(turnClient);
106 if (self = [super init]) {
107 _roomServerClient = rsClient;
108 _channel = channel;
109 _turnClient = turnClient;
110 _delegate = delegate;
111 [self configure];
112 }
113 return self;
114}
115
116- (void)configure {
117 _factory = [[RTCPeerConnectionFactory alloc] init];
118 _messageQueue = [NSMutableArray array];
119 _iceServers = [NSMutableArray arrayWithObject:[self defaultSTUNServer]];
Zeke Chin2d3b7e22015-07-14 12:55:44 -0700120 _fileLogger = [[RTCFileLogger alloc] init];
121 [_fileLogger start];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000122}
123
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000124- (void)dealloc {
125 [self disconnect];
126}
127
128- (void)setState:(ARDAppClientState)state {
129 if (_state == state) {
130 return;
131 }
132 _state = state;
133 [_delegate appClient:self didChangeState:_state];
134}
135
136- (void)connectToRoomWithId:(NSString *)roomId
137 options:(NSDictionary *)options {
138 NSParameterAssert(roomId.length);
139 NSParameterAssert(_state == kARDAppClientStateDisconnected);
140 self.state = kARDAppClientStateConnecting;
141
142 // Request TURN.
143 __weak ARDAppClient *weakSelf = self;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000144 [_turnClient requestServersWithCompletionHandler:^(NSArray *turnServers,
145 NSError *error) {
146 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700147 RTCLogError("Error retrieving TURN servers: %@",
148 error.localizedDescription);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000149 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000150 ARDAppClient *strongSelf = weakSelf;
151 [strongSelf.iceServers addObjectsFromArray:turnServers];
152 strongSelf.isTurnComplete = YES;
153 [strongSelf startSignalingIfReady];
154 }];
jiayl@webrtc.org27f53172014-12-31 00:26:20 +0000155
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000156 // Join room on room server.
157 [_roomServerClient joinRoomWithRoomId:roomId
158 completionHandler:^(ARDJoinResponse *response, NSError *error) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000159 ARDAppClient *strongSelf = weakSelf;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000160 if (error) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000161 [strongSelf.delegate appClient:strongSelf didError:error];
162 return;
163 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000164 NSError *joinError =
165 [[strongSelf class] errorForJoinResultType:response.result];
166 if (joinError) {
tkchinc3f46a92015-07-23 12:50:55 -0700167 RTCLogError(@"Failed to join room:%@ on room server.", roomId);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000168 [strongSelf disconnect];
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000169 [strongSelf.delegate appClient:strongSelf didError:joinError];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000170 return;
171 }
tkchinc3f46a92015-07-23 12:50:55 -0700172 RTCLog(@"Joined room:%@ on room server.", roomId);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000173 strongSelf.roomId = response.roomId;
174 strongSelf.clientId = response.clientId;
175 strongSelf.isInitiator = response.isInitiator;
176 for (ARDSignalingMessage *message in response.messages) {
177 if (message.type == kARDSignalingMessageTypeOffer ||
178 message.type == kARDSignalingMessageTypeAnswer) {
179 strongSelf.hasReceivedSdp = YES;
180 [strongSelf.messageQueue insertObject:message atIndex:0];
181 } else {
182 [strongSelf.messageQueue addObject:message];
183 }
184 }
185 strongSelf.webSocketURL = response.webSocketURL;
186 strongSelf.webSocketRestURL = response.webSocketRestURL;
187 [strongSelf registerWithColliderIfReady];
188 [strongSelf startSignalingIfReady];
189 }];
190}
191
192- (void)disconnect {
193 if (_state == kARDAppClientStateDisconnected) {
194 return;
195 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000196 if (self.hasJoinedRoomServerRoom) {
197 [_roomServerClient leaveRoomWithRoomId:_roomId
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000198 clientId:_clientId
199 completionHandler:nil];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000200 }
201 if (_channel) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000202 if (_channel.state == kARDSignalingChannelStateRegistered) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000203 // Tell the other client we're hanging up.
204 ARDByeMessage *byeMessage = [[ARDByeMessage alloc] init];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000205 [_channel sendMessage:byeMessage];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000206 }
207 // Disconnect from collider.
208 _channel = nil;
209 }
210 _clientId = nil;
211 _roomId = nil;
212 _isInitiator = NO;
213 _hasReceivedSdp = NO;
214 _messageQueue = [NSMutableArray array];
215 _peerConnection = nil;
216 self.state = kARDAppClientStateDisconnected;
217}
218
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000219#pragma mark - ARDSignalingChannelDelegate
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000220
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000221- (void)channel:(id<ARDSignalingChannel>)channel
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000222 didReceiveMessage:(ARDSignalingMessage *)message {
223 switch (message.type) {
224 case kARDSignalingMessageTypeOffer:
225 case kARDSignalingMessageTypeAnswer:
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000226 // Offers and answers must be processed before any other message, so we
227 // place them at the front of the queue.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000228 _hasReceivedSdp = YES;
229 [_messageQueue insertObject:message atIndex:0];
230 break;
231 case kARDSignalingMessageTypeCandidate:
232 [_messageQueue addObject:message];
233 break;
234 case kARDSignalingMessageTypeBye:
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000235 // Disconnects can be processed immediately.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000236 [self processSignalingMessage:message];
237 return;
238 }
239 [self drainMessageQueueIfReady];
240}
241
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000242- (void)channel:(id<ARDSignalingChannel>)channel
243 didChangeState:(ARDSignalingChannelState)state {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000244 switch (state) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000245 case kARDSignalingChannelStateOpen:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000246 break;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000247 case kARDSignalingChannelStateRegistered:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000248 break;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000249 case kARDSignalingChannelStateClosed:
250 case kARDSignalingChannelStateError:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000251 // TODO(tkchin): reconnection scenarios. Right now we just disconnect
252 // completely if the websocket connection fails.
253 [self disconnect];
254 break;
255 }
256}
257
258#pragma mark - RTCPeerConnectionDelegate
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000259// Callbacks for this delegate occur on non-main thread and need to be
260// dispatched back to main queue as needed.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000261
262- (void)peerConnection:(RTCPeerConnection *)peerConnection
263 signalingStateChanged:(RTCSignalingState)stateChanged {
tkchinc3f46a92015-07-23 12:50:55 -0700264 RTCLog(@"Signaling state changed: %d", stateChanged);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000265}
266
267- (void)peerConnection:(RTCPeerConnection *)peerConnection
268 addedStream:(RTCMediaStream *)stream {
269 dispatch_async(dispatch_get_main_queue(), ^{
tkchinc3f46a92015-07-23 12:50:55 -0700270 RTCLog(@"Received %lu video tracks and %lu audio tracks",
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000271 (unsigned long)stream.videoTracks.count,
272 (unsigned long)stream.audioTracks.count);
273 if (stream.videoTracks.count) {
274 RTCVideoTrack *videoTrack = stream.videoTracks[0];
275 [_delegate appClient:self didReceiveRemoteVideoTrack:videoTrack];
276 }
277 });
278}
279
280- (void)peerConnection:(RTCPeerConnection *)peerConnection
281 removedStream:(RTCMediaStream *)stream {
tkchinc3f46a92015-07-23 12:50:55 -0700282 RTCLog(@"Stream was removed.");
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000283}
284
285- (void)peerConnectionOnRenegotiationNeeded:
286 (RTCPeerConnection *)peerConnection {
tkchinc3f46a92015-07-23 12:50:55 -0700287 RTCLog(@"WARNING: Renegotiation needed but unimplemented.");
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000288}
289
290- (void)peerConnection:(RTCPeerConnection *)peerConnection
291 iceConnectionChanged:(RTCICEConnectionState)newState {
tkchinc3f46a92015-07-23 12:50:55 -0700292 RTCLog(@"ICE state changed: %d", newState);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000293 dispatch_async(dispatch_get_main_queue(), ^{
294 [_delegate appClient:self didChangeConnectionState:newState];
295 });
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000296}
297
298- (void)peerConnection:(RTCPeerConnection *)peerConnection
299 iceGatheringChanged:(RTCICEGatheringState)newState {
tkchinc3f46a92015-07-23 12:50:55 -0700300 RTCLog(@"ICE gathering state changed: %d", newState);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000301}
302
303- (void)peerConnection:(RTCPeerConnection *)peerConnection
304 gotICECandidate:(RTCICECandidate *)candidate {
305 dispatch_async(dispatch_get_main_queue(), ^{
306 ARDICECandidateMessage *message =
307 [[ARDICECandidateMessage alloc] initWithCandidate:candidate];
308 [self sendSignalingMessage:message];
309 });
310}
311
312- (void)peerConnection:(RTCPeerConnection*)peerConnection
313 didOpenDataChannel:(RTCDataChannel*)dataChannel {
314}
315
316#pragma mark - RTCSessionDescriptionDelegate
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000317// Callbacks for this delegate occur on non-main thread and need to be
318// dispatched back to main queue as needed.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000319
320- (void)peerConnection:(RTCPeerConnection *)peerConnection
321 didCreateSessionDescription:(RTCSessionDescription *)sdp
322 error:(NSError *)error {
323 dispatch_async(dispatch_get_main_queue(), ^{
324 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700325 RTCLogError(@"Failed to create session description. Error: %@", error);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000326 [self disconnect];
327 NSDictionary *userInfo = @{
328 NSLocalizedDescriptionKey: @"Failed to create session description.",
329 };
330 NSError *sdpError =
331 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
332 code:kARDAppClientErrorCreateSDP
333 userInfo:userInfo];
334 [_delegate appClient:self didError:sdpError];
335 return;
336 }
Zeke Chin71f6f442015-06-29 14:34:58 -0700337 // Prefer H264 if available.
338 RTCSessionDescription *sdpPreferringH264 =
339 [ARDSDPUtils descriptionForDescription:sdp
340 preferredVideoCodec:@"H264"];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000341 [_peerConnection setLocalDescriptionWithDelegate:self
Zeke Chin71f6f442015-06-29 14:34:58 -0700342 sessionDescription:sdpPreferringH264];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000343 ARDSessionDescriptionMessage *message =
Zeke Chin71f6f442015-06-29 14:34:58 -0700344 [[ARDSessionDescriptionMessage alloc]
345 initWithDescription:sdpPreferringH264];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000346 [self sendSignalingMessage:message];
347 });
348}
349
350- (void)peerConnection:(RTCPeerConnection *)peerConnection
351 didSetSessionDescriptionWithError:(NSError *)error {
352 dispatch_async(dispatch_get_main_queue(), ^{
353 if (error) {
tkchinc3f46a92015-07-23 12:50:55 -0700354 RTCLogError(@"Failed to set session description. Error: %@", error);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000355 [self disconnect];
356 NSDictionary *userInfo = @{
357 NSLocalizedDescriptionKey: @"Failed to set session description.",
358 };
359 NSError *sdpError =
360 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
361 code:kARDAppClientErrorSetSDP
362 userInfo:userInfo];
363 [_delegate appClient:self didError:sdpError];
364 return;
365 }
366 // If we're answering and we've just set the remote offer we need to create
367 // an answer and set the local description.
368 if (!_isInitiator && !_peerConnection.localDescription) {
369 RTCMediaConstraints *constraints = [self defaultAnswerConstraints];
370 [_peerConnection createAnswerWithDelegate:self
371 constraints:constraints];
372
373 }
374 });
375}
376
377#pragma mark - Private
378
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000379- (BOOL)hasJoinedRoomServerRoom {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000380 return _clientId.length;
381}
382
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000383// Begins the peer connection connection process if we have both joined a room
384// on the room server and tried to obtain a TURN server. Otherwise does nothing.
385// A peer connection object will be created with a stream that contains local
386// audio and video capture. If this client is the caller, an offer is created as
387// well, otherwise the client will wait for an offer to arrive.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000388- (void)startSignalingIfReady {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000389 if (!_isTurnComplete || !self.hasJoinedRoomServerRoom) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000390 return;
391 }
392 self.state = kARDAppClientStateConnected;
393
394 // Create peer connection.
395 RTCMediaConstraints *constraints = [self defaultPeerConnectionConstraints];
Zeke Chinbc7dd7e2015-05-29 14:59:14 -0700396 RTCConfiguration *config = [[RTCConfiguration alloc] init];
397 config.iceServers = _iceServers;
398 _peerConnection = [_factory peerConnectionWithConfiguration:config
399 constraints:constraints
400 delegate:self];
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000401 // Create AV media stream and add it to the peer connection.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000402 RTCMediaStream *localStream = [self createLocalMediaStream];
403 [_peerConnection addStream:localStream];
404 if (_isInitiator) {
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000405 // Send offer.
406 [_peerConnection createOfferWithDelegate:self
407 constraints:[self defaultOfferConstraints]];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000408 } else {
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000409 // Check if we've received an offer.
410 [self drainMessageQueueIfReady];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000411 }
412}
413
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000414// Processes the messages that we've received from the room server and the
415// signaling channel. The offer or answer message must be processed before other
416// signaling messages, however they can arrive out of order. Hence, this method
417// only processes pending messages if there is a peer connection object and
418// if we have received either an offer or answer.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000419- (void)drainMessageQueueIfReady {
420 if (!_peerConnection || !_hasReceivedSdp) {
421 return;
422 }
423 for (ARDSignalingMessage *message in _messageQueue) {
424 [self processSignalingMessage:message];
425 }
426 [_messageQueue removeAllObjects];
427}
428
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000429// Processes the given signaling message based on its type.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000430- (void)processSignalingMessage:(ARDSignalingMessage *)message {
431 NSParameterAssert(_peerConnection ||
432 message.type == kARDSignalingMessageTypeBye);
433 switch (message.type) {
434 case kARDSignalingMessageTypeOffer:
435 case kARDSignalingMessageTypeAnswer: {
436 ARDSessionDescriptionMessage *sdpMessage =
437 (ARDSessionDescriptionMessage *)message;
438 RTCSessionDescription *description = sdpMessage.sessionDescription;
Zeke Chin71f6f442015-06-29 14:34:58 -0700439 // Prefer H264 if available.
440 RTCSessionDescription *sdpPreferringH264 =
441 [ARDSDPUtils descriptionForDescription:description
442 preferredVideoCodec:@"H264"];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000443 [_peerConnection setRemoteDescriptionWithDelegate:self
Zeke Chin71f6f442015-06-29 14:34:58 -0700444 sessionDescription:sdpPreferringH264];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000445 break;
446 }
447 case kARDSignalingMessageTypeCandidate: {
448 ARDICECandidateMessage *candidateMessage =
449 (ARDICECandidateMessage *)message;
450 [_peerConnection addICECandidate:candidateMessage.candidate];
451 break;
452 }
453 case kARDSignalingMessageTypeBye:
454 // Other client disconnected.
455 // TODO(tkchin): support waiting in room for next client. For now just
456 // disconnect.
457 [self disconnect];
458 break;
459 }
460}
461
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000462// Sends a signaling message to the other client. The caller will send messages
463// through the room server, whereas the callee will send messages over the
464// signaling channel.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000465- (void)sendSignalingMessage:(ARDSignalingMessage *)message {
466 if (_isInitiator) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000467 __weak ARDAppClient *weakSelf = self;
468 [_roomServerClient sendMessage:message
469 forRoomId:_roomId
470 clientId:_clientId
471 completionHandler:^(ARDMessageResponse *response,
472 NSError *error) {
473 ARDAppClient *strongSelf = weakSelf;
474 if (error) {
475 [strongSelf.delegate appClient:strongSelf didError:error];
476 return;
477 }
478 NSError *messageError =
479 [[strongSelf class] errorForMessageResultType:response.result];
480 if (messageError) {
481 [strongSelf.delegate appClient:strongSelf didError:messageError];
482 return;
483 }
484 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000485 } else {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000486 [_channel sendMessage:message];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000487 }
488}
489
490- (RTCMediaStream *)createLocalMediaStream {
491 RTCMediaStream* localStream = [_factory mediaStreamWithLabel:@"ARDAMS"];
Zeke Chin57cc74e2015-05-05 07:52:31 -0700492 RTCVideoTrack* localVideoTrack = [self createLocalVideoTrack];
493 if (localVideoTrack) {
494 [localStream addVideoTrack:localVideoTrack];
495 [_delegate appClient:self didReceiveLocalVideoTrack:localVideoTrack];
496 }
497 [localStream addAudioTrack:[_factory audioTrackWithID:@"ARDAMSa0"]];
498 return localStream;
499}
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000500
Zeke Chin57cc74e2015-05-05 07:52:31 -0700501- (RTCVideoTrack *)createLocalVideoTrack {
502 RTCVideoTrack* localVideoTrack = nil;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000503 // The iOS simulator doesn't provide any sort of camera capture
504 // support or emulation (http://goo.gl/rHAnC1) so don't bother
505 // trying to open a local stream.
506 // TODO(tkchin): local video capture for OSX. See
507 // https://code.google.com/p/webrtc/issues/detail?id=3417.
508#if !TARGET_IPHONE_SIMULATOR && TARGET_OS_IPHONE
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000509 RTCMediaConstraints *mediaConstraints = [self defaultMediaStreamConstraints];
Zeke Chin57cc74e2015-05-05 07:52:31 -0700510 RTCAVFoundationVideoSource *source =
511 [[RTCAVFoundationVideoSource alloc] initWithFactory:_factory
512 constraints:mediaConstraints];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000513 localVideoTrack =
Zeke Chin57cc74e2015-05-05 07:52:31 -0700514 [[RTCVideoTrack alloc] initWithFactory:_factory
515 source:source
516 trackId:@"ARDAMSv0"];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000517#endif
Zeke Chin57cc74e2015-05-05 07:52:31 -0700518 return localVideoTrack;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000519}
520
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000521#pragma mark - Collider methods
522
523- (void)registerWithColliderIfReady {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000524 if (!self.hasJoinedRoomServerRoom) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000525 return;
526 }
527 // Open WebSocket connection.
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000528 if (!_channel) {
529 _channel =
530 [[ARDWebSocketChannel alloc] initWithURL:_websocketURL
531 restURL:_websocketRestURL
532 delegate:self];
533 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000534 [_channel registerForRoomId:_roomId clientId:_clientId];
535}
536
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000537#pragma mark - Defaults
538
539- (RTCMediaConstraints *)defaultMediaStreamConstraints {
540 RTCMediaConstraints* constraints =
541 [[RTCMediaConstraints alloc]
542 initWithMandatoryConstraints:nil
543 optionalConstraints:nil];
544 return constraints;
545}
546
547- (RTCMediaConstraints *)defaultAnswerConstraints {
548 return [self defaultOfferConstraints];
549}
550
551- (RTCMediaConstraints *)defaultOfferConstraints {
552 NSArray *mandatoryConstraints = @[
553 [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" value:@"true"],
554 [[RTCPair alloc] initWithKey:@"OfferToReceiveVideo" value:@"true"]
555 ];
556 RTCMediaConstraints* constraints =
557 [[RTCMediaConstraints alloc]
558 initWithMandatoryConstraints:mandatoryConstraints
559 optionalConstraints:nil];
560 return constraints;
561}
562
563- (RTCMediaConstraints *)defaultPeerConnectionConstraints {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000564 if (_defaultPeerConnectionConstraints) {
565 return _defaultPeerConnectionConstraints;
566 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000567 NSArray *optionalConstraints = @[
568 [[RTCPair alloc] initWithKey:@"DtlsSrtpKeyAgreement" value:@"true"]
569 ];
570 RTCMediaConstraints* constraints =
571 [[RTCMediaConstraints alloc]
572 initWithMandatoryConstraints:nil
573 optionalConstraints:optionalConstraints];
574 return constraints;
575}
576
577- (RTCICEServer *)defaultSTUNServer {
578 NSURL *defaultSTUNServerURL = [NSURL URLWithString:kARDDefaultSTUNServerUrl];
579 return [[RTCICEServer alloc] initWithURI:defaultSTUNServerURL
580 username:@""
581 password:@""];
582}
583
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000584#pragma mark - Errors
585
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000586+ (NSError *)errorForJoinResultType:(ARDJoinResultType)resultType {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000587 NSError *error = nil;
588 switch (resultType) {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000589 case kARDJoinResultTypeSuccess:
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000590 break;
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000591 case kARDJoinResultTypeUnknown: {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000592 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
593 code:kARDAppClientErrorUnknown
594 userInfo:@{
595 NSLocalizedDescriptionKey: @"Unknown error.",
596 }];
597 break;
598 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000599 case kARDJoinResultTypeFull: {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000600 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
601 code:kARDAppClientErrorRoomFull
602 userInfo:@{
603 NSLocalizedDescriptionKey: @"Room is full.",
604 }];
605 break;
606 }
607 }
608 return error;
609}
610
611+ (NSError *)errorForMessageResultType:(ARDMessageResultType)resultType {
612 NSError *error = nil;
613 switch (resultType) {
614 case kARDMessageResultTypeSuccess:
615 break;
616 case kARDMessageResultTypeUnknown:
617 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
618 code:kARDAppClientErrorUnknown
619 userInfo:@{
620 NSLocalizedDescriptionKey: @"Unknown error.",
621 }];
622 break;
623 case kARDMessageResultTypeInvalidClient:
624 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
625 code:kARDAppClientErrorInvalidClient
626 userInfo:@{
627 NSLocalizedDescriptionKey: @"Invalid client.",
628 }];
629 break;
630 case kARDMessageResultTypeInvalidRoom:
631 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
632 code:kARDAppClientErrorInvalidRoom
633 userInfo:@{
634 NSLocalizedDescriptionKey: @"Invalid room.",
635 }];
636 break;
637 }
638 return error;
639}
640
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000641@end