blob: 695b86e8f0c9b83a6f57f8a89f2f5675f4a2c67e [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
30#import <AVFoundation/AVFoundation.h>
31
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000032#import "ARDAppEngineClient.h"
33#import "ARDCEODTURNClient.h"
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +000034#import "ARDJoinResponse.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000035#import "ARDMessageResponse.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000036#import "ARDSignalingMessage.h"
37#import "ARDUtilities.h"
38#import "ARDWebSocketChannel.h"
39#import "RTCICECandidate+JSON.h"
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000040#import "RTCICEServer.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000041#import "RTCMediaConstraints.h"
42#import "RTCMediaStream.h"
43#import "RTCPair.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000044#import "RTCSessionDescription+JSON.h"
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000045#import "RTCVideoCapturer.h"
46#import "RTCVideoTrack.h"
47
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +000048static NSString * const kARDDefaultSTUNServerUrl =
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000049 @"stun:stun.l.google.com:19302";
50// TODO(tkchin): figure out a better username for CEOD statistics.
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +000051static NSString * const kARDTurnRequestUrl =
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000052 @"https://computeengineondemand.appspot.com"
53 @"/turn?username=iapprtc&key=4080218913";
54
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +000055static NSString * const kARDAppClientErrorDomain = @"ARDAppClient";
56static NSInteger const kARDAppClientErrorUnknown = -1;
57static NSInteger const kARDAppClientErrorRoomFull = -2;
58static NSInteger const kARDAppClientErrorCreateSDP = -3;
59static NSInteger const kARDAppClientErrorSetSDP = -4;
60static NSInteger const kARDAppClientErrorInvalidClient = -5;
61static NSInteger const kARDAppClientErrorInvalidRoom = -6;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000062
63@implementation ARDAppClient
64
65@synthesize delegate = _delegate;
66@synthesize state = _state;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000067@synthesize roomServerClient = _roomServerClient;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000068@synthesize channel = _channel;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000069@synthesize turnClient = _turnClient;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000070@synthesize peerConnection = _peerConnection;
71@synthesize factory = _factory;
72@synthesize messageQueue = _messageQueue;
73@synthesize isTurnComplete = _isTurnComplete;
74@synthesize hasReceivedSdp = _hasReceivedSdp;
75@synthesize roomId = _roomId;
76@synthesize clientId = _clientId;
77@synthesize isInitiator = _isInitiator;
78@synthesize iceServers = _iceServers;
79@synthesize webSocketURL = _websocketURL;
80@synthesize webSocketRestURL = _websocketRestURL;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000081@synthesize defaultPeerConnectionConstraints =
82 _defaultPeerConnectionConstraints;
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000083
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +000084- (instancetype)init {
85 if (self = [super init]) {
86 _roomServerClient = [[ARDAppEngineClient alloc] init];
87 NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl];
88 _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL];
89 [self configure];
90 }
91 return self;
92}
93
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000094- (instancetype)initWithDelegate:(id<ARDAppClientDelegate>)delegate {
95 if (self = [super init]) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000096 _roomServerClient = [[ARDAppEngineClient alloc] init];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +000097 _delegate = delegate;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000098 NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl];
99 _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL];
100 [self configure];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000101 }
102 return self;
103}
104
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000105// TODO(tkchin): Provide signaling channel factory interface so we can recreate
106// channel if we need to on network failure. Also, make this the default public
107// constructor.
108- (instancetype)initWithRoomServerClient:(id<ARDRoomServerClient>)rsClient
109 signalingChannel:(id<ARDSignalingChannel>)channel
110 turnClient:(id<ARDTURNClient>)turnClient
111 delegate:(id<ARDAppClientDelegate>)delegate {
112 NSParameterAssert(rsClient);
113 NSParameterAssert(channel);
114 NSParameterAssert(turnClient);
115 if (self = [super init]) {
116 _roomServerClient = rsClient;
117 _channel = channel;
118 _turnClient = turnClient;
119 _delegate = delegate;
120 [self configure];
121 }
122 return self;
123}
124
125- (void)configure {
126 _factory = [[RTCPeerConnectionFactory alloc] init];
127 _messageQueue = [NSMutableArray array];
128 _iceServers = [NSMutableArray arrayWithObject:[self defaultSTUNServer]];
129}
130
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000131- (void)dealloc {
132 [self disconnect];
133}
134
135- (void)setState:(ARDAppClientState)state {
136 if (_state == state) {
137 return;
138 }
139 _state = state;
140 [_delegate appClient:self didChangeState:_state];
141}
142
143- (void)connectToRoomWithId:(NSString *)roomId
144 options:(NSDictionary *)options {
145 NSParameterAssert(roomId.length);
146 NSParameterAssert(_state == kARDAppClientStateDisconnected);
147 self.state = kARDAppClientStateConnecting;
148
149 // Request TURN.
150 __weak ARDAppClient *weakSelf = self;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000151 [_turnClient requestServersWithCompletionHandler:^(NSArray *turnServers,
152 NSError *error) {
153 if (error) {
154 NSLog(@"Error retrieving TURN servers: %@", error);
155 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000156 ARDAppClient *strongSelf = weakSelf;
157 [strongSelf.iceServers addObjectsFromArray:turnServers];
158 strongSelf.isTurnComplete = YES;
159 [strongSelf startSignalingIfReady];
160 }];
jiayl@webrtc.org27f53172014-12-31 00:26:20 +0000161
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000162 // Join room on room server.
163 [_roomServerClient joinRoomWithRoomId:roomId
164 completionHandler:^(ARDJoinResponse *response, NSError *error) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000165 ARDAppClient *strongSelf = weakSelf;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000166 if (error) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000167 [strongSelf.delegate appClient:strongSelf didError:error];
168 return;
169 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000170 NSError *joinError =
171 [[strongSelf class] errorForJoinResultType:response.result];
172 if (joinError) {
173 NSLog(@"Failed to join room:%@ on room server.", roomId);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000174 [strongSelf disconnect];
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000175 [strongSelf.delegate appClient:strongSelf didError:joinError];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000176 return;
177 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000178 NSLog(@"Joined room:%@ on room server.", roomId);
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000179 strongSelf.roomId = response.roomId;
180 strongSelf.clientId = response.clientId;
181 strongSelf.isInitiator = response.isInitiator;
182 for (ARDSignalingMessage *message in response.messages) {
183 if (message.type == kARDSignalingMessageTypeOffer ||
184 message.type == kARDSignalingMessageTypeAnswer) {
185 strongSelf.hasReceivedSdp = YES;
186 [strongSelf.messageQueue insertObject:message atIndex:0];
187 } else {
188 [strongSelf.messageQueue addObject:message];
189 }
190 }
191 strongSelf.webSocketURL = response.webSocketURL;
192 strongSelf.webSocketRestURL = response.webSocketRestURL;
193 [strongSelf registerWithColliderIfReady];
194 [strongSelf startSignalingIfReady];
195 }];
196}
197
198- (void)disconnect {
199 if (_state == kARDAppClientStateDisconnected) {
200 return;
201 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000202 if (self.hasJoinedRoomServerRoom) {
203 [_roomServerClient leaveRoomWithRoomId:_roomId
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000204 clientId:_clientId
205 completionHandler:nil];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000206 }
207 if (_channel) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000208 if (_channel.state == kARDSignalingChannelStateRegistered) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000209 // Tell the other client we're hanging up.
210 ARDByeMessage *byeMessage = [[ARDByeMessage alloc] init];
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000211 [_channel sendMessage:byeMessage];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000212 }
213 // Disconnect from collider.
214 _channel = nil;
215 }
216 _clientId = nil;
217 _roomId = nil;
218 _isInitiator = NO;
219 _hasReceivedSdp = NO;
220 _messageQueue = [NSMutableArray array];
221 _peerConnection = nil;
222 self.state = kARDAppClientStateDisconnected;
223}
224
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000225#pragma mark - ARDSignalingChannelDelegate
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000226
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000227- (void)channel:(id<ARDSignalingChannel>)channel
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000228 didReceiveMessage:(ARDSignalingMessage *)message {
229 switch (message.type) {
230 case kARDSignalingMessageTypeOffer:
231 case kARDSignalingMessageTypeAnswer:
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000232 // Offers and answers must be processed before any other message, so we
233 // place them at the front of the queue.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000234 _hasReceivedSdp = YES;
235 [_messageQueue insertObject:message atIndex:0];
236 break;
237 case kARDSignalingMessageTypeCandidate:
238 [_messageQueue addObject:message];
239 break;
240 case kARDSignalingMessageTypeBye:
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000241 // Disconnects can be processed immediately.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000242 [self processSignalingMessage:message];
243 return;
244 }
245 [self drainMessageQueueIfReady];
246}
247
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000248- (void)channel:(id<ARDSignalingChannel>)channel
249 didChangeState:(ARDSignalingChannelState)state {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000250 switch (state) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000251 case kARDSignalingChannelStateOpen:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000252 break;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000253 case kARDSignalingChannelStateRegistered:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000254 break;
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000255 case kARDSignalingChannelStateClosed:
256 case kARDSignalingChannelStateError:
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000257 // TODO(tkchin): reconnection scenarios. Right now we just disconnect
258 // completely if the websocket connection fails.
259 [self disconnect];
260 break;
261 }
262}
263
264#pragma mark - RTCPeerConnectionDelegate
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000265// Callbacks for this delegate occur on non-main thread and need to be
266// dispatched back to main queue as needed.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000267
268- (void)peerConnection:(RTCPeerConnection *)peerConnection
269 signalingStateChanged:(RTCSignalingState)stateChanged {
270 NSLog(@"Signaling state changed: %d", stateChanged);
271}
272
273- (void)peerConnection:(RTCPeerConnection *)peerConnection
274 addedStream:(RTCMediaStream *)stream {
275 dispatch_async(dispatch_get_main_queue(), ^{
276 NSLog(@"Received %lu video tracks and %lu audio tracks",
277 (unsigned long)stream.videoTracks.count,
278 (unsigned long)stream.audioTracks.count);
279 if (stream.videoTracks.count) {
280 RTCVideoTrack *videoTrack = stream.videoTracks[0];
281 [_delegate appClient:self didReceiveRemoteVideoTrack:videoTrack];
282 }
283 });
284}
285
286- (void)peerConnection:(RTCPeerConnection *)peerConnection
287 removedStream:(RTCMediaStream *)stream {
288 NSLog(@"Stream was removed.");
289}
290
291- (void)peerConnectionOnRenegotiationNeeded:
292 (RTCPeerConnection *)peerConnection {
293 NSLog(@"WARNING: Renegotiation needed but unimplemented.");
294}
295
296- (void)peerConnection:(RTCPeerConnection *)peerConnection
297 iceConnectionChanged:(RTCICEConnectionState)newState {
298 NSLog(@"ICE state changed: %d", newState);
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000299 dispatch_async(dispatch_get_main_queue(), ^{
300 [_delegate appClient:self didChangeConnectionState:newState];
301 });
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000302}
303
304- (void)peerConnection:(RTCPeerConnection *)peerConnection
305 iceGatheringChanged:(RTCICEGatheringState)newState {
306 NSLog(@"ICE gathering state changed: %d", newState);
307}
308
309- (void)peerConnection:(RTCPeerConnection *)peerConnection
310 gotICECandidate:(RTCICECandidate *)candidate {
311 dispatch_async(dispatch_get_main_queue(), ^{
312 ARDICECandidateMessage *message =
313 [[ARDICECandidateMessage alloc] initWithCandidate:candidate];
314 [self sendSignalingMessage:message];
315 });
316}
317
318- (void)peerConnection:(RTCPeerConnection*)peerConnection
319 didOpenDataChannel:(RTCDataChannel*)dataChannel {
320}
321
322#pragma mark - RTCSessionDescriptionDelegate
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000323// Callbacks for this delegate occur on non-main thread and need to be
324// dispatched back to main queue as needed.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000325
326- (void)peerConnection:(RTCPeerConnection *)peerConnection
327 didCreateSessionDescription:(RTCSessionDescription *)sdp
328 error:(NSError *)error {
329 dispatch_async(dispatch_get_main_queue(), ^{
330 if (error) {
331 NSLog(@"Failed to create session description. Error: %@", error);
332 [self disconnect];
333 NSDictionary *userInfo = @{
334 NSLocalizedDescriptionKey: @"Failed to create session description.",
335 };
336 NSError *sdpError =
337 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
338 code:kARDAppClientErrorCreateSDP
339 userInfo:userInfo];
340 [_delegate appClient:self didError:sdpError];
341 return;
342 }
343 [_peerConnection setLocalDescriptionWithDelegate:self
344 sessionDescription:sdp];
345 ARDSessionDescriptionMessage *message =
346 [[ARDSessionDescriptionMessage alloc] initWithDescription:sdp];
347 [self sendSignalingMessage:message];
348 });
349}
350
351- (void)peerConnection:(RTCPeerConnection *)peerConnection
352 didSetSessionDescriptionWithError:(NSError *)error {
353 dispatch_async(dispatch_get_main_queue(), ^{
354 if (error) {
355 NSLog(@"Failed to set session description. Error: %@", error);
356 [self disconnect];
357 NSDictionary *userInfo = @{
358 NSLocalizedDescriptionKey: @"Failed to set session description.",
359 };
360 NSError *sdpError =
361 [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
362 code:kARDAppClientErrorSetSDP
363 userInfo:userInfo];
364 [_delegate appClient:self didError:sdpError];
365 return;
366 }
367 // If we're answering and we've just set the remote offer we need to create
368 // an answer and set the local description.
369 if (!_isInitiator && !_peerConnection.localDescription) {
370 RTCMediaConstraints *constraints = [self defaultAnswerConstraints];
371 [_peerConnection createAnswerWithDelegate:self
372 constraints:constraints];
373
374 }
375 });
376}
377
378#pragma mark - Private
379
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000380- (BOOL)hasJoinedRoomServerRoom {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000381 return _clientId.length;
382}
383
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000384// Begins the peer connection connection process if we have both joined a room
385// on the room server and tried to obtain a TURN server. Otherwise does nothing.
386// A peer connection object will be created with a stream that contains local
387// audio and video capture. If this client is the caller, an offer is created as
388// well, otherwise the client will wait for an offer to arrive.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000389- (void)startSignalingIfReady {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000390 if (!_isTurnComplete || !self.hasJoinedRoomServerRoom) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000391 return;
392 }
393 self.state = kARDAppClientStateConnected;
394
395 // Create peer connection.
396 RTCMediaConstraints *constraints = [self defaultPeerConnectionConstraints];
397 _peerConnection = [_factory peerConnectionWithICEServers:_iceServers
398 constraints:constraints
399 delegate:self];
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000400 // Create AV media stream and add it to the peer connection.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000401 RTCMediaStream *localStream = [self createLocalMediaStream];
402 [_peerConnection addStream:localStream];
403 if (_isInitiator) {
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000404 // Send offer.
405 [_peerConnection createOfferWithDelegate:self
406 constraints:[self defaultOfferConstraints]];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000407 } else {
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000408 // Check if we've received an offer.
409 [self drainMessageQueueIfReady];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000410 }
411}
412
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000413// Processes the messages that we've received from the room server and the
414// signaling channel. The offer or answer message must be processed before other
415// signaling messages, however they can arrive out of order. Hence, this method
416// only processes pending messages if there is a peer connection object and
417// if we have received either an offer or answer.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000418- (void)drainMessageQueueIfReady {
419 if (!_peerConnection || !_hasReceivedSdp) {
420 return;
421 }
422 for (ARDSignalingMessage *message in _messageQueue) {
423 [self processSignalingMessage:message];
424 }
425 [_messageQueue removeAllObjects];
426}
427
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000428// Processes the given signaling message based on its type.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000429- (void)processSignalingMessage:(ARDSignalingMessage *)message {
430 NSParameterAssert(_peerConnection ||
431 message.type == kARDSignalingMessageTypeBye);
432 switch (message.type) {
433 case kARDSignalingMessageTypeOffer:
434 case kARDSignalingMessageTypeAnswer: {
435 ARDSessionDescriptionMessage *sdpMessage =
436 (ARDSessionDescriptionMessage *)message;
437 RTCSessionDescription *description = sdpMessage.sessionDescription;
438 [_peerConnection setRemoteDescriptionWithDelegate:self
439 sessionDescription:description];
440 break;
441 }
442 case kARDSignalingMessageTypeCandidate: {
443 ARDICECandidateMessage *candidateMessage =
444 (ARDICECandidateMessage *)message;
445 [_peerConnection addICECandidate:candidateMessage.candidate];
446 break;
447 }
448 case kARDSignalingMessageTypeBye:
449 // Other client disconnected.
450 // TODO(tkchin): support waiting in room for next client. For now just
451 // disconnect.
452 [self disconnect];
453 break;
454 }
455}
456
tkchin@webrtc.org8cc47e92015-03-18 23:38:04 +0000457// Sends a signaling message to the other client. The caller will send messages
458// through the room server, whereas the callee will send messages over the
459// signaling channel.
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000460- (void)sendSignalingMessage:(ARDSignalingMessage *)message {
461 if (_isInitiator) {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000462 __weak ARDAppClient *weakSelf = self;
463 [_roomServerClient sendMessage:message
464 forRoomId:_roomId
465 clientId:_clientId
466 completionHandler:^(ARDMessageResponse *response,
467 NSError *error) {
468 ARDAppClient *strongSelf = weakSelf;
469 if (error) {
470 [strongSelf.delegate appClient:strongSelf didError:error];
471 return;
472 }
473 NSError *messageError =
474 [[strongSelf class] errorForMessageResultType:response.result];
475 if (messageError) {
476 [strongSelf.delegate appClient:strongSelf didError:messageError];
477 return;
478 }
479 }];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000480 } else {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000481 [_channel sendMessage:message];
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000482 }
483}
484
485- (RTCMediaStream *)createLocalMediaStream {
486 RTCMediaStream* localStream = [_factory mediaStreamWithLabel:@"ARDAMS"];
487 RTCVideoTrack* localVideoTrack = nil;
488
489 // The iOS simulator doesn't provide any sort of camera capture
490 // support or emulation (http://goo.gl/rHAnC1) so don't bother
491 // trying to open a local stream.
492 // TODO(tkchin): local video capture for OSX. See
493 // https://code.google.com/p/webrtc/issues/detail?id=3417.
494#if !TARGET_IPHONE_SIMULATOR && TARGET_OS_IPHONE
495 NSString *cameraID = nil;
496 for (AVCaptureDevice *captureDevice in
497 [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) {
498 if (captureDevice.position == AVCaptureDevicePositionFront) {
499 cameraID = [captureDevice localizedName];
500 break;
501 }
502 }
503 NSAssert(cameraID, @"Unable to get the front camera id");
504
505 RTCVideoCapturer *capturer =
506 [RTCVideoCapturer capturerWithDeviceName:cameraID];
507 RTCMediaConstraints *mediaConstraints = [self defaultMediaStreamConstraints];
508 RTCVideoSource *videoSource =
509 [_factory videoSourceWithCapturer:capturer
510 constraints:mediaConstraints];
511 localVideoTrack =
512 [_factory videoTrackWithID:@"ARDAMSv0" source:videoSource];
513 if (localVideoTrack) {
514 [localStream addVideoTrack:localVideoTrack];
515 }
516 [_delegate appClient:self didReceiveLocalVideoTrack:localVideoTrack];
517#endif
518 [localStream addAudioTrack:[_factory audioTrackWithID:@"ARDAMSa0"]];
519 return localStream;
520}
521
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000522#pragma mark - Collider methods
523
524- (void)registerWithColliderIfReady {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000525 if (!self.hasJoinedRoomServerRoom) {
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000526 return;
527 }
528 // Open WebSocket connection.
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000529 if (!_channel) {
530 _channel =
531 [[ARDWebSocketChannel alloc] initWithURL:_websocketURL
532 restURL:_websocketRestURL
533 delegate:self];
534 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000535 [_channel registerForRoomId:_roomId clientId:_clientId];
536}
537
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000538#pragma mark - Defaults
539
540- (RTCMediaConstraints *)defaultMediaStreamConstraints {
541 RTCMediaConstraints* constraints =
542 [[RTCMediaConstraints alloc]
543 initWithMandatoryConstraints:nil
544 optionalConstraints:nil];
545 return constraints;
546}
547
548- (RTCMediaConstraints *)defaultAnswerConstraints {
549 return [self defaultOfferConstraints];
550}
551
552- (RTCMediaConstraints *)defaultOfferConstraints {
553 NSArray *mandatoryConstraints = @[
554 [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" value:@"true"],
555 [[RTCPair alloc] initWithKey:@"OfferToReceiveVideo" value:@"true"]
556 ];
557 RTCMediaConstraints* constraints =
558 [[RTCMediaConstraints alloc]
559 initWithMandatoryConstraints:mandatoryConstraints
560 optionalConstraints:nil];
561 return constraints;
562}
563
564- (RTCMediaConstraints *)defaultPeerConnectionConstraints {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000565 if (_defaultPeerConnectionConstraints) {
566 return _defaultPeerConnectionConstraints;
567 }
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000568 NSArray *optionalConstraints = @[
569 [[RTCPair alloc] initWithKey:@"DtlsSrtpKeyAgreement" value:@"true"]
570 ];
571 RTCMediaConstraints* constraints =
572 [[RTCMediaConstraints alloc]
573 initWithMandatoryConstraints:nil
574 optionalConstraints:optionalConstraints];
575 return constraints;
576}
577
578- (RTCICEServer *)defaultSTUNServer {
579 NSURL *defaultSTUNServerURL = [NSURL URLWithString:kARDDefaultSTUNServerUrl];
580 return [[RTCICEServer alloc] initWithURI:defaultSTUNServerURL
581 username:@""
582 password:@""];
583}
584
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000585#pragma mark - Errors
586
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000587+ (NSError *)errorForJoinResultType:(ARDJoinResultType)resultType {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000588 NSError *error = nil;
589 switch (resultType) {
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000590 case kARDJoinResultTypeSuccess:
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000591 break;
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000592 case kARDJoinResultTypeUnknown: {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000593 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
594 code:kARDAppClientErrorUnknown
595 userInfo:@{
596 NSLocalizedDescriptionKey: @"Unknown error.",
597 }];
598 break;
599 }
tkchin@webrtc.org36401ab2015-01-27 21:34:39 +0000600 case kARDJoinResultTypeFull: {
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +0000601 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
602 code:kARDAppClientErrorRoomFull
603 userInfo:@{
604 NSLocalizedDescriptionKey: @"Room is full.",
605 }];
606 break;
607 }
608 }
609 return error;
610}
611
612+ (NSError *)errorForMessageResultType:(ARDMessageResultType)resultType {
613 NSError *error = nil;
614 switch (resultType) {
615 case kARDMessageResultTypeSuccess:
616 break;
617 case kARDMessageResultTypeUnknown:
618 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
619 code:kARDAppClientErrorUnknown
620 userInfo:@{
621 NSLocalizedDescriptionKey: @"Unknown error.",
622 }];
623 break;
624 case kARDMessageResultTypeInvalidClient:
625 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
626 code:kARDAppClientErrorInvalidClient
627 userInfo:@{
628 NSLocalizedDescriptionKey: @"Invalid client.",
629 }];
630 break;
631 case kARDMessageResultTypeInvalidRoom:
632 error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
633 code:kARDAppClientErrorInvalidRoom
634 userInfo:@{
635 NSLocalizedDescriptionKey: @"Invalid room.",
636 }];
637 break;
638 }
639 return error;
640}
641
tkchin@webrtc.org87776a82014-12-09 19:32:35 +0000642@end