blob: 5b905c6e65f80f1af56c37f1b1ce80a794a7cd04 [file] [log] [blame]
tkchin@webrtc.org87776a82014-12-09 19:32:35 +00001/*
2 * libjingle
jlmiller@webrtc.org5f93d0a2015-01-20 21:36:13 +00003 * Copyright 2014 Google Inc.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +00004 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000028#import "ARDAppClient+Internal.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000029
Zeke Chin57cc74e2015-05-05 07:52:31 -070030#if defined(WEBRTC_IOS)
31#import "RTCAVFoundationVideoSource.h"
32#endif
Zeke Chin2d3b7e22015-07-14 12:55:44 -070033#import "RTCFileLogger.h"
Zeke Chin57cc74e2015-05-05 07:52:31 -070034#import "RTCICEServer.h"
35#import "RTCMediaConstraints.h"
36#import "RTCMediaStream.h"
37#import "RTCPair.h"
Zeke Chinbc7dd7e2015-05-29 14:59:14 -070038#import "RTCPeerConnectionInterface.h"
Zeke Chin57cc74e2015-05-05 07:52:31 -070039#import "RTCVideoCapturer.h"
40#import "RTCAVFoundationVideoSource.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000041
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000042#import "ARDAppEngineClient.h"
43#import "ARDCEODTURNClient.h"
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +000044#import "ARDJoinResponse.h"
Zeke Chin2d3b7e22015-07-14 12:55:44 -070045#import "ARDLogging.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000046#import "ARDMessageResponse.h"
Zeke Chin71f6f442015-06-29 14:34:58 -070047#import "ARDSDPUtils.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000048#import "ARDSignalingMessage.h"
49#import "ARDUtilities.h"
50#import "ARDWebSocketChannel.h"
51#import "RTCICECandidate+JSON.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000052#import "RTCSessionDescription+JSON.h"
Zeke Chin57cc74e2015-05-05 07:52:31 -070053
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000054
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +000055static NSString * const kARDDefaultSTUNServerUrl =
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000056 @"stun:stun.l.google.com:19302";
57// TODO(tkchin): figure out a better username for CEOD statistics.
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +000058static NSString * const kARDTurnRequestUrl =
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000059 @"https://computeengineondemand.appspot.com"
60 @"/turn?username=iapprtc&key=4080218913";
61
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +000062static NSString * const kARDAppClientErrorDomain = @"ARDAppClient";
63static NSInteger const kARDAppClientErrorUnknown = -1;
64static NSInteger const kARDAppClientErrorRoomFull = -2;
65static NSInteger const kARDAppClientErrorCreateSDP = -3;
66static NSInteger const kARDAppClientErrorSetSDP = -4;
67static NSInteger const kARDAppClientErrorInvalidClient = -5;
68static NSInteger const kARDAppClientErrorInvalidRoom = -6;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000069
Zeke Chin2d3b7e22015-07-14 12:55:44 -070070@implementation ARDAppClient {
71 RTCFileLogger *_fileLogger;
72}
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000073
74@synthesize delegate = _delegate;
75@synthesize state = _state;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000076@synthesize roomServerClient = _roomServerClient;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000077@synthesize channel = _channel;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000078@synthesize turnClient = _turnClient;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000079@synthesize peerConnection = _peerConnection;
80@synthesize factory = _factory;
81@synthesize messageQueue = _messageQueue;
82@synthesize isTurnComplete = _isTurnComplete;
83@synthesize hasReceivedSdp = _hasReceivedSdp;
84@synthesize roomId = _roomId;
85@synthesize clientId = _clientId;
86@synthesize isInitiator = _isInitiator;
87@synthesize iceServers = _iceServers;
88@synthesize webSocketURL = _websocketURL;
89@synthesize webSocketRestURL = _websocketRestURL;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000090@synthesize defaultPeerConnectionConstraints =
91 _defaultPeerConnectionConstraints;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000092
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +000093- (instancetype)init {
94 if (self = [super init]) {
95 _roomServerClient = [[ARDAppEngineClient alloc] init];
96 NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl];
97 _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL];
98 [self configure];
99 }
100 return self;
101}
102
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000103- (instancetype)initWithDelegate:(id<ARDAppClientDelegate>)delegate {
104 if (self = [super init]) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000105 _roomServerClient = [[ARDAppEngineClient alloc] init];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000106 _delegate = delegate;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000107 NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl];
108 _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL];
109 [self configure];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000110 }
111 return self;
112}
113
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000114// TODO(tkchin): Provide signaling channel factory interface so we can recreate
115// channel if we need to on network failure. Also, make this the default public
116// constructor.
117- (instancetype)initWithRoomServerClient:(id<ARDRoomServerClient>)rsClient
118 signalingChannel:(id<ARDSignalingChannel>)channel
119 turnClient:(id<ARDTURNClient>)turnClient
120 delegate:(id<ARDAppClientDelegate>)delegate {
121 NSParameterAssert(rsClient);
122 NSParameterAssert(channel);
123 NSParameterAssert(turnClient);
124 if (self = [super init]) {
125 _roomServerClient = rsClient;
126 _channel = channel;
127 _turnClient = turnClient;
128 _delegate = delegate;
129 [self configure];
130 }
131 return self;
132}
133
134- (void)configure {
135 _factory = [[RTCPeerConnectionFactory alloc] init];
136 _messageQueue = [NSMutableArray array];
137 _iceServers = [NSMutableArray arrayWithObject:[self defaultSTUNServer]];
Zeke Chin2d3b7e22015-07-14 12:55:44 -0700138 _fileLogger = [[RTCFileLogger alloc] init];
139 [_fileLogger start];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000140}
141
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000142- (void)dealloc {
143 [self disconnect];
144}
145
146- (void)setState:(ARDAppClientState)state {
147 if (_state == state) {
148 return;
149 }
150 _state = state;
151 [_delegate appClient:self didChangeState:_state];
152}
153
154- (void)connectToRoomWithId:(NSString *)roomId
155 options:(NSDictionary *)options {
156 NSParameterAssert(roomId.length);
157 NSParameterAssert(_state == kARDAppClientStateDisconnected);
158 self.state = kARDAppClientStateConnecting;
159
160 // Request TURN.
161 __weak ARDAppClient *weakSelf = self;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000162 [_turnClient requestServersWithCompletionHandler:^(NSArray *turnServers,
163 NSError *error) {
164 if (error) {
Zeke Chin2d3b7e22015-07-14 12:55:44 -0700165 ARDLog("Error retrieving TURN servers: %@", error.localizedDescription);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000166 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000167 ARDAppClient *strongSelf = weakSelf;
168 [strongSelf.iceServers addObjectsFromArray:turnServers];
169 strongSelf.isTurnComplete = YES;
170 [strongSelf startSignalingIfReady];
171 }];
jiayl@webrtc.org27f53172014-12-31 00:26:20 +0000172
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000173 // Join room on room server.
174 [_roomServerClient joinRoomWithRoomId:roomId
175 completionHandler:^(ARDJoinResponse *response, NSError *error) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000176 ARDAppClient *strongSelf = weakSelf;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000177 if (error) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000178 [strongSelf.delegate appClient:strongSelf didError:error];
179 return;
180 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000181 NSError *joinError =
182 [[strongSelf class] errorForJoinResultType:response.result];
183 if (joinError) {
Zeke Chin2d3b7e22015-07-14 12:55:44 -0700184 ARDLog(@"Failed to join room:%@ on room server.", roomId);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000185 [strongSelf disconnect];
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000186 [strongSelf.delegate appClient:strongSelf didError:joinError];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000187 return;
188 }
Zeke Chin2d3b7e22015-07-14 12:55:44 -0700189 ARDLog(@"Joined room:%@ on room server.", roomId);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000190 strongSelf.roomId = response.roomId;
191 strongSelf.clientId = response.clientId;
192 strongSelf.isInitiator = response.isInitiator;
193 for (ARDSignalingMessage *message in response.messages) {
194 if (message.type == kARDSignalingMessageTypeOffer ||
195 message.type == kARDSignalingMessageTypeAnswer) {
196 strongSelf.hasReceivedSdp = YES;
197 [strongSelf.messageQueue insertObject:message atIndex:0];
198 } else {
199 [strongSelf.messageQueue addObject:message];
200 }
201 }
202 strongSelf.webSocketURL = response.webSocketURL;
203 strongSelf.webSocketRestURL = response.webSocketRestURL;
204 [strongSelf registerWithColliderIfReady];
205 [strongSelf startSignalingIfReady];
206 }];
207}
208
209- (void)disconnect {
210 if (_state == kARDAppClientStateDisconnected) {
211 return;
212 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000213 if (self.hasJoinedRoomServerRoom) {
214 [_roomServerClient leaveRoomWithRoomId:_roomId
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000215 clientId:_clientId
216 completionHandler:nil];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000217 }
218 if (_channel) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000219 if (_channel.state == kARDSignalingChannelStateRegistered) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000220 // Tell the other client we're hanging up.
221 ARDByeMessage *byeMessage = [[ARDByeMessage alloc] init];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000222 [_channel sendMessage:byeMessage];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000223 }
224 // Disconnect from collider.
225 _channel = nil;
226 }
227 _clientId = nil;
228 _roomId = nil;
229 _isInitiator = NO;
230 _hasReceivedSdp = NO;
231 _messageQueue = [NSMutableArray array];
232 _peerConnection = nil;
233 self.state = kARDAppClientStateDisconnected;
234}
235
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000236#pragma mark - ARDSignalingChannelDelegate
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000237
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000238- (void)channel:(id<ARDSignalingChannel>)channel
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000239 didReceiveMessage:(ARDSignalingMessage *)message {
240 switch (message.type) {
241 case kARDSignalingMessageTypeOffer:
242 case kARDSignalingMessageTypeAnswer:
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000243 // Offers and answers must be processed before any other message, so we
244 // place them at the front of the queue.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000245 _hasReceivedSdp = YES;
246 [_messageQueue insertObject:message atIndex:0];
247 break;
248 case kARDSignalingMessageTypeCandidate:
249 [_messageQueue addObject:message];
250 break;
251 case kARDSignalingMessageTypeBye:
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000252 // Disconnects can be processed immediately.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000253 [self processSignalingMessage:message];
254 return;
255 }
256 [self drainMessageQueueIfReady];
257}
258
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000259- (void)channel:(id<ARDSignalingChannel>)channel
260 didChangeState:(ARDSignalingChannelState)state {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000261 switch (state) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000262 case kARDSignalingChannelStateOpen:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000263 break;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000264 case kARDSignalingChannelStateRegistered:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000265 break;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000266 case kARDSignalingChannelStateClosed:
267 case kARDSignalingChannelStateError:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000268 // TODO(tkchin): reconnection scenarios. Right now we just disconnect
269 // completely if the websocket connection fails.
270 [self disconnect];
271 break;
272 }
273}
274
275#pragma mark - RTCPeerConnectionDelegate
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000276// Callbacks for this delegate occur on non-main thread and need to be
277// dispatched back to main queue as needed.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000278
279- (void)peerConnection:(RTCPeerConnection *)peerConnection
280 signalingStateChanged:(RTCSignalingState)stateChanged {
Zeke Chin2d3b7e22015-07-14 12:55:44 -0700281 ARDLog(@"Signaling state changed: %d", stateChanged);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000282}
283
284- (void)peerConnection:(RTCPeerConnection *)peerConnection
285 addedStream:(RTCMediaStream *)stream {
286 dispatch_async(dispatch_get_main_queue(), ^{
Zeke Chin2d3b7e22015-07-14 12:55:44 -0700287 ARDLog(@"Received %lu video tracks and %lu audio tracks",
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000288 (unsigned long)stream.videoTracks.count,
289 (unsigned long)stream.audioTracks.count);
290 if (stream.videoTracks.count) {
291 RTCVideoTrack *videoTrack = stream.videoTracks[0];
292 [_delegate appClient:self didReceiveRemoteVideoTrack:videoTrack];
293 }
294 });
295}
296
297- (void)peerConnection:(RTCPeerConnection *)peerConnection
298 removedStream:(RTCMediaStream *)stream {
Zeke Chin2d3b7e22015-07-14 12:55:44 -0700299 ARDLog(@"Stream was removed.");
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000300}
301
302- (void)peerConnectionOnRenegotiationNeeded:
303 (RTCPeerConnection *)peerConnection {
Zeke Chin2d3b7e22015-07-14 12:55:44 -0700304 ARDLog(@"WARNING: Renegotiation needed but unimplemented.");
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000305}
306
307- (void)peerConnection:(RTCPeerConnection *)peerConnection
308 iceConnectionChanged:(RTCICEConnectionState)newState {
Zeke Chin2d3b7e22015-07-14 12:55:44 -0700309 ARDLog(@"ICE state changed: %d", newState);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000310 dispatch_async(dispatch_get_main_queue(), ^{
311 [_delegate appClient:self didChangeConnectionState:newState];
312 });
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000313}
314
315- (void)peerConnection:(RTCPeerConnection *)peerConnection
316 iceGatheringChanged:(RTCICEGatheringState)newState {
Zeke Chin2d3b7e22015-07-14 12:55:44 -0700317 ARDLog(@"ICE gathering state changed: %d", newState);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000318}
319
320- (void)peerConnection:(RTCPeerConnection *)peerConnection
321 gotICECandidate:(RTCICECandidate *)candidate {
322 dispatch_async(dispatch_get_main_queue(), ^{
323 ARDICECandidateMessage *message =
324 [[ARDICECandidateMessage alloc] initWithCandidate:candidate];
325 [self sendSignalingMessage:message];
326 });
327}
328
329- (void)peerConnection:(RTCPeerConnection*)peerConnection
330 didOpenDataChannel:(RTCDataChannel*)dataChannel {
331}
332
333#pragma mark - RTCSessionDescriptionDelegate
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000334// Callbacks for this delegate occur on non-main thread and need to be
335// dispatched back to main queue as needed.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000336
337- (void)peerConnection:(RTCPeerConnection *)peerConnection
338 didCreateSessionDescription:(RTCSessionDescription *)sdp
339 error:(NSError *)error {
340 dispatch_async(dispatch_get_main_queue(), ^{
341 if (error) {
Zeke Chin2d3b7e22015-07-14 12:55:44 -0700342 ARDLog(@"Failed to create session description. Error: %@", error);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000343 [self disconnect];
344 NSDictionary *userInfo = @{
345 NSLocalizedDescriptionKey: @"Failed to create session description.",
346 };
347 NSError *sdpError =
348 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
349 code:kARDAppClientErrorCreateSDP
350 userInfo:userInfo];
351 [_delegate appClient:self didError:sdpError];
352 return;
353 }
Zeke Chin71f6f442015-06-29 14:34:58 -0700354 // Prefer H264 if available.
355 RTCSessionDescription *sdpPreferringH264 =
356 [ARDSDPUtils descriptionForDescription:sdp
357 preferredVideoCodec:@"H264"];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000358 [_peerConnection setLocalDescriptionWithDelegate:self
Zeke Chin71f6f442015-06-29 14:34:58 -0700359 sessionDescription:sdpPreferringH264];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000360 ARDSessionDescriptionMessage *message =
Zeke Chin71f6f442015-06-29 14:34:58 -0700361 [[ARDSessionDescriptionMessage alloc]
362 initWithDescription:sdpPreferringH264];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000363 [self sendSignalingMessage:message];
364 });
365}
366
367- (void)peerConnection:(RTCPeerConnection *)peerConnection
368 didSetSessionDescriptionWithError:(NSError *)error {
369 dispatch_async(dispatch_get_main_queue(), ^{
370 if (error) {
Zeke Chin2d3b7e22015-07-14 12:55:44 -0700371 ARDLog(@"Failed to set session description. Error: %@", error);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000372 [self disconnect];
373 NSDictionary *userInfo = @{
374 NSLocalizedDescriptionKey: @"Failed to set session description.",
375 };
376 NSError *sdpError =
377 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
378 code:kARDAppClientErrorSetSDP
379 userInfo:userInfo];
380 [_delegate appClient:self didError:sdpError];
381 return;
382 }
383 // If we're answering and we've just set the remote offer we need to create
384 // an answer and set the local description.
385 if (!_isInitiator && !_peerConnection.localDescription) {
386 RTCMediaConstraints *constraints = [self defaultAnswerConstraints];
387 [_peerConnection createAnswerWithDelegate:self
388 constraints:constraints];
389
390 }
391 });
392}
393
394#pragma mark - Private
395
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000396- (BOOL)hasJoinedRoomServerRoom {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000397 return _clientId.length;
398}
399
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000400// Begins the peer connection connection process if we have both joined a room
401// on the room server and tried to obtain a TURN server. Otherwise does nothing.
402// A peer connection object will be created with a stream that contains local
403// audio and video capture. If this client is the caller, an offer is created as
404// well, otherwise the client will wait for an offer to arrive.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000405- (void)startSignalingIfReady {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000406 if (!_isTurnComplete || !self.hasJoinedRoomServerRoom) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000407 return;
408 }
409 self.state = kARDAppClientStateConnected;
410
411 // Create peer connection.
412 RTCMediaConstraints *constraints = [self defaultPeerConnectionConstraints];
Zeke Chinbc7dd7e2015-05-29 14:59:14 -0700413 RTCConfiguration *config = [[RTCConfiguration alloc] init];
414 config.iceServers = _iceServers;
415 _peerConnection = [_factory peerConnectionWithConfiguration:config
416 constraints:constraints
417 delegate:self];
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000418 // Create AV media stream and add it to the peer connection.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000419 RTCMediaStream *localStream = [self createLocalMediaStream];
420 [_peerConnection addStream:localStream];
421 if (_isInitiator) {
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000422 // Send offer.
423 [_peerConnection createOfferWithDelegate:self
424 constraints:[self defaultOfferConstraints]];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000425 } else {
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000426 // Check if we've received an offer.
427 [self drainMessageQueueIfReady];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000428 }
429}
430
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000431// Processes the messages that we've received from the room server and the
432// signaling channel. The offer or answer message must be processed before other
433// signaling messages, however they can arrive out of order. Hence, this method
434// only processes pending messages if there is a peer connection object and
435// if we have received either an offer or answer.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000436- (void)drainMessageQueueIfReady {
437 if (!_peerConnection || !_hasReceivedSdp) {
438 return;
439 }
440 for (ARDSignalingMessage *message in _messageQueue) {
441 [self processSignalingMessage:message];
442 }
443 [_messageQueue removeAllObjects];
444}
445
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000446// Processes the given signaling message based on its type.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000447- (void)processSignalingMessage:(ARDSignalingMessage *)message {
448 NSParameterAssert(_peerConnection ||
449 message.type == kARDSignalingMessageTypeBye);
450 switch (message.type) {
451 case kARDSignalingMessageTypeOffer:
452 case kARDSignalingMessageTypeAnswer: {
453 ARDSessionDescriptionMessage *sdpMessage =
454 (ARDSessionDescriptionMessage *)message;
455 RTCSessionDescription *description = sdpMessage.sessionDescription;
Zeke Chin71f6f442015-06-29 14:34:58 -0700456 // Prefer H264 if available.
457 RTCSessionDescription *sdpPreferringH264 =
458 [ARDSDPUtils descriptionForDescription:description
459 preferredVideoCodec:@"H264"];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000460 [_peerConnection setRemoteDescriptionWithDelegate:self
Zeke Chin71f6f442015-06-29 14:34:58 -0700461 sessionDescription:sdpPreferringH264];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000462 break;
463 }
464 case kARDSignalingMessageTypeCandidate: {
465 ARDICECandidateMessage *candidateMessage =
466 (ARDICECandidateMessage *)message;
467 [_peerConnection addICECandidate:candidateMessage.candidate];
468 break;
469 }
470 case kARDSignalingMessageTypeBye:
471 // Other client disconnected.
472 // TODO(tkchin): support waiting in room for next client. For now just
473 // disconnect.
474 [self disconnect];
475 break;
476 }
477}
478
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000479// Sends a signaling message to the other client. The caller will send messages
480// through the room server, whereas the callee will send messages over the
481// signaling channel.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000482- (void)sendSignalingMessage:(ARDSignalingMessage *)message {
483 if (_isInitiator) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000484 __weak ARDAppClient *weakSelf = self;
485 [_roomServerClient sendMessage:message
486 forRoomId:_roomId
487 clientId:_clientId
488 completionHandler:^(ARDMessageResponse *response,
489 NSError *error) {
490 ARDAppClient *strongSelf = weakSelf;
491 if (error) {
492 [strongSelf.delegate appClient:strongSelf didError:error];
493 return;
494 }
495 NSError *messageError =
496 [[strongSelf class] errorForMessageResultType:response.result];
497 if (messageError) {
498 [strongSelf.delegate appClient:strongSelf didError:messageError];
499 return;
500 }
501 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000502 } else {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000503 [_channel sendMessage:message];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000504 }
505}
506
507- (RTCMediaStream *)createLocalMediaStream {
508 RTCMediaStream* localStream = [_factory mediaStreamWithLabel:@"ARDAMS"];
Zeke Chin57cc74e2015-05-05 07:52:31 -0700509 RTCVideoTrack* localVideoTrack = [self createLocalVideoTrack];
510 if (localVideoTrack) {
511 [localStream addVideoTrack:localVideoTrack];
512 [_delegate appClient:self didReceiveLocalVideoTrack:localVideoTrack];
513 }
514 [localStream addAudioTrack:[_factory audioTrackWithID:@"ARDAMSa0"]];
515 return localStream;
516}
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000517
Zeke Chin57cc74e2015-05-05 07:52:31 -0700518- (RTCVideoTrack *)createLocalVideoTrack {
519 RTCVideoTrack* localVideoTrack = nil;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000520 // The iOS simulator doesn't provide any sort of camera capture
521 // support or emulation (http://goo.gl/rHAnC1) so don't bother
522 // trying to open a local stream.
523 // TODO(tkchin): local video capture for OSX. See
524 // https://code.google.com/p/webrtc/issues/detail?id=3417.
525#if !TARGET_IPHONE_SIMULATOR && TARGET_OS_IPHONE
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000526 RTCMediaConstraints *mediaConstraints = [self defaultMediaStreamConstraints];
Zeke Chin57cc74e2015-05-05 07:52:31 -0700527 RTCAVFoundationVideoSource *source =
528 [[RTCAVFoundationVideoSource alloc] initWithFactory:_factory
529 constraints:mediaConstraints];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000530 localVideoTrack =
Zeke Chin57cc74e2015-05-05 07:52:31 -0700531 [[RTCVideoTrack alloc] initWithFactory:_factory
532 source:source
533 trackId:@"ARDAMSv0"];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000534#endif
Zeke Chin57cc74e2015-05-05 07:52:31 -0700535 return localVideoTrack;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000536}
537
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000538#pragma mark - Collider methods
539
540- (void)registerWithColliderIfReady {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000541 if (!self.hasJoinedRoomServerRoom) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000542 return;
543 }
544 // Open WebSocket connection.
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000545 if (!_channel) {
546 _channel =
547 [[ARDWebSocketChannel alloc] initWithURL:_websocketURL
548 restURL:_websocketRestURL
549 delegate:self];
550 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000551 [_channel registerForRoomId:_roomId clientId:_clientId];
552}
553
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000554#pragma mark - Defaults
555
556- (RTCMediaConstraints *)defaultMediaStreamConstraints {
557 RTCMediaConstraints* constraints =
558 [[RTCMediaConstraints alloc]
559 initWithMandatoryConstraints:nil
560 optionalConstraints:nil];
561 return constraints;
562}
563
564- (RTCMediaConstraints *)defaultAnswerConstraints {
565 return [self defaultOfferConstraints];
566}
567
568- (RTCMediaConstraints *)defaultOfferConstraints {
569 NSArray *mandatoryConstraints = @[
570 [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" value:@"true"],
571 [[RTCPair alloc] initWithKey:@"OfferToReceiveVideo" value:@"true"]
572 ];
573 RTCMediaConstraints* constraints =
574 [[RTCMediaConstraints alloc]
575 initWithMandatoryConstraints:mandatoryConstraints
576 optionalConstraints:nil];
577 return constraints;
578}
579
580- (RTCMediaConstraints *)defaultPeerConnectionConstraints {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000581 if (_defaultPeerConnectionConstraints) {
582 return _defaultPeerConnectionConstraints;
583 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000584 NSArray *optionalConstraints = @[
585 [[RTCPair alloc] initWithKey:@"DtlsSrtpKeyAgreement" value:@"true"]
586 ];
587 RTCMediaConstraints* constraints =
588 [[RTCMediaConstraints alloc]
589 initWithMandatoryConstraints:nil
590 optionalConstraints:optionalConstraints];
591 return constraints;
592}
593
594- (RTCICEServer *)defaultSTUNServer {
595 NSURL *defaultSTUNServerURL = [NSURL URLWithString:kARDDefaultSTUNServerUrl];
596 return [[RTCICEServer alloc] initWithURI:defaultSTUNServerURL
597 username:@""
598 password:@""];
599}
600
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000601#pragma mark - Errors
602
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000603+ (NSError *)errorForJoinResultType:(ARDJoinResultType)resultType {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000604 NSError *error = nil;
605 switch (resultType) {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000606 case kARDJoinResultTypeSuccess:
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000607 break;
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000608 case kARDJoinResultTypeUnknown: {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000609 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
610 code:kARDAppClientErrorUnknown
611 userInfo:@{
612 NSLocalizedDescriptionKey: @"Unknown error.",
613 }];
614 break;
615 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000616 case kARDJoinResultTypeFull: {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000617 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
618 code:kARDAppClientErrorRoomFull
619 userInfo:@{
620 NSLocalizedDescriptionKey: @"Room is full.",
621 }];
622 break;
623 }
624 }
625 return error;
626}
627
628+ (NSError *)errorForMessageResultType:(ARDMessageResultType)resultType {
629 NSError *error = nil;
630 switch (resultType) {
631 case kARDMessageResultTypeSuccess:
632 break;
633 case kARDMessageResultTypeUnknown:
634 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
635 code:kARDAppClientErrorUnknown
636 userInfo:@{
637 NSLocalizedDescriptionKey: @"Unknown error.",
638 }];
639 break;
640 case kARDMessageResultTypeInvalidClient:
641 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
642 code:kARDAppClientErrorInvalidClient
643 userInfo:@{
644 NSLocalizedDescriptionKey: @"Invalid client.",
645 }];
646 break;
647 case kARDMessageResultTypeInvalidRoom:
648 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
649 code:kARDAppClientErrorInvalidRoom
650 userInfo:@{
651 NSLocalizedDescriptionKey: @"Invalid room.",
652 }];
653 break;
654 }
655 return error;
656}
657
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000658@end