• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 *  Copyright 2014 The WebRTC Project Authors. All rights reserved.
3 *
4 *  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.
9 */
10
11#import "ARDAppClient+Internal.h"
12
13#if defined(WEBRTC_IOS)
14#import "RTCAVFoundationVideoSource.h"
15#endif
16#import "RTCFileLogger.h"
17#import "RTCICEServer.h"
18#import "RTCLogging.h"
19#import "RTCMediaConstraints.h"
20#import "RTCMediaStream.h"
21#import "RTCPair.h"
22#import "RTCPeerConnectionInterface.h"
23#import "RTCVideoCapturer.h"
24
25#import "ARDAppEngineClient.h"
26#import "ARDCEODTURNClient.h"
27#import "ARDJoinResponse.h"
28#import "ARDMessageResponse.h"
29#import "ARDSDPUtils.h"
30#import "ARDSignalingMessage.h"
31#import "ARDUtilities.h"
32#import "ARDWebSocketChannel.h"
33#import "RTCICECandidate+JSON.h"
34#import "RTCSessionDescription+JSON.h"
35
36static NSString * const kARDDefaultSTUNServerUrl =
37    @"stun:stun.l.google.com:19302";
38// TODO(tkchin): figure out a better username for CEOD statistics.
39static NSString * const kARDTurnRequestUrl =
40    @"https://computeengineondemand.appspot.com"
41    @"/turn?username=iapprtc&key=4080218913";
42
43static NSString * const kARDAppClientErrorDomain = @"ARDAppClient";
44static NSInteger const kARDAppClientErrorUnknown = -1;
45static NSInteger const kARDAppClientErrorRoomFull = -2;
46static NSInteger const kARDAppClientErrorCreateSDP = -3;
47static NSInteger const kARDAppClientErrorSetSDP = -4;
48static NSInteger const kARDAppClientErrorInvalidClient = -5;
49static NSInteger const kARDAppClientErrorInvalidRoom = -6;
50
51// We need a proxy to NSTimer because it causes a strong retain cycle. When
52// using the proxy, |invalidate| must be called before it properly deallocs.
53@interface ARDTimerProxy : NSObject
54
55- (instancetype)initWithInterval:(NSTimeInterval)interval
56                         repeats:(BOOL)repeats
57                    timerHandler:(void (^)(void))timerHandler;
58- (void)invalidate;
59
60@end
61
62@implementation ARDTimerProxy {
63  NSTimer *_timer;
64  void (^_timerHandler)(void);
65}
66
67- (instancetype)initWithInterval:(NSTimeInterval)interval
68                         repeats:(BOOL)repeats
69                    timerHandler:(void (^)(void))timerHandler {
70  NSParameterAssert(timerHandler);
71  if (self = [super init]) {
72    _timerHandler = timerHandler;
73    _timer = [NSTimer scheduledTimerWithTimeInterval:interval
74                                              target:self
75                                            selector:@selector(timerDidFire:)
76                                            userInfo:nil
77                                             repeats:repeats];
78  }
79  return self;
80}
81
82- (void)invalidate {
83  [_timer invalidate];
84}
85
86- (void)timerDidFire:(NSTimer *)timer {
87  _timerHandler();
88}
89
90@end
91
92@implementation ARDAppClient {
93  RTCFileLogger *_fileLogger;
94  ARDTimerProxy *_statsTimer;
95}
96
97@synthesize shouldGetStats = _shouldGetStats;
98@synthesize state = _state;
99@synthesize delegate = _delegate;
100@synthesize roomServerClient = _roomServerClient;
101@synthesize channel = _channel;
102@synthesize loopbackChannel = _loopbackChannel;
103@synthesize turnClient = _turnClient;
104@synthesize peerConnection = _peerConnection;
105@synthesize factory = _factory;
106@synthesize messageQueue = _messageQueue;
107@synthesize isTurnComplete = _isTurnComplete;
108@synthesize hasReceivedSdp  = _hasReceivedSdp;
109@synthesize roomId = _roomId;
110@synthesize clientId = _clientId;
111@synthesize isInitiator = _isInitiator;
112@synthesize iceServers = _iceServers;
113@synthesize webSocketURL = _websocketURL;
114@synthesize webSocketRestURL = _websocketRestURL;
115@synthesize defaultPeerConnectionConstraints =
116    _defaultPeerConnectionConstraints;
117@synthesize isLoopback = _isLoopback;
118@synthesize isAudioOnly = _isAudioOnly;
119
120- (instancetype)init {
121  if (self = [super init]) {
122    _roomServerClient = [[ARDAppEngineClient alloc] init];
123    NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl];
124    _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL];
125    [self configure];
126  }
127  return self;
128}
129
130- (instancetype)initWithDelegate:(id<ARDAppClientDelegate>)delegate {
131  if (self = [super init]) {
132    _roomServerClient = [[ARDAppEngineClient alloc] init];
133    _delegate = delegate;
134    NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl];
135    _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL];
136    [self configure];
137  }
138  return self;
139}
140
141// TODO(tkchin): Provide signaling channel factory interface so we can recreate
142// channel if we need to on network failure. Also, make this the default public
143// constructor.
144- (instancetype)initWithRoomServerClient:(id<ARDRoomServerClient>)rsClient
145                        signalingChannel:(id<ARDSignalingChannel>)channel
146                              turnClient:(id<ARDTURNClient>)turnClient
147                                delegate:(id<ARDAppClientDelegate>)delegate {
148  NSParameterAssert(rsClient);
149  NSParameterAssert(channel);
150  NSParameterAssert(turnClient);
151  if (self = [super init]) {
152    _roomServerClient = rsClient;
153    _channel = channel;
154    _turnClient = turnClient;
155    _delegate = delegate;
156    [self configure];
157  }
158  return self;
159}
160
161- (void)configure {
162  _factory = [[RTCPeerConnectionFactory alloc] init];
163  _messageQueue = [NSMutableArray array];
164  _iceServers = [NSMutableArray arrayWithObject:[self defaultSTUNServer]];
165  _fileLogger = [[RTCFileLogger alloc] init];
166  [_fileLogger start];
167}
168
169- (void)dealloc {
170  self.shouldGetStats = NO;
171  [self disconnect];
172}
173
174- (void)setShouldGetStats:(BOOL)shouldGetStats {
175  if (_shouldGetStats == shouldGetStats) {
176    return;
177  }
178  if (shouldGetStats) {
179    __weak ARDAppClient *weakSelf = self;
180    _statsTimer = [[ARDTimerProxy alloc] initWithInterval:1
181                                                  repeats:YES
182                                             timerHandler:^{
183      ARDAppClient *strongSelf = weakSelf;
184      [strongSelf.peerConnection getStatsWithDelegate:strongSelf
185                                     mediaStreamTrack:nil
186                                     statsOutputLevel:RTCStatsOutputLevelDebug];
187    }];
188  } else {
189    [_statsTimer invalidate];
190    _statsTimer = nil;
191  }
192  _shouldGetStats = shouldGetStats;
193}
194
195- (void)setState:(ARDAppClientState)state {
196  if (_state == state) {
197    return;
198  }
199  _state = state;
200  [_delegate appClient:self didChangeState:_state];
201}
202
203- (void)connectToRoomWithId:(NSString *)roomId
204                 isLoopback:(BOOL)isLoopback
205                isAudioOnly:(BOOL)isAudioOnly {
206  NSParameterAssert(roomId.length);
207  NSParameterAssert(_state == kARDAppClientStateDisconnected);
208  _isLoopback = isLoopback;
209  _isAudioOnly = isAudioOnly;
210  self.state = kARDAppClientStateConnecting;
211
212  // Request TURN.
213  __weak ARDAppClient *weakSelf = self;
214  [_turnClient requestServersWithCompletionHandler:^(NSArray *turnServers,
215                                                     NSError *error) {
216    if (error) {
217      RTCLogError("Error retrieving TURN servers: %@",
218                  error.localizedDescription);
219    }
220    ARDAppClient *strongSelf = weakSelf;
221    [strongSelf.iceServers addObjectsFromArray:turnServers];
222    strongSelf.isTurnComplete = YES;
223    [strongSelf startSignalingIfReady];
224  }];
225
226  // Join room on room server.
227  [_roomServerClient joinRoomWithRoomId:roomId
228                             isLoopback:isLoopback
229      completionHandler:^(ARDJoinResponse *response, NSError *error) {
230    ARDAppClient *strongSelf = weakSelf;
231    if (error) {
232      [strongSelf.delegate appClient:strongSelf didError:error];
233      return;
234    }
235    NSError *joinError =
236        [[strongSelf class] errorForJoinResultType:response.result];
237    if (joinError) {
238      RTCLogError(@"Failed to join room:%@ on room server.", roomId);
239      [strongSelf disconnect];
240      [strongSelf.delegate appClient:strongSelf didError:joinError];
241      return;
242    }
243    RTCLog(@"Joined room:%@ on room server.", roomId);
244    strongSelf.roomId = response.roomId;
245    strongSelf.clientId = response.clientId;
246    strongSelf.isInitiator = response.isInitiator;
247    for (ARDSignalingMessage *message in response.messages) {
248      if (message.type == kARDSignalingMessageTypeOffer ||
249          message.type == kARDSignalingMessageTypeAnswer) {
250        strongSelf.hasReceivedSdp = YES;
251        [strongSelf.messageQueue insertObject:message atIndex:0];
252      } else {
253        [strongSelf.messageQueue addObject:message];
254      }
255    }
256    strongSelf.webSocketURL = response.webSocketURL;
257    strongSelf.webSocketRestURL = response.webSocketRestURL;
258    [strongSelf registerWithColliderIfReady];
259    [strongSelf startSignalingIfReady];
260  }];
261}
262
263- (void)disconnect {
264  if (_state == kARDAppClientStateDisconnected) {
265    return;
266  }
267  if (self.hasJoinedRoomServerRoom) {
268    [_roomServerClient leaveRoomWithRoomId:_roomId
269                                  clientId:_clientId
270                         completionHandler:nil];
271  }
272  if (_channel) {
273    if (_channel.state == kARDSignalingChannelStateRegistered) {
274      // Tell the other client we're hanging up.
275      ARDByeMessage *byeMessage = [[ARDByeMessage alloc] init];
276      [_channel sendMessage:byeMessage];
277    }
278    // Disconnect from collider.
279    _channel = nil;
280  }
281  _clientId = nil;
282  _roomId = nil;
283  _isInitiator = NO;
284  _hasReceivedSdp = NO;
285  _messageQueue = [NSMutableArray array];
286  _peerConnection = nil;
287  self.state = kARDAppClientStateDisconnected;
288}
289
290#pragma mark - ARDSignalingChannelDelegate
291
292- (void)channel:(id<ARDSignalingChannel>)channel
293    didReceiveMessage:(ARDSignalingMessage *)message {
294  switch (message.type) {
295    case kARDSignalingMessageTypeOffer:
296    case kARDSignalingMessageTypeAnswer:
297      // Offers and answers must be processed before any other message, so we
298      // place them at the front of the queue.
299      _hasReceivedSdp = YES;
300      [_messageQueue insertObject:message atIndex:0];
301      break;
302    case kARDSignalingMessageTypeCandidate:
303      [_messageQueue addObject:message];
304      break;
305    case kARDSignalingMessageTypeBye:
306      // Disconnects can be processed immediately.
307      [self processSignalingMessage:message];
308      return;
309  }
310  [self drainMessageQueueIfReady];
311}
312
313- (void)channel:(id<ARDSignalingChannel>)channel
314    didChangeState:(ARDSignalingChannelState)state {
315  switch (state) {
316    case kARDSignalingChannelStateOpen:
317      break;
318    case kARDSignalingChannelStateRegistered:
319      break;
320    case kARDSignalingChannelStateClosed:
321    case kARDSignalingChannelStateError:
322      // TODO(tkchin): reconnection scenarios. Right now we just disconnect
323      // completely if the websocket connection fails.
324      [self disconnect];
325      break;
326  }
327}
328
329#pragma mark - RTCPeerConnectionDelegate
330// Callbacks for this delegate occur on non-main thread and need to be
331// dispatched back to main queue as needed.
332
333- (void)peerConnection:(RTCPeerConnection *)peerConnection
334    signalingStateChanged:(RTCSignalingState)stateChanged {
335  RTCLog(@"Signaling state changed: %d", stateChanged);
336}
337
338- (void)peerConnection:(RTCPeerConnection *)peerConnection
339           addedStream:(RTCMediaStream *)stream {
340  dispatch_async(dispatch_get_main_queue(), ^{
341    RTCLog(@"Received %lu video tracks and %lu audio tracks",
342        (unsigned long)stream.videoTracks.count,
343        (unsigned long)stream.audioTracks.count);
344    if (stream.videoTracks.count) {
345      RTCVideoTrack *videoTrack = stream.videoTracks[0];
346      [_delegate appClient:self didReceiveRemoteVideoTrack:videoTrack];
347    }
348  });
349}
350
351- (void)peerConnection:(RTCPeerConnection *)peerConnection
352        removedStream:(RTCMediaStream *)stream {
353  RTCLog(@"Stream was removed.");
354}
355
356- (void)peerConnectionOnRenegotiationNeeded:
357    (RTCPeerConnection *)peerConnection {
358  RTCLog(@"WARNING: Renegotiation needed but unimplemented.");
359}
360
361- (void)peerConnection:(RTCPeerConnection *)peerConnection
362    iceConnectionChanged:(RTCICEConnectionState)newState {
363  RTCLog(@"ICE state changed: %d", newState);
364  dispatch_async(dispatch_get_main_queue(), ^{
365    [_delegate appClient:self didChangeConnectionState:newState];
366  });
367}
368
369- (void)peerConnection:(RTCPeerConnection *)peerConnection
370    iceGatheringChanged:(RTCICEGatheringState)newState {
371  RTCLog(@"ICE gathering state changed: %d", newState);
372}
373
374- (void)peerConnection:(RTCPeerConnection *)peerConnection
375       gotICECandidate:(RTCICECandidate *)candidate {
376  dispatch_async(dispatch_get_main_queue(), ^{
377    ARDICECandidateMessage *message =
378        [[ARDICECandidateMessage alloc] initWithCandidate:candidate];
379    [self sendSignalingMessage:message];
380  });
381}
382
383- (void)peerConnection:(RTCPeerConnection *)peerConnection
384    didOpenDataChannel:(RTCDataChannel *)dataChannel {
385}
386
387#pragma mark - RTCStatsDelegate
388
389- (void)peerConnection:(RTCPeerConnection *)peerConnection
390           didGetStats:(NSArray *)stats {
391  dispatch_async(dispatch_get_main_queue(), ^{
392    [_delegate appClient:self didGetStats:stats];
393  });
394}
395
396#pragma mark - RTCSessionDescriptionDelegate
397// Callbacks for this delegate occur on non-main thread and need to be
398// dispatched back to main queue as needed.
399
400- (void)peerConnection:(RTCPeerConnection *)peerConnection
401    didCreateSessionDescription:(RTCSessionDescription *)sdp
402                          error:(NSError *)error {
403  dispatch_async(dispatch_get_main_queue(), ^{
404    if (error) {
405      RTCLogError(@"Failed to create session description. Error: %@", error);
406      [self disconnect];
407      NSDictionary *userInfo = @{
408        NSLocalizedDescriptionKey: @"Failed to create session description.",
409      };
410      NSError *sdpError =
411          [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
412                                     code:kARDAppClientErrorCreateSDP
413                                 userInfo:userInfo];
414      [_delegate appClient:self didError:sdpError];
415      return;
416    }
417    // Prefer H264 if available.
418    RTCSessionDescription *sdpPreferringH264 =
419        [ARDSDPUtils descriptionForDescription:sdp
420                           preferredVideoCodec:@"H264"];
421    [_peerConnection setLocalDescriptionWithDelegate:self
422                                  sessionDescription:sdpPreferringH264];
423    ARDSessionDescriptionMessage *message =
424        [[ARDSessionDescriptionMessage alloc]
425            initWithDescription:sdpPreferringH264];
426    [self sendSignalingMessage:message];
427  });
428}
429
430- (void)peerConnection:(RTCPeerConnection *)peerConnection
431    didSetSessionDescriptionWithError:(NSError *)error {
432  dispatch_async(dispatch_get_main_queue(), ^{
433    if (error) {
434      RTCLogError(@"Failed to set session description. Error: %@", error);
435      [self disconnect];
436      NSDictionary *userInfo = @{
437        NSLocalizedDescriptionKey: @"Failed to set session description.",
438      };
439      NSError *sdpError =
440          [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
441                                     code:kARDAppClientErrorSetSDP
442                                 userInfo:userInfo];
443      [_delegate appClient:self didError:sdpError];
444      return;
445    }
446    // If we're answering and we've just set the remote offer we need to create
447    // an answer and set the local description.
448    if (!_isInitiator && !_peerConnection.localDescription) {
449      RTCMediaConstraints *constraints = [self defaultAnswerConstraints];
450      [_peerConnection createAnswerWithDelegate:self
451                                    constraints:constraints];
452
453    }
454  });
455}
456
457#pragma mark - Private
458
459- (BOOL)hasJoinedRoomServerRoom {
460  return _clientId.length;
461}
462
463// Begins the peer connection connection process if we have both joined a room
464// on the room server and tried to obtain a TURN server. Otherwise does nothing.
465// A peer connection object will be created with a stream that contains local
466// audio and video capture. If this client is the caller, an offer is created as
467// well, otherwise the client will wait for an offer to arrive.
468- (void)startSignalingIfReady {
469  if (!_isTurnComplete || !self.hasJoinedRoomServerRoom) {
470    return;
471  }
472  self.state = kARDAppClientStateConnected;
473
474  // Create peer connection.
475  RTCMediaConstraints *constraints = [self defaultPeerConnectionConstraints];
476  RTCConfiguration *config = [[RTCConfiguration alloc] init];
477  config.iceServers = _iceServers;
478  _peerConnection = [_factory peerConnectionWithConfiguration:config
479                                                  constraints:constraints
480                                                     delegate:self];
481  // Create AV media stream and add it to the peer connection.
482  RTCMediaStream *localStream = [self createLocalMediaStream];
483  [_peerConnection addStream:localStream];
484  if (_isInitiator) {
485    // Send offer.
486    [_peerConnection createOfferWithDelegate:self
487                                 constraints:[self defaultOfferConstraints]];
488  } else {
489    // Check if we've received an offer.
490    [self drainMessageQueueIfReady];
491  }
492}
493
494// Processes the messages that we've received from the room server and the
495// signaling channel. The offer or answer message must be processed before other
496// signaling messages, however they can arrive out of order. Hence, this method
497// only processes pending messages if there is a peer connection object and
498// if we have received either an offer or answer.
499- (void)drainMessageQueueIfReady {
500  if (!_peerConnection || !_hasReceivedSdp) {
501    return;
502  }
503  for (ARDSignalingMessage *message in _messageQueue) {
504    [self processSignalingMessage:message];
505  }
506  [_messageQueue removeAllObjects];
507}
508
509// Processes the given signaling message based on its type.
510- (void)processSignalingMessage:(ARDSignalingMessage *)message {
511  NSParameterAssert(_peerConnection ||
512      message.type == kARDSignalingMessageTypeBye);
513  switch (message.type) {
514    case kARDSignalingMessageTypeOffer:
515    case kARDSignalingMessageTypeAnswer: {
516      ARDSessionDescriptionMessage *sdpMessage =
517          (ARDSessionDescriptionMessage *)message;
518      RTCSessionDescription *description = sdpMessage.sessionDescription;
519      // Prefer H264 if available.
520      RTCSessionDescription *sdpPreferringH264 =
521          [ARDSDPUtils descriptionForDescription:description
522                             preferredVideoCodec:@"H264"];
523      [_peerConnection setRemoteDescriptionWithDelegate:self
524                                     sessionDescription:sdpPreferringH264];
525      break;
526    }
527    case kARDSignalingMessageTypeCandidate: {
528      ARDICECandidateMessage *candidateMessage =
529          (ARDICECandidateMessage *)message;
530      [_peerConnection addICECandidate:candidateMessage.candidate];
531      break;
532    }
533    case kARDSignalingMessageTypeBye:
534      // Other client disconnected.
535      // TODO(tkchin): support waiting in room for next client. For now just
536      // disconnect.
537      [self disconnect];
538      break;
539  }
540}
541
542// Sends a signaling message to the other client. The caller will send messages
543// through the room server, whereas the callee will send messages over the
544// signaling channel.
545- (void)sendSignalingMessage:(ARDSignalingMessage *)message {
546  if (_isInitiator) {
547    __weak ARDAppClient *weakSelf = self;
548    [_roomServerClient sendMessage:message
549                         forRoomId:_roomId
550                          clientId:_clientId
551                 completionHandler:^(ARDMessageResponse *response,
552                                     NSError *error) {
553      ARDAppClient *strongSelf = weakSelf;
554      if (error) {
555        [strongSelf.delegate appClient:strongSelf didError:error];
556        return;
557      }
558      NSError *messageError =
559          [[strongSelf class] errorForMessageResultType:response.result];
560      if (messageError) {
561        [strongSelf.delegate appClient:strongSelf didError:messageError];
562        return;
563      }
564    }];
565  } else {
566    [_channel sendMessage:message];
567  }
568}
569
570- (RTCMediaStream *)createLocalMediaStream {
571  RTCMediaStream* localStream = [_factory mediaStreamWithLabel:@"ARDAMS"];
572  RTCVideoTrack* localVideoTrack = [self createLocalVideoTrack];
573  if (localVideoTrack) {
574    [localStream addVideoTrack:localVideoTrack];
575    [_delegate appClient:self didReceiveLocalVideoTrack:localVideoTrack];
576  }
577  [localStream addAudioTrack:[_factory audioTrackWithID:@"ARDAMSa0"]];
578  return localStream;
579}
580
581- (RTCVideoTrack *)createLocalVideoTrack {
582  RTCVideoTrack* localVideoTrack = nil;
583  // The iOS simulator doesn't provide any sort of camera capture
584  // support or emulation (http://goo.gl/rHAnC1) so don't bother
585  // trying to open a local stream.
586  // TODO(tkchin): local video capture for OSX. See
587  // https://code.google.com/p/webrtc/issues/detail?id=3417.
588#if !TARGET_IPHONE_SIMULATOR && TARGET_OS_IPHONE
589  if (!_isAudioOnly) {
590    RTCMediaConstraints *mediaConstraints =
591        [self defaultMediaStreamConstraints];
592    RTCAVFoundationVideoSource *source =
593        [[RTCAVFoundationVideoSource alloc] initWithFactory:_factory
594                                                constraints:mediaConstraints];
595    localVideoTrack =
596        [[RTCVideoTrack alloc] initWithFactory:_factory
597                                        source:source
598                                       trackId:@"ARDAMSv0"];
599  }
600#endif
601  return localVideoTrack;
602}
603
604#pragma mark - Collider methods
605
606- (void)registerWithColliderIfReady {
607  if (!self.hasJoinedRoomServerRoom) {
608    return;
609  }
610  // Open WebSocket connection.
611  if (!_channel) {
612    _channel =
613        [[ARDWebSocketChannel alloc] initWithURL:_websocketURL
614                                         restURL:_websocketRestURL
615                                        delegate:self];
616    if (_isLoopback) {
617      _loopbackChannel =
618          [[ARDLoopbackWebSocketChannel alloc] initWithURL:_websocketURL
619                                                   restURL:_websocketRestURL];
620    }
621  }
622  [_channel registerForRoomId:_roomId clientId:_clientId];
623  if (_isLoopback) {
624    [_loopbackChannel registerForRoomId:_roomId clientId:@"LOOPBACK_CLIENT_ID"];
625  }
626}
627
628#pragma mark - Defaults
629
630- (RTCMediaConstraints *)defaultMediaStreamConstraints {
631  RTCMediaConstraints* constraints =
632      [[RTCMediaConstraints alloc]
633          initWithMandatoryConstraints:nil
634                   optionalConstraints:nil];
635  return constraints;
636}
637
638- (RTCMediaConstraints *)defaultAnswerConstraints {
639  return [self defaultOfferConstraints];
640}
641
642- (RTCMediaConstraints *)defaultOfferConstraints {
643  NSArray *mandatoryConstraints = @[
644      [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" value:@"true"],
645      [[RTCPair alloc] initWithKey:@"OfferToReceiveVideo" value:@"true"]
646  ];
647  RTCMediaConstraints* constraints =
648      [[RTCMediaConstraints alloc]
649          initWithMandatoryConstraints:mandatoryConstraints
650                   optionalConstraints:nil];
651  return constraints;
652}
653
654- (RTCMediaConstraints *)defaultPeerConnectionConstraints {
655  if (_defaultPeerConnectionConstraints) {
656    return _defaultPeerConnectionConstraints;
657  }
658  NSString *value = _isLoopback ? @"false" : @"true";
659  NSArray *optionalConstraints = @[
660      [[RTCPair alloc] initWithKey:@"DtlsSrtpKeyAgreement" value:value]
661  ];
662  RTCMediaConstraints* constraints =
663      [[RTCMediaConstraints alloc]
664          initWithMandatoryConstraints:nil
665                   optionalConstraints:optionalConstraints];
666  return constraints;
667}
668
669- (RTCICEServer *)defaultSTUNServer {
670  NSURL *defaultSTUNServerURL = [NSURL URLWithString:kARDDefaultSTUNServerUrl];
671  return [[RTCICEServer alloc] initWithURI:defaultSTUNServerURL
672                                  username:@""
673                                  password:@""];
674}
675
676#pragma mark - Errors
677
678+ (NSError *)errorForJoinResultType:(ARDJoinResultType)resultType {
679  NSError *error = nil;
680  switch (resultType) {
681    case kARDJoinResultTypeSuccess:
682      break;
683    case kARDJoinResultTypeUnknown: {
684      error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
685                                         code:kARDAppClientErrorUnknown
686                                     userInfo:@{
687        NSLocalizedDescriptionKey: @"Unknown error.",
688      }];
689      break;
690    }
691    case kARDJoinResultTypeFull: {
692      error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
693                                         code:kARDAppClientErrorRoomFull
694                                     userInfo:@{
695        NSLocalizedDescriptionKey: @"Room is full.",
696      }];
697      break;
698    }
699  }
700  return error;
701}
702
703+ (NSError *)errorForMessageResultType:(ARDMessageResultType)resultType {
704  NSError *error = nil;
705  switch (resultType) {
706    case kARDMessageResultTypeSuccess:
707      break;
708    case kARDMessageResultTypeUnknown:
709      error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
710                                         code:kARDAppClientErrorUnknown
711                                     userInfo:@{
712        NSLocalizedDescriptionKey: @"Unknown error.",
713      }];
714      break;
715    case kARDMessageResultTypeInvalidClient:
716      error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
717                                         code:kARDAppClientErrorInvalidClient
718                                     userInfo:@{
719        NSLocalizedDescriptionKey: @"Invalid client.",
720      }];
721      break;
722    case kARDMessageResultTypeInvalidRoom:
723      error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
724                                         code:kARDAppClientErrorInvalidRoom
725                                     userInfo:@{
726        NSLocalizedDescriptionKey: @"Invalid room.",
727      }];
728      break;
729  }
730  return error;
731}
732
733@end
734