• 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#import "sdk/objc/api/peerconnection/RTCAudioTrack.h"
14#import "sdk/objc/api/peerconnection/RTCConfiguration.h"
15#import "sdk/objc/api/peerconnection/RTCFileLogger.h"
16#import "sdk/objc/api/peerconnection/RTCIceCandidateErrorEvent.h"
17#import "sdk/objc/api/peerconnection/RTCIceServer.h"
18#import "sdk/objc/api/peerconnection/RTCMediaConstraints.h"
19#import "sdk/objc/api/peerconnection/RTCMediaStream.h"
20#import "sdk/objc/api/peerconnection/RTCPeerConnectionFactory.h"
21#import "sdk/objc/api/peerconnection/RTCRtpSender.h"
22#import "sdk/objc/api/peerconnection/RTCRtpTransceiver.h"
23#import "sdk/objc/api/peerconnection/RTCTracing.h"
24#import "sdk/objc/api/peerconnection/RTCVideoSource.h"
25#import "sdk/objc/api/peerconnection/RTCVideoTrack.h"
26#import "sdk/objc/base/RTCLogging.h"
27#import "sdk/objc/components/capturer/RTCCameraVideoCapturer.h"
28#import "sdk/objc/components/capturer/RTCFileVideoCapturer.h"
29#import "sdk/objc/components/video_codec/RTCDefaultVideoDecoderFactory.h"
30#import "sdk/objc/components/video_codec/RTCDefaultVideoEncoderFactory.h"
31
32#import "ARDAppEngineClient.h"
33#import "ARDExternalSampleCapturer.h"
34#import "ARDJoinResponse.h"
35#import "ARDMessageResponse.h"
36#import "ARDSettingsModel.h"
37#import "ARDSignalingMessage.h"
38#import "ARDTURNClient+Internal.h"
39#import "ARDUtilities.h"
40#import "ARDWebSocketChannel.h"
41#import "RTCIceCandidate+JSON.h"
42#import "RTCSessionDescription+JSON.h"
43
44static NSString * const kARDIceServerRequestUrl = @"https://appr.tc/params";
45
46static NSString * const kARDAppClientErrorDomain = @"ARDAppClient";
47static NSInteger const kARDAppClientErrorUnknown = -1;
48static NSInteger const kARDAppClientErrorRoomFull = -2;
49static NSInteger const kARDAppClientErrorCreateSDP = -3;
50static NSInteger const kARDAppClientErrorSetSDP = -4;
51static NSInteger const kARDAppClientErrorInvalidClient = -5;
52static NSInteger const kARDAppClientErrorInvalidRoom = -6;
53static NSString * const kARDMediaStreamId = @"ARDAMS";
54static NSString * const kARDAudioTrackId = @"ARDAMSa0";
55static NSString * const kARDVideoTrackId = @"ARDAMSv0";
56static NSString * const kARDVideoTrackKind = @"video";
57
58// TODO(tkchin): Add these as UI options.
59#if defined(WEBRTC_IOS)
60static BOOL const kARDAppClientEnableTracing = NO;
61static BOOL const kARDAppClientEnableRtcEventLog = YES;
62static int64_t const kARDAppClientAecDumpMaxSizeInBytes = 5e6;  // 5 MB.
63static int64_t const kARDAppClientRtcEventLogMaxSizeInBytes = 5e6;  // 5 MB.
64#endif
65static int const kKbpsMultiplier = 1000;
66
67// We need a proxy to NSTimer because it causes a strong retain cycle. When
68// using the proxy, `invalidate` must be called before it properly deallocs.
69@interface ARDTimerProxy : NSObject
70
71- (instancetype)initWithInterval:(NSTimeInterval)interval
72                         repeats:(BOOL)repeats
73                    timerHandler:(void (^)(void))timerHandler;
74- (void)invalidate;
75
76@end
77
78@implementation ARDTimerProxy {
79  NSTimer *_timer;
80  void (^_timerHandler)(void);
81}
82
83- (instancetype)initWithInterval:(NSTimeInterval)interval
84                         repeats:(BOOL)repeats
85                    timerHandler:(void (^)(void))timerHandler {
86  NSParameterAssert(timerHandler);
87  if (self = [super init]) {
88    _timerHandler = timerHandler;
89    _timer = [NSTimer scheduledTimerWithTimeInterval:interval
90                                              target:self
91                                            selector:@selector(timerDidFire:)
92                                            userInfo:nil
93                                             repeats:repeats];
94  }
95  return self;
96}
97
98- (void)invalidate {
99  [_timer invalidate];
100}
101
102- (void)timerDidFire:(NSTimer *)timer {
103  _timerHandler();
104}
105
106@end
107
108@implementation ARDAppClient {
109  RTC_OBJC_TYPE(RTCFileLogger) * _fileLogger;
110  ARDTimerProxy *_statsTimer;
111  ARDSettingsModel *_settings;
112  RTC_OBJC_TYPE(RTCVideoTrack) * _localVideoTrack;
113}
114
115@synthesize shouldGetStats = _shouldGetStats;
116@synthesize state = _state;
117@synthesize delegate = _delegate;
118@synthesize roomServerClient = _roomServerClient;
119@synthesize channel = _channel;
120@synthesize loopbackChannel = _loopbackChannel;
121@synthesize turnClient = _turnClient;
122@synthesize peerConnection = _peerConnection;
123@synthesize factory = _factory;
124@synthesize messageQueue = _messageQueue;
125@synthesize isTurnComplete = _isTurnComplete;
126@synthesize hasReceivedSdp  = _hasReceivedSdp;
127@synthesize roomId = _roomId;
128@synthesize clientId = _clientId;
129@synthesize isInitiator = _isInitiator;
130@synthesize iceServers = _iceServers;
131@synthesize webSocketURL = _websocketURL;
132@synthesize webSocketRestURL = _websocketRestURL;
133@synthesize defaultPeerConnectionConstraints =
134    _defaultPeerConnectionConstraints;
135@synthesize isLoopback = _isLoopback;
136@synthesize broadcast = _broadcast;
137
138- (instancetype)init {
139  return [self initWithDelegate:nil];
140}
141
142- (instancetype)initWithDelegate:(id<ARDAppClientDelegate>)delegate {
143  if (self = [super init]) {
144    _roomServerClient = [[ARDAppEngineClient alloc] init];
145    _delegate = delegate;
146    NSURL *turnRequestURL = [NSURL URLWithString:kARDIceServerRequestUrl];
147    _turnClient = [[ARDTURNClient alloc] initWithURL:turnRequestURL];
148    [self configure];
149  }
150  return self;
151}
152
153// TODO(tkchin): Provide signaling channel factory interface so we can recreate
154// channel if we need to on network failure. Also, make this the default public
155// constructor.
156- (instancetype)initWithRoomServerClient:(id<ARDRoomServerClient>)rsClient
157                        signalingChannel:(id<ARDSignalingChannel>)channel
158                              turnClient:(id<ARDTURNClient>)turnClient
159                                delegate:(id<ARDAppClientDelegate>)delegate {
160  NSParameterAssert(rsClient);
161  NSParameterAssert(channel);
162  NSParameterAssert(turnClient);
163  if (self = [super init]) {
164    _roomServerClient = rsClient;
165    _channel = channel;
166    _turnClient = turnClient;
167    _delegate = delegate;
168    [self configure];
169  }
170  return self;
171}
172
173- (void)configure {
174  _messageQueue = [NSMutableArray array];
175  _iceServers = [NSMutableArray array];
176  _fileLogger = [[RTC_OBJC_TYPE(RTCFileLogger) alloc] init];
177  [_fileLogger start];
178}
179
180- (void)dealloc {
181  self.shouldGetStats = NO;
182  [self disconnect];
183}
184
185- (void)setShouldGetStats:(BOOL)shouldGetStats {
186  if (_shouldGetStats == shouldGetStats) {
187    return;
188  }
189  if (shouldGetStats) {
190    __weak ARDAppClient *weakSelf = self;
191    _statsTimer = [[ARDTimerProxy alloc] initWithInterval:1
192                                                  repeats:YES
193                                             timerHandler:^{
194      ARDAppClient *strongSelf = weakSelf;
195      [strongSelf.peerConnection statisticsWithCompletionHandler:^(
196                                     RTC_OBJC_TYPE(RTCStatisticsReport) * stats) {
197        dispatch_async(dispatch_get_main_queue(), ^{
198          ARDAppClient *strongSelf = weakSelf;
199          [strongSelf.delegate appClient:strongSelf didGetStats:stats];
200        });
201      }];
202    }];
203  } else {
204    [_statsTimer invalidate];
205    _statsTimer = nil;
206  }
207  _shouldGetStats = shouldGetStats;
208}
209
210- (void)setState:(ARDAppClientState)state {
211  if (_state == state) {
212    return;
213  }
214  _state = state;
215  [_delegate appClient:self didChangeState:_state];
216}
217
218- (void)connectToRoomWithId:(NSString *)roomId
219                   settings:(ARDSettingsModel *)settings
220                 isLoopback:(BOOL)isLoopback {
221  NSParameterAssert(roomId.length);
222  NSParameterAssert(_state == kARDAppClientStateDisconnected);
223  _settings = settings;
224  _isLoopback = isLoopback;
225  self.state = kARDAppClientStateConnecting;
226
227  RTC_OBJC_TYPE(RTCDefaultVideoDecoderFactory) *decoderFactory =
228      [[RTC_OBJC_TYPE(RTCDefaultVideoDecoderFactory) alloc] init];
229  RTC_OBJC_TYPE(RTCDefaultVideoEncoderFactory) *encoderFactory =
230      [[RTC_OBJC_TYPE(RTCDefaultVideoEncoderFactory) alloc] init];
231  encoderFactory.preferredCodec = [settings currentVideoCodecSettingFromStore];
232  _factory =
233      [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] initWithEncoderFactory:encoderFactory
234                                                               decoderFactory:decoderFactory];
235
236#if defined(WEBRTC_IOS)
237  if (kARDAppClientEnableTracing) {
238    NSString *filePath = [self documentsFilePathForFileName:@"webrtc-trace.txt"];
239    RTCStartInternalCapture(filePath);
240  }
241#endif
242
243  // Request TURN.
244  __weak ARDAppClient *weakSelf = self;
245  [_turnClient requestServersWithCompletionHandler:^(NSArray *turnServers,
246                                                     NSError *error) {
247    if (error) {
248      RTCLogError(@"Error retrieving TURN servers: %@", error.localizedDescription);
249    }
250    ARDAppClient *strongSelf = weakSelf;
251    [strongSelf.iceServers addObjectsFromArray:turnServers];
252    strongSelf.isTurnComplete = YES;
253    [strongSelf startSignalingIfReady];
254  }];
255
256  // Join room on room server.
257  [_roomServerClient joinRoomWithRoomId:roomId
258                             isLoopback:isLoopback
259      completionHandler:^(ARDJoinResponse *response, NSError *error) {
260    ARDAppClient *strongSelf = weakSelf;
261    if (error) {
262      [strongSelf.delegate appClient:strongSelf didError:error];
263      return;
264    }
265    NSError *joinError =
266        [[strongSelf class] errorForJoinResultType:response.result];
267    if (joinError) {
268      RTCLogError(@"Failed to join room:%@ on room server.", roomId);
269      [strongSelf disconnect];
270      [strongSelf.delegate appClient:strongSelf didError:joinError];
271      return;
272    }
273    RTCLog(@"Joined room:%@ on room server.", roomId);
274    strongSelf.roomId = response.roomId;
275    strongSelf.clientId = response.clientId;
276    strongSelf.isInitiator = response.isInitiator;
277    for (ARDSignalingMessage *message in response.messages) {
278      if (message.type == kARDSignalingMessageTypeOffer ||
279          message.type == kARDSignalingMessageTypeAnswer) {
280        strongSelf.hasReceivedSdp = YES;
281        [strongSelf.messageQueue insertObject:message atIndex:0];
282      } else {
283        [strongSelf.messageQueue addObject:message];
284      }
285    }
286    strongSelf.webSocketURL = response.webSocketURL;
287    strongSelf.webSocketRestURL = response.webSocketRestURL;
288    [strongSelf registerWithColliderIfReady];
289    [strongSelf startSignalingIfReady];
290  }];
291}
292
293- (void)disconnect {
294  if (_state == kARDAppClientStateDisconnected) {
295    return;
296  }
297  if (self.hasJoinedRoomServerRoom) {
298    [_roomServerClient leaveRoomWithRoomId:_roomId
299                                  clientId:_clientId
300                         completionHandler:nil];
301  }
302  if (_channel) {
303    if (_channel.state == kARDSignalingChannelStateRegistered) {
304      // Tell the other client we're hanging up.
305      ARDByeMessage *byeMessage = [[ARDByeMessage alloc] init];
306      [_channel sendMessage:byeMessage];
307    }
308    // Disconnect from collider.
309    _channel = nil;
310  }
311  _clientId = nil;
312  _roomId = nil;
313  _isInitiator = NO;
314  _hasReceivedSdp = NO;
315  _messageQueue = [NSMutableArray array];
316  _localVideoTrack = nil;
317#if defined(WEBRTC_IOS)
318  [_factory stopAecDump];
319  [_peerConnection stopRtcEventLog];
320#endif
321  [_peerConnection close];
322  _peerConnection = nil;
323  self.state = kARDAppClientStateDisconnected;
324#if defined(WEBRTC_IOS)
325  if (kARDAppClientEnableTracing) {
326    RTCStopInternalCapture();
327  }
328#endif
329}
330
331#pragma mark - ARDSignalingChannelDelegate
332
333- (void)channel:(id<ARDSignalingChannel>)channel
334    didReceiveMessage:(ARDSignalingMessage *)message {
335  switch (message.type) {
336    case kARDSignalingMessageTypeOffer:
337    case kARDSignalingMessageTypeAnswer:
338      // Offers and answers must be processed before any other message, so we
339      // place them at the front of the queue.
340      _hasReceivedSdp = YES;
341      [_messageQueue insertObject:message atIndex:0];
342      break;
343    case kARDSignalingMessageTypeCandidate:
344    case kARDSignalingMessageTypeCandidateRemoval:
345      [_messageQueue addObject:message];
346      break;
347    case kARDSignalingMessageTypeBye:
348      // Disconnects can be processed immediately.
349      [self processSignalingMessage:message];
350      return;
351  }
352  [self drainMessageQueueIfReady];
353}
354
355- (void)channel:(id<ARDSignalingChannel>)channel
356    didChangeState:(ARDSignalingChannelState)state {
357  switch (state) {
358    case kARDSignalingChannelStateOpen:
359      break;
360    case kARDSignalingChannelStateRegistered:
361      break;
362    case kARDSignalingChannelStateClosed:
363    case kARDSignalingChannelStateError:
364      // TODO(tkchin): reconnection scenarios. Right now we just disconnect
365      // completely if the websocket connection fails.
366      [self disconnect];
367      break;
368  }
369}
370
371#pragma mark - RTC_OBJC_TYPE(RTCPeerConnectionDelegate)
372// Callbacks for this delegate occur on non-main thread and need to be
373// dispatched back to main queue as needed.
374
375- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
376    didChangeSignalingState:(RTCSignalingState)stateChanged {
377  RTCLog(@"Signaling state changed: %ld", (long)stateChanged);
378}
379
380- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
381          didAddStream:(RTC_OBJC_TYPE(RTCMediaStream) *)stream {
382  RTCLog(@"Stream with %lu video tracks and %lu audio tracks was added.",
383         (unsigned long)stream.videoTracks.count,
384         (unsigned long)stream.audioTracks.count);
385}
386
387- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
388    didStartReceivingOnTransceiver:(RTC_OBJC_TYPE(RTCRtpTransceiver) *)transceiver {
389  RTC_OBJC_TYPE(RTCMediaStreamTrack) *track = transceiver.receiver.track;
390  RTCLog(@"Now receiving %@ on track %@.", track.kind, track.trackId);
391}
392
393- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
394       didRemoveStream:(RTC_OBJC_TYPE(RTCMediaStream) *)stream {
395  RTCLog(@"Stream was removed.");
396}
397
398- (void)peerConnectionShouldNegotiate:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection {
399  RTCLog(@"WARNING: Renegotiation needed but unimplemented.");
400}
401
402- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
403    didChangeIceConnectionState:(RTCIceConnectionState)newState {
404  RTCLog(@"ICE state changed: %ld", (long)newState);
405  dispatch_async(dispatch_get_main_queue(), ^{
406    [self.delegate appClient:self didChangeConnectionState:newState];
407  });
408}
409
410- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
411    didChangeConnectionState:(RTCPeerConnectionState)newState {
412  RTCLog(@"ICE+DTLS state changed: %ld", (long)newState);
413}
414
415- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
416    didChangeIceGatheringState:(RTCIceGatheringState)newState {
417  RTCLog(@"ICE gathering state changed: %ld", (long)newState);
418}
419
420- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
421    didGenerateIceCandidate:(RTC_OBJC_TYPE(RTCIceCandidate) *)candidate {
422  dispatch_async(dispatch_get_main_queue(), ^{
423    ARDICECandidateMessage *message =
424        [[ARDICECandidateMessage alloc] initWithCandidate:candidate];
425    [self sendSignalingMessage:message];
426  });
427}
428
429- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
430    didFailToGatherIceCandidate:(RTC_OBJC_TYPE(RTCIceCandidateErrorEvent) *)event {
431  RTCLog(@"Failed to gather ICE candidate. address: %@, port: %d, url: %@, errorCode: %d, "
432         @"errorText: %@",
433         event.address,
434         event.port,
435         event.url,
436         event.errorCode,
437         event.errorText);
438}
439
440- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
441    didRemoveIceCandidates:(NSArray<RTC_OBJC_TYPE(RTCIceCandidate) *> *)candidates {
442  dispatch_async(dispatch_get_main_queue(), ^{
443    ARDICECandidateRemovalMessage *message =
444        [[ARDICECandidateRemovalMessage alloc]
445            initWithRemovedCandidates:candidates];
446    [self sendSignalingMessage:message];
447  });
448}
449
450- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
451     didChangeLocalCandidate:(RTC_OBJC_TYPE(RTCIceCandidate) *)local
452    didChangeRemoteCandidate:(RTC_OBJC_TYPE(RTCIceCandidate) *)remote
453              lastReceivedMs:(int)lastDataReceivedMs
454               didHaveReason:(NSString *)reason {
455  RTCLog(@"ICE candidate pair changed because: %@", reason);
456}
457
458- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
459    didOpenDataChannel:(RTC_OBJC_TYPE(RTCDataChannel) *)dataChannel {
460}
461
462#pragma mark - RTCSessionDescriptionDelegate
463// Callbacks for this delegate occur on non-main thread and need to be
464// dispatched back to main queue as needed.
465
466- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
467    didCreateSessionDescription:(RTC_OBJC_TYPE(RTCSessionDescription) *)sdp
468                          error:(NSError *)error {
469  dispatch_async(dispatch_get_main_queue(), ^{
470    if (error) {
471      RTCLogError(@"Failed to create session description. Error: %@", error);
472      [self disconnect];
473      NSDictionary *userInfo = @{
474        NSLocalizedDescriptionKey: @"Failed to create session description.",
475      };
476      NSError *sdpError =
477          [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
478                                     code:kARDAppClientErrorCreateSDP
479                                 userInfo:userInfo];
480      [self.delegate appClient:self didError:sdpError];
481      return;
482    }
483    __weak ARDAppClient *weakSelf = self;
484    [self.peerConnection setLocalDescription:sdp
485                           completionHandler:^(NSError *error) {
486                             ARDAppClient *strongSelf = weakSelf;
487                             [strongSelf peerConnection:strongSelf.peerConnection
488                                 didSetSessionDescriptionWithError:error];
489                           }];
490    ARDSessionDescriptionMessage *message =
491        [[ARDSessionDescriptionMessage alloc] initWithDescription:sdp];
492    [self sendSignalingMessage:message];
493    [self setMaxBitrateForPeerConnectionVideoSender];
494  });
495}
496
497- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
498    didSetSessionDescriptionWithError:(NSError *)error {
499  dispatch_async(dispatch_get_main_queue(), ^{
500    if (error) {
501      RTCLogError(@"Failed to set session description. Error: %@", error);
502      [self disconnect];
503      NSDictionary *userInfo = @{
504        NSLocalizedDescriptionKey: @"Failed to set session description.",
505      };
506      NSError *sdpError =
507          [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
508                                     code:kARDAppClientErrorSetSDP
509                                 userInfo:userInfo];
510      [self.delegate appClient:self didError:sdpError];
511      return;
512    }
513    // If we're answering and we've just set the remote offer we need to create
514    // an answer and set the local description.
515    if (!self.isInitiator && !self.peerConnection.localDescription) {
516      RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = [self defaultAnswerConstraints];
517      __weak ARDAppClient *weakSelf = self;
518      [self.peerConnection
519          answerForConstraints:constraints
520             completionHandler:^(RTC_OBJC_TYPE(RTCSessionDescription) * sdp, NSError * error) {
521               ARDAppClient *strongSelf = weakSelf;
522               [strongSelf peerConnection:strongSelf.peerConnection
523                   didCreateSessionDescription:sdp
524                                         error:error];
525             }];
526    }
527  });
528}
529
530#pragma mark - Private
531
532#if defined(WEBRTC_IOS)
533
534- (NSString *)documentsFilePathForFileName:(NSString *)fileName {
535  NSParameterAssert(fileName.length);
536  NSArray *paths = NSSearchPathForDirectoriesInDomains(
537      NSDocumentDirectory, NSUserDomainMask, YES);
538  NSString *documentsDirPath = paths.firstObject;
539  NSString *filePath =
540      [documentsDirPath stringByAppendingPathComponent:fileName];
541  return filePath;
542}
543
544#endif
545
546- (BOOL)hasJoinedRoomServerRoom {
547  return _clientId.length;
548}
549
550// Begins the peer connection connection process if we have both joined a room
551// on the room server and tried to obtain a TURN server. Otherwise does nothing.
552// A peer connection object will be created with a stream that contains local
553// audio and video capture. If this client is the caller, an offer is created as
554// well, otherwise the client will wait for an offer to arrive.
555- (void)startSignalingIfReady {
556  if (!_isTurnComplete || !self.hasJoinedRoomServerRoom) {
557    return;
558  }
559  self.state = kARDAppClientStateConnected;
560
561  // Create peer connection.
562  RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = [self defaultPeerConnectionConstraints];
563  RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init];
564  RTC_OBJC_TYPE(RTCCertificate) *pcert = [RTC_OBJC_TYPE(RTCCertificate)
565      generateCertificateWithParams:@{@"expires" : @100000, @"name" : @"RSASSA-PKCS1-v1_5"}];
566  config.iceServers = _iceServers;
567  config.sdpSemantics = RTCSdpSemanticsUnifiedPlan;
568  config.certificate = pcert;
569
570  _peerConnection = [_factory peerConnectionWithConfiguration:config
571                                                  constraints:constraints
572                                                     delegate:self];
573  // Create AV senders.
574  [self createMediaSenders];
575  if (_isInitiator) {
576    // Send offer.
577    __weak ARDAppClient *weakSelf = self;
578    [_peerConnection
579        offerForConstraints:[self defaultOfferConstraints]
580          completionHandler:^(RTC_OBJC_TYPE(RTCSessionDescription) * sdp, NSError * error) {
581            ARDAppClient *strongSelf = weakSelf;
582            [strongSelf peerConnection:strongSelf.peerConnection
583                didCreateSessionDescription:sdp
584                                      error:error];
585          }];
586  } else {
587    // Check if we've received an offer.
588    [self drainMessageQueueIfReady];
589  }
590#if defined(WEBRTC_IOS)
591  // Start event log.
592  if (kARDAppClientEnableRtcEventLog) {
593    NSString *filePath = [self documentsFilePathForFileName:@"webrtc-rtceventlog"];
594    if (![_peerConnection startRtcEventLogWithFilePath:filePath
595                                 maxSizeInBytes:kARDAppClientRtcEventLogMaxSizeInBytes]) {
596      RTCLogError(@"Failed to start event logging.");
597    }
598  }
599
600  // Start aecdump diagnostic recording.
601  if ([_settings currentCreateAecDumpSettingFromStore]) {
602    NSString *filePath = [self documentsFilePathForFileName:@"webrtc-audio.aecdump"];
603    if (![_factory startAecDumpWithFilePath:filePath
604                             maxSizeInBytes:kARDAppClientAecDumpMaxSizeInBytes]) {
605      RTCLogError(@"Failed to start aec dump.");
606    }
607  }
608#endif
609}
610
611// Processes the messages that we've received from the room server and the
612// signaling channel. The offer or answer message must be processed before other
613// signaling messages, however they can arrive out of order. Hence, this method
614// only processes pending messages if there is a peer connection object and
615// if we have received either an offer or answer.
616- (void)drainMessageQueueIfReady {
617  if (!_peerConnection || !_hasReceivedSdp) {
618    return;
619  }
620  for (ARDSignalingMessage *message in _messageQueue) {
621    [self processSignalingMessage:message];
622  }
623  [_messageQueue removeAllObjects];
624}
625
626// Processes the given signaling message based on its type.
627- (void)processSignalingMessage:(ARDSignalingMessage *)message {
628  NSParameterAssert(_peerConnection ||
629      message.type == kARDSignalingMessageTypeBye);
630  switch (message.type) {
631    case kARDSignalingMessageTypeOffer:
632    case kARDSignalingMessageTypeAnswer: {
633      ARDSessionDescriptionMessage *sdpMessage =
634          (ARDSessionDescriptionMessage *)message;
635      RTC_OBJC_TYPE(RTCSessionDescription) *description = sdpMessage.sessionDescription;
636      __weak ARDAppClient *weakSelf = self;
637      [_peerConnection setRemoteDescription:description
638                          completionHandler:^(NSError *error) {
639                            ARDAppClient *strongSelf = weakSelf;
640                            [strongSelf peerConnection:strongSelf.peerConnection
641                                didSetSessionDescriptionWithError:error];
642                          }];
643      break;
644    }
645    case kARDSignalingMessageTypeCandidate: {
646      ARDICECandidateMessage *candidateMessage =
647          (ARDICECandidateMessage *)message;
648      __weak ARDAppClient *weakSelf = self;
649      [_peerConnection addIceCandidate:candidateMessage.candidate
650                     completionHandler:^(NSError *error) {
651                       ARDAppClient *strongSelf = weakSelf;
652                       if (error) {
653                         [strongSelf.delegate appClient:strongSelf didError:error];
654                       }
655                     }];
656      break;
657    }
658    case kARDSignalingMessageTypeCandidateRemoval: {
659      ARDICECandidateRemovalMessage *candidateMessage =
660          (ARDICECandidateRemovalMessage *)message;
661      [_peerConnection removeIceCandidates:candidateMessage.candidates];
662      break;
663    }
664    case kARDSignalingMessageTypeBye:
665      // Other client disconnected.
666      // TODO(tkchin): support waiting in room for next client. For now just
667      // disconnect.
668      [self disconnect];
669      break;
670  }
671}
672
673// Sends a signaling message to the other client. The caller will send messages
674// through the room server, whereas the callee will send messages over the
675// signaling channel.
676- (void)sendSignalingMessage:(ARDSignalingMessage *)message {
677  if (_isInitiator) {
678    __weak ARDAppClient *weakSelf = self;
679    [_roomServerClient sendMessage:message
680                         forRoomId:_roomId
681                          clientId:_clientId
682                 completionHandler:^(ARDMessageResponse *response,
683                                     NSError *error) {
684      ARDAppClient *strongSelf = weakSelf;
685      if (error) {
686        [strongSelf.delegate appClient:strongSelf didError:error];
687        return;
688      }
689      NSError *messageError =
690          [[strongSelf class] errorForMessageResultType:response.result];
691      if (messageError) {
692        [strongSelf.delegate appClient:strongSelf didError:messageError];
693        return;
694      }
695    }];
696  } else {
697    [_channel sendMessage:message];
698  }
699}
700
701- (void)setMaxBitrateForPeerConnectionVideoSender {
702  for (RTC_OBJC_TYPE(RTCRtpSender) * sender in _peerConnection.senders) {
703    if (sender.track != nil) {
704      if ([sender.track.kind isEqualToString:kARDVideoTrackKind]) {
705        [self setMaxBitrate:[_settings currentMaxBitrateSettingFromStore] forVideoSender:sender];
706      }
707    }
708  }
709}
710
711- (void)setMaxBitrate:(NSNumber *)maxBitrate forVideoSender:(RTC_OBJC_TYPE(RTCRtpSender) *)sender {
712  if (maxBitrate.intValue <= 0) {
713    return;
714  }
715
716  RTC_OBJC_TYPE(RTCRtpParameters) *parametersToModify = sender.parameters;
717  for (RTC_OBJC_TYPE(RTCRtpEncodingParameters) * encoding in parametersToModify.encodings) {
718    encoding.maxBitrateBps = @(maxBitrate.intValue * kKbpsMultiplier);
719  }
720  [sender setParameters:parametersToModify];
721}
722
723- (RTC_OBJC_TYPE(RTCRtpTransceiver) *)videoTransceiver {
724  for (RTC_OBJC_TYPE(RTCRtpTransceiver) * transceiver in _peerConnection.transceivers) {
725    if (transceiver.mediaType == RTCRtpMediaTypeVideo) {
726      return transceiver;
727    }
728  }
729  return nil;
730}
731
732- (void)createMediaSenders {
733  RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = [self defaultMediaAudioConstraints];
734  RTC_OBJC_TYPE(RTCAudioSource) *source = [_factory audioSourceWithConstraints:constraints];
735  RTC_OBJC_TYPE(RTCAudioTrack) *track = [_factory audioTrackWithSource:source
736                                                               trackId:kARDAudioTrackId];
737  [_peerConnection addTrack:track streamIds:@[ kARDMediaStreamId ]];
738  _localVideoTrack = [self createLocalVideoTrack];
739  if (_localVideoTrack) {
740    [_peerConnection addTrack:_localVideoTrack streamIds:@[ kARDMediaStreamId ]];
741    [_delegate appClient:self didReceiveLocalVideoTrack:_localVideoTrack];
742    // We can set up rendering for the remote track right away since the transceiver already has an
743    // RTC_OBJC_TYPE(RTCRtpReceiver) with a track. The track will automatically get unmuted and
744    // produce frames once RTP is received.
745    RTC_OBJC_TYPE(RTCVideoTrack) *track =
746        (RTC_OBJC_TYPE(RTCVideoTrack) *)([self videoTransceiver].receiver.track);
747    [_delegate appClient:self didReceiveRemoteVideoTrack:track];
748  }
749}
750
751- (RTC_OBJC_TYPE(RTCVideoTrack) *)createLocalVideoTrack {
752  if ([_settings currentAudioOnlySettingFromStore]) {
753    return nil;
754  }
755
756  RTC_OBJC_TYPE(RTCVideoSource) *source = [_factory videoSource];
757
758#if !TARGET_IPHONE_SIMULATOR
759  if (self.isBroadcast) {
760    ARDExternalSampleCapturer *capturer =
761        [[ARDExternalSampleCapturer alloc] initWithDelegate:source];
762    [_delegate appClient:self didCreateLocalExternalSampleCapturer:capturer];
763  } else {
764    RTC_OBJC_TYPE(RTCCameraVideoCapturer) *capturer =
765        [[RTC_OBJC_TYPE(RTCCameraVideoCapturer) alloc] initWithDelegate:source];
766    [_delegate appClient:self didCreateLocalCapturer:capturer];
767  }
768#else
769#if defined(__IPHONE_11_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0)
770  if (@available(iOS 10, *)) {
771    RTC_OBJC_TYPE(RTCFileVideoCapturer) *fileCapturer =
772        [[RTC_OBJC_TYPE(RTCFileVideoCapturer) alloc] initWithDelegate:source];
773    [_delegate appClient:self didCreateLocalFileCapturer:fileCapturer];
774  }
775#endif
776#endif
777
778  return [_factory videoTrackWithSource:source trackId:kARDVideoTrackId];
779}
780
781#pragma mark - Collider methods
782
783- (void)registerWithColliderIfReady {
784  if (!self.hasJoinedRoomServerRoom) {
785    return;
786  }
787  // Open WebSocket connection.
788  if (!_channel) {
789    _channel =
790        [[ARDWebSocketChannel alloc] initWithURL:_websocketURL
791                                         restURL:_websocketRestURL
792                                        delegate:self];
793    if (_isLoopback) {
794      _loopbackChannel =
795          [[ARDLoopbackWebSocketChannel alloc] initWithURL:_websocketURL
796                                                   restURL:_websocketRestURL];
797    }
798  }
799  [_channel registerForRoomId:_roomId clientId:_clientId];
800  if (_isLoopback) {
801    [_loopbackChannel registerForRoomId:_roomId clientId:@"LOOPBACK_CLIENT_ID"];
802  }
803}
804
805#pragma mark - Defaults
806
807- (RTC_OBJC_TYPE(RTCMediaConstraints) *)defaultMediaAudioConstraints {
808  NSDictionary *mandatoryConstraints = @{};
809  RTC_OBJC_TYPE(RTCMediaConstraints) *constraints =
810      [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:mandatoryConstraints
811                                                           optionalConstraints:nil];
812  return constraints;
813}
814
815- (RTC_OBJC_TYPE(RTCMediaConstraints) *)defaultAnswerConstraints {
816  return [self defaultOfferConstraints];
817}
818
819- (RTC_OBJC_TYPE(RTCMediaConstraints) *)defaultOfferConstraints {
820  NSDictionary *mandatoryConstraints = @{
821    @"OfferToReceiveAudio" : @"true",
822    @"OfferToReceiveVideo" : @"true"
823  };
824  RTC_OBJC_TYPE(RTCMediaConstraints) *constraints =
825      [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:mandatoryConstraints
826                                                           optionalConstraints:nil];
827  return constraints;
828}
829
830- (RTC_OBJC_TYPE(RTCMediaConstraints) *)defaultPeerConnectionConstraints {
831  if (_defaultPeerConnectionConstraints) {
832    return _defaultPeerConnectionConstraints;
833  }
834  NSString *value = _isLoopback ? @"false" : @"true";
835  NSDictionary *optionalConstraints = @{ @"DtlsSrtpKeyAgreement" : value };
836  RTC_OBJC_TYPE(RTCMediaConstraints) *constraints =
837      [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:nil
838                                                           optionalConstraints:optionalConstraints];
839  return constraints;
840}
841
842#pragma mark - Errors
843
844+ (NSError *)errorForJoinResultType:(ARDJoinResultType)resultType {
845  NSError *error = nil;
846  switch (resultType) {
847    case kARDJoinResultTypeSuccess:
848      break;
849    case kARDJoinResultTypeUnknown: {
850      error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
851                                         code:kARDAppClientErrorUnknown
852                                     userInfo:@{
853        NSLocalizedDescriptionKey: @"Unknown error.",
854      }];
855      break;
856    }
857    case kARDJoinResultTypeFull: {
858      error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
859                                         code:kARDAppClientErrorRoomFull
860                                     userInfo:@{
861        NSLocalizedDescriptionKey: @"Room is full.",
862      }];
863      break;
864    }
865  }
866  return error;
867}
868
869+ (NSError *)errorForMessageResultType:(ARDMessageResultType)resultType {
870  NSError *error = nil;
871  switch (resultType) {
872    case kARDMessageResultTypeSuccess:
873      break;
874    case kARDMessageResultTypeUnknown:
875      error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
876                                         code:kARDAppClientErrorUnknown
877                                     userInfo:@{
878        NSLocalizedDescriptionKey: @"Unknown error.",
879      }];
880      break;
881    case kARDMessageResultTypeInvalidClient:
882      error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
883                                         code:kARDAppClientErrorInvalidClient
884                                     userInfo:@{
885        NSLocalizedDescriptionKey: @"Invalid client.",
886      }];
887      break;
888    case kARDMessageResultTypeInvalidRoom:
889      error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
890                                         code:kARDAppClientErrorInvalidRoom
891                                     userInfo:@{
892        NSLocalizedDescriptionKey: @"Invalid room.",
893      }];
894      break;
895  }
896  return error;
897}
898
899@end
900