• 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 <WebRTC/RTCAudioTrack.h>
14#import <WebRTC/RTCCameraVideoCapturer.h>
15#import <WebRTC/RTCConfiguration.h>
16#import <WebRTC/RTCDefaultVideoDecoderFactory.h>
17#import <WebRTC/RTCDefaultVideoEncoderFactory.h>
18#import <WebRTC/RTCFileLogger.h>
19#import <WebRTC/RTCFileVideoCapturer.h>
20#import <WebRTC/RTCIceServer.h>
21#import <WebRTC/RTCLogging.h>
22#import <WebRTC/RTCMediaConstraints.h>
23#import <WebRTC/RTCMediaStream.h>
24#import <WebRTC/RTCPeerConnectionFactory.h>
25#import <WebRTC/RTCRtpSender.h>
26#import <WebRTC/RTCRtpTransceiver.h>
27#import <WebRTC/RTCTracing.h>
28#import <WebRTC/RTCVideoSource.h>
29#import <WebRTC/RTCVideoTrack.h>
30
31#import "ARDAppEngineClient.h"
32#import "ARDExternalSampleCapturer.h"
33#import "ARDJoinResponse.h"
34#import "ARDMessageResponse.h"
35#import "ARDSettingsModel.h"
36#import "ARDSignalingMessage.h"
37#import "ARDTURNClient+Internal.h"
38#import "ARDUtilities.h"
39#import "ARDWebSocketChannel.h"
40#import "RTCIceCandidate+JSON.h"
41#import "RTCSessionDescription+JSON.h"
42
43static NSString * const kARDIceServerRequestUrl = @"https://appr.tc/params";
44
45static NSString * const kARDAppClientErrorDomain = @"ARDAppClient";
46static NSInteger const kARDAppClientErrorUnknown = -1;
47static NSInteger const kARDAppClientErrorRoomFull = -2;
48static NSInteger const kARDAppClientErrorCreateSDP = -3;
49static NSInteger const kARDAppClientErrorSetSDP = -4;
50static NSInteger const kARDAppClientErrorInvalidClient = -5;
51static NSInteger const kARDAppClientErrorInvalidRoom = -6;
52static NSString * const kARDMediaStreamId = @"ARDAMS";
53static NSString * const kARDAudioTrackId = @"ARDAMSa0";
54static NSString * const kARDVideoTrackId = @"ARDAMSv0";
55static NSString * const kARDVideoTrackKind = @"video";
56
57// TODO(tkchin): Add these as UI options.
58#if defined(WEBRTC_IOS)
59static BOOL const kARDAppClientEnableTracing = NO;
60static BOOL const kARDAppClientEnableRtcEventLog = YES;
61static int64_t const kARDAppClientAecDumpMaxSizeInBytes = 5e6;  // 5 MB.
62static int64_t const kARDAppClientRtcEventLogMaxSizeInBytes = 5e6;  // 5 MB.
63#endif
64static int const kKbpsMultiplier = 1000;
65
66// We need a proxy to NSTimer because it causes a strong retain cycle. When
67// using the proxy, |invalidate| must be called before it properly deallocs.
68@interface ARDTimerProxy : NSObject
69
70- (instancetype)initWithInterval:(NSTimeInterval)interval
71                         repeats:(BOOL)repeats
72                    timerHandler:(void (^)(void))timerHandler;
73- (void)invalidate;
74
75@end
76
77@implementation ARDTimerProxy {
78  NSTimer *_timer;
79  void (^_timerHandler)(void);
80}
81
82- (instancetype)initWithInterval:(NSTimeInterval)interval
83                         repeats:(BOOL)repeats
84                    timerHandler:(void (^)(void))timerHandler {
85  NSParameterAssert(timerHandler);
86  if (self = [super init]) {
87    _timerHandler = timerHandler;
88    _timer = [NSTimer scheduledTimerWithTimeInterval:interval
89                                              target:self
90                                            selector:@selector(timerDidFire:)
91                                            userInfo:nil
92                                             repeats:repeats];
93  }
94  return self;
95}
96
97- (void)invalidate {
98  [_timer invalidate];
99}
100
101- (void)timerDidFire:(NSTimer *)timer {
102  _timerHandler();
103}
104
105@end
106
107@implementation ARDAppClient {
108  RTC_OBJC_TYPE(RTCFileLogger) * _fileLogger;
109  ARDTimerProxy *_statsTimer;
110  ARDSettingsModel *_settings;
111  RTC_OBJC_TYPE(RTCVideoTrack) * _localVideoTrack;
112}
113
114@synthesize shouldGetStats = _shouldGetStats;
115@synthesize state = _state;
116@synthesize delegate = _delegate;
117@synthesize roomServerClient = _roomServerClient;
118@synthesize channel = _channel;
119@synthesize loopbackChannel = _loopbackChannel;
120@synthesize turnClient = _turnClient;
121@synthesize peerConnection = _peerConnection;
122@synthesize factory = _factory;
123@synthesize messageQueue = _messageQueue;
124@synthesize isTurnComplete = _isTurnComplete;
125@synthesize hasReceivedSdp  = _hasReceivedSdp;
126@synthesize roomId = _roomId;
127@synthesize clientId = _clientId;
128@synthesize isInitiator = _isInitiator;
129@synthesize iceServers = _iceServers;
130@synthesize webSocketURL = _websocketURL;
131@synthesize webSocketRestURL = _websocketRestURL;
132@synthesize defaultPeerConnectionConstraints =
133    _defaultPeerConnectionConstraints;
134@synthesize isLoopback = _isLoopback;
135@synthesize broadcast = _broadcast;
136
137- (instancetype)init {
138  return [self initWithDelegate:nil];
139}
140
141- (instancetype)initWithDelegate:(id<ARDAppClientDelegate>)delegate {
142  if (self = [super init]) {
143    _roomServerClient = [[ARDAppEngineClient alloc] init];
144    _delegate = delegate;
145    NSURL *turnRequestURL = [NSURL URLWithString:kARDIceServerRequestUrl];
146    _turnClient = [[ARDTURNClient alloc] initWithURL:turnRequestURL];
147    [self configure];
148  }
149  return self;
150}
151
152// TODO(tkchin): Provide signaling channel factory interface so we can recreate
153// channel if we need to on network failure. Also, make this the default public
154// constructor.
155- (instancetype)initWithRoomServerClient:(id<ARDRoomServerClient>)rsClient
156                        signalingChannel:(id<ARDSignalingChannel>)channel
157                              turnClient:(id<ARDTURNClient>)turnClient
158                                delegate:(id<ARDAppClientDelegate>)delegate {
159  NSParameterAssert(rsClient);
160  NSParameterAssert(channel);
161  NSParameterAssert(turnClient);
162  if (self = [super init]) {
163    _roomServerClient = rsClient;
164    _channel = channel;
165    _turnClient = turnClient;
166    _delegate = delegate;
167    [self configure];
168  }
169  return self;
170}
171
172- (void)configure {
173  _messageQueue = [NSMutableArray array];
174  _iceServers = [NSMutableArray array];
175  _fileLogger = [[RTC_OBJC_TYPE(RTCFileLogger) alloc] init];
176  [_fileLogger start];
177}
178
179- (void)dealloc {
180  self.shouldGetStats = NO;
181  [self disconnect];
182}
183
184- (void)setShouldGetStats:(BOOL)shouldGetStats {
185  if (_shouldGetStats == shouldGetStats) {
186    return;
187  }
188  if (shouldGetStats) {
189    __weak ARDAppClient *weakSelf = self;
190    _statsTimer = [[ARDTimerProxy alloc] initWithInterval:1
191                                                  repeats:YES
192                                             timerHandler:^{
193      ARDAppClient *strongSelf = weakSelf;
194      [strongSelf.peerConnection statsForTrack:nil
195                              statsOutputLevel:RTCStatsOutputLevelDebug
196                             completionHandler:^(NSArray *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    didRemoveIceCandidates:(NSArray<RTC_OBJC_TYPE(RTCIceCandidate) *> *)candidates {
431  dispatch_async(dispatch_get_main_queue(), ^{
432    ARDICECandidateRemovalMessage *message =
433        [[ARDICECandidateRemovalMessage alloc]
434            initWithRemovedCandidates:candidates];
435    [self sendSignalingMessage:message];
436  });
437}
438
439- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
440     didChangeLocalCandidate:(RTC_OBJC_TYPE(RTCIceCandidate) *)local
441    didChangeRemoteCandidate:(RTC_OBJC_TYPE(RTCIceCandidate) *)remote
442              lastReceivedMs:(int)lastDataReceivedMs
443               didHaveReason:(NSString *)reason {
444  RTCLog(@"ICE candidate pair changed because: %@", reason);
445}
446
447- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
448    didOpenDataChannel:(RTC_OBJC_TYPE(RTCDataChannel) *)dataChannel {
449}
450
451#pragma mark - RTCSessionDescriptionDelegate
452// Callbacks for this delegate occur on non-main thread and need to be
453// dispatched back to main queue as needed.
454
455- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
456    didCreateSessionDescription:(RTC_OBJC_TYPE(RTCSessionDescription) *)sdp
457                          error:(NSError *)error {
458  dispatch_async(dispatch_get_main_queue(), ^{
459    if (error) {
460      RTCLogError(@"Failed to create session description. Error: %@", error);
461      [self disconnect];
462      NSDictionary *userInfo = @{
463        NSLocalizedDescriptionKey: @"Failed to create session description.",
464      };
465      NSError *sdpError =
466          [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
467                                     code:kARDAppClientErrorCreateSDP
468                                 userInfo:userInfo];
469      [self.delegate appClient:self didError:sdpError];
470      return;
471    }
472    __weak ARDAppClient *weakSelf = self;
473    [self.peerConnection setLocalDescription:sdp
474                           completionHandler:^(NSError *error) {
475                             ARDAppClient *strongSelf = weakSelf;
476                             [strongSelf peerConnection:strongSelf.peerConnection
477                                 didSetSessionDescriptionWithError:error];
478                           }];
479    ARDSessionDescriptionMessage *message =
480        [[ARDSessionDescriptionMessage alloc] initWithDescription:sdp];
481    [self sendSignalingMessage:message];
482    [self setMaxBitrateForPeerConnectionVideoSender];
483  });
484}
485
486- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
487    didSetSessionDescriptionWithError:(NSError *)error {
488  dispatch_async(dispatch_get_main_queue(), ^{
489    if (error) {
490      RTCLogError(@"Failed to set session description. Error: %@", error);
491      [self disconnect];
492      NSDictionary *userInfo = @{
493        NSLocalizedDescriptionKey: @"Failed to set session description.",
494      };
495      NSError *sdpError =
496          [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
497                                     code:kARDAppClientErrorSetSDP
498                                 userInfo:userInfo];
499      [self.delegate appClient:self didError:sdpError];
500      return;
501    }
502    // If we're answering and we've just set the remote offer we need to create
503    // an answer and set the local description.
504    if (!self.isInitiator && !self.peerConnection.localDescription) {
505      RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = [self defaultAnswerConstraints];
506      __weak ARDAppClient *weakSelf = self;
507      [self.peerConnection
508          answerForConstraints:constraints
509             completionHandler:^(RTC_OBJC_TYPE(RTCSessionDescription) * sdp, NSError * error) {
510               ARDAppClient *strongSelf = weakSelf;
511               [strongSelf peerConnection:strongSelf.peerConnection
512                   didCreateSessionDescription:sdp
513                                         error:error];
514             }];
515    }
516  });
517}
518
519#pragma mark - Private
520
521#if defined(WEBRTC_IOS)
522
523- (NSString *)documentsFilePathForFileName:(NSString *)fileName {
524  NSParameterAssert(fileName.length);
525  NSArray *paths = NSSearchPathForDirectoriesInDomains(
526      NSDocumentDirectory, NSUserDomainMask, YES);
527  NSString *documentsDirPath = paths.firstObject;
528  NSString *filePath =
529      [documentsDirPath stringByAppendingPathComponent:fileName];
530  return filePath;
531}
532
533#endif
534
535- (BOOL)hasJoinedRoomServerRoom {
536  return _clientId.length;
537}
538
539// Begins the peer connection connection process if we have both joined a room
540// on the room server and tried to obtain a TURN server. Otherwise does nothing.
541// A peer connection object will be created with a stream that contains local
542// audio and video capture. If this client is the caller, an offer is created as
543// well, otherwise the client will wait for an offer to arrive.
544- (void)startSignalingIfReady {
545  if (!_isTurnComplete || !self.hasJoinedRoomServerRoom) {
546    return;
547  }
548  self.state = kARDAppClientStateConnected;
549
550  // Create peer connection.
551  RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = [self defaultPeerConnectionConstraints];
552  RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init];
553  RTC_OBJC_TYPE(RTCCertificate) *pcert = [RTC_OBJC_TYPE(RTCCertificate)
554      generateCertificateWithParams:@{@"expires" : @100000, @"name" : @"RSASSA-PKCS1-v1_5"}];
555  config.iceServers = _iceServers;
556  config.sdpSemantics = RTCSdpSemanticsUnifiedPlan;
557  config.certificate = pcert;
558
559  _peerConnection = [_factory peerConnectionWithConfiguration:config
560                                                  constraints:constraints
561                                                     delegate:self];
562  // Create AV senders.
563  [self createMediaSenders];
564  if (_isInitiator) {
565    // Send offer.
566    __weak ARDAppClient *weakSelf = self;
567    [_peerConnection
568        offerForConstraints:[self defaultOfferConstraints]
569          completionHandler:^(RTC_OBJC_TYPE(RTCSessionDescription) * sdp, NSError * error) {
570            ARDAppClient *strongSelf = weakSelf;
571            [strongSelf peerConnection:strongSelf.peerConnection
572                didCreateSessionDescription:sdp
573                                      error:error];
574          }];
575  } else {
576    // Check if we've received an offer.
577    [self drainMessageQueueIfReady];
578  }
579#if defined(WEBRTC_IOS)
580  // Start event log.
581  if (kARDAppClientEnableRtcEventLog) {
582    NSString *filePath = [self documentsFilePathForFileName:@"webrtc-rtceventlog"];
583    if (![_peerConnection startRtcEventLogWithFilePath:filePath
584                                 maxSizeInBytes:kARDAppClientRtcEventLogMaxSizeInBytes]) {
585      RTCLogError(@"Failed to start event logging.");
586    }
587  }
588
589  // Start aecdump diagnostic recording.
590  if ([_settings currentCreateAecDumpSettingFromStore]) {
591    NSString *filePath = [self documentsFilePathForFileName:@"webrtc-audio.aecdump"];
592    if (![_factory startAecDumpWithFilePath:filePath
593                             maxSizeInBytes:kARDAppClientAecDumpMaxSizeInBytes]) {
594      RTCLogError(@"Failed to start aec dump.");
595    }
596  }
597#endif
598}
599
600// Processes the messages that we've received from the room server and the
601// signaling channel. The offer or answer message must be processed before other
602// signaling messages, however they can arrive out of order. Hence, this method
603// only processes pending messages if there is a peer connection object and
604// if we have received either an offer or answer.
605- (void)drainMessageQueueIfReady {
606  if (!_peerConnection || !_hasReceivedSdp) {
607    return;
608  }
609  for (ARDSignalingMessage *message in _messageQueue) {
610    [self processSignalingMessage:message];
611  }
612  [_messageQueue removeAllObjects];
613}
614
615// Processes the given signaling message based on its type.
616- (void)processSignalingMessage:(ARDSignalingMessage *)message {
617  NSParameterAssert(_peerConnection ||
618      message.type == kARDSignalingMessageTypeBye);
619  switch (message.type) {
620    case kARDSignalingMessageTypeOffer:
621    case kARDSignalingMessageTypeAnswer: {
622      ARDSessionDescriptionMessage *sdpMessage =
623          (ARDSessionDescriptionMessage *)message;
624      RTC_OBJC_TYPE(RTCSessionDescription) *description = sdpMessage.sessionDescription;
625      __weak ARDAppClient *weakSelf = self;
626      [_peerConnection setRemoteDescription:description
627                          completionHandler:^(NSError *error) {
628                            ARDAppClient *strongSelf = weakSelf;
629                            [strongSelf peerConnection:strongSelf.peerConnection
630                                didSetSessionDescriptionWithError:error];
631                          }];
632      break;
633    }
634    case kARDSignalingMessageTypeCandidate: {
635      ARDICECandidateMessage *candidateMessage =
636          (ARDICECandidateMessage *)message;
637      [_peerConnection addIceCandidate:candidateMessage.candidate];
638      break;
639    }
640    case kARDSignalingMessageTypeCandidateRemoval: {
641      ARDICECandidateRemovalMessage *candidateMessage =
642          (ARDICECandidateRemovalMessage *)message;
643      [_peerConnection removeIceCandidates:candidateMessage.candidates];
644      break;
645    }
646    case kARDSignalingMessageTypeBye:
647      // Other client disconnected.
648      // TODO(tkchin): support waiting in room for next client. For now just
649      // disconnect.
650      [self disconnect];
651      break;
652  }
653}
654
655// Sends a signaling message to the other client. The caller will send messages
656// through the room server, whereas the callee will send messages over the
657// signaling channel.
658- (void)sendSignalingMessage:(ARDSignalingMessage *)message {
659  if (_isInitiator) {
660    __weak ARDAppClient *weakSelf = self;
661    [_roomServerClient sendMessage:message
662                         forRoomId:_roomId
663                          clientId:_clientId
664                 completionHandler:^(ARDMessageResponse *response,
665                                     NSError *error) {
666      ARDAppClient *strongSelf = weakSelf;
667      if (error) {
668        [strongSelf.delegate appClient:strongSelf didError:error];
669        return;
670      }
671      NSError *messageError =
672          [[strongSelf class] errorForMessageResultType:response.result];
673      if (messageError) {
674        [strongSelf.delegate appClient:strongSelf didError:messageError];
675        return;
676      }
677    }];
678  } else {
679    [_channel sendMessage:message];
680  }
681}
682
683- (void)setMaxBitrateForPeerConnectionVideoSender {
684  for (RTC_OBJC_TYPE(RTCRtpSender) * sender in _peerConnection.senders) {
685    if (sender.track != nil) {
686      if ([sender.track.kind isEqualToString:kARDVideoTrackKind]) {
687        [self setMaxBitrate:[_settings currentMaxBitrateSettingFromStore] forVideoSender:sender];
688      }
689    }
690  }
691}
692
693- (void)setMaxBitrate:(NSNumber *)maxBitrate forVideoSender:(RTC_OBJC_TYPE(RTCRtpSender) *)sender {
694  if (maxBitrate.intValue <= 0) {
695    return;
696  }
697
698  RTC_OBJC_TYPE(RTCRtpParameters) *parametersToModify = sender.parameters;
699  for (RTC_OBJC_TYPE(RTCRtpEncodingParameters) * encoding in parametersToModify.encodings) {
700    encoding.maxBitrateBps = @(maxBitrate.intValue * kKbpsMultiplier);
701  }
702  [sender setParameters:parametersToModify];
703}
704
705- (RTC_OBJC_TYPE(RTCRtpTransceiver) *)videoTransceiver {
706  for (RTC_OBJC_TYPE(RTCRtpTransceiver) * transceiver in _peerConnection.transceivers) {
707    if (transceiver.mediaType == RTCRtpMediaTypeVideo) {
708      return transceiver;
709    }
710  }
711  return nil;
712}
713
714- (void)createMediaSenders {
715  RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = [self defaultMediaAudioConstraints];
716  RTC_OBJC_TYPE(RTCAudioSource) *source = [_factory audioSourceWithConstraints:constraints];
717  RTC_OBJC_TYPE(RTCAudioTrack) *track = [_factory audioTrackWithSource:source
718                                                               trackId:kARDAudioTrackId];
719  [_peerConnection addTrack:track streamIds:@[ kARDMediaStreamId ]];
720  _localVideoTrack = [self createLocalVideoTrack];
721  if (_localVideoTrack) {
722    [_peerConnection addTrack:_localVideoTrack streamIds:@[ kARDMediaStreamId ]];
723    [_delegate appClient:self didReceiveLocalVideoTrack:_localVideoTrack];
724    // We can set up rendering for the remote track right away since the transceiver already has an
725    // RTC_OBJC_TYPE(RTCRtpReceiver) with a track. The track will automatically get unmuted and
726    // produce frames once RTP is received.
727    RTC_OBJC_TYPE(RTCVideoTrack) *track =
728        (RTC_OBJC_TYPE(RTCVideoTrack) *)([self videoTransceiver].receiver.track);
729    [_delegate appClient:self didReceiveRemoteVideoTrack:track];
730  }
731}
732
733- (RTC_OBJC_TYPE(RTCVideoTrack) *)createLocalVideoTrack {
734  if ([_settings currentAudioOnlySettingFromStore]) {
735    return nil;
736  }
737
738  RTC_OBJC_TYPE(RTCVideoSource) *source = [_factory videoSource];
739
740#if !TARGET_IPHONE_SIMULATOR
741  if (self.isBroadcast) {
742    ARDExternalSampleCapturer *capturer =
743        [[ARDExternalSampleCapturer alloc] initWithDelegate:source];
744    [_delegate appClient:self didCreateLocalExternalSampleCapturer:capturer];
745  } else {
746    RTC_OBJC_TYPE(RTCCameraVideoCapturer) *capturer =
747        [[RTC_OBJC_TYPE(RTCCameraVideoCapturer) alloc] initWithDelegate:source];
748    [_delegate appClient:self didCreateLocalCapturer:capturer];
749  }
750#else
751#if defined(__IPHONE_11_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0)
752  if (@available(iOS 10, *)) {
753    RTC_OBJC_TYPE(RTCFileVideoCapturer) *fileCapturer =
754        [[RTC_OBJC_TYPE(RTCFileVideoCapturer) alloc] initWithDelegate:source];
755    [_delegate appClient:self didCreateLocalFileCapturer:fileCapturer];
756  }
757#endif
758#endif
759
760  return [_factory videoTrackWithSource:source trackId:kARDVideoTrackId];
761}
762
763#pragma mark - Collider methods
764
765- (void)registerWithColliderIfReady {
766  if (!self.hasJoinedRoomServerRoom) {
767    return;
768  }
769  // Open WebSocket connection.
770  if (!_channel) {
771    _channel =
772        [[ARDWebSocketChannel alloc] initWithURL:_websocketURL
773                                         restURL:_websocketRestURL
774                                        delegate:self];
775    if (_isLoopback) {
776      _loopbackChannel =
777          [[ARDLoopbackWebSocketChannel alloc] initWithURL:_websocketURL
778                                                   restURL:_websocketRestURL];
779    }
780  }
781  [_channel registerForRoomId:_roomId clientId:_clientId];
782  if (_isLoopback) {
783    [_loopbackChannel registerForRoomId:_roomId clientId:@"LOOPBACK_CLIENT_ID"];
784  }
785}
786
787#pragma mark - Defaults
788
789- (RTC_OBJC_TYPE(RTCMediaConstraints) *)defaultMediaAudioConstraints {
790  NSDictionary *mandatoryConstraints = @{};
791  RTC_OBJC_TYPE(RTCMediaConstraints) *constraints =
792      [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:mandatoryConstraints
793                                                           optionalConstraints:nil];
794  return constraints;
795}
796
797- (RTC_OBJC_TYPE(RTCMediaConstraints) *)defaultAnswerConstraints {
798  return [self defaultOfferConstraints];
799}
800
801- (RTC_OBJC_TYPE(RTCMediaConstraints) *)defaultOfferConstraints {
802  NSDictionary *mandatoryConstraints = @{
803    @"OfferToReceiveAudio" : @"true",
804    @"OfferToReceiveVideo" : @"true"
805  };
806  RTC_OBJC_TYPE(RTCMediaConstraints) *constraints =
807      [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:mandatoryConstraints
808                                                           optionalConstraints:nil];
809  return constraints;
810}
811
812- (RTC_OBJC_TYPE(RTCMediaConstraints) *)defaultPeerConnectionConstraints {
813  if (_defaultPeerConnectionConstraints) {
814    return _defaultPeerConnectionConstraints;
815  }
816  NSString *value = _isLoopback ? @"false" : @"true";
817  NSDictionary *optionalConstraints = @{ @"DtlsSrtpKeyAgreement" : value };
818  RTC_OBJC_TYPE(RTCMediaConstraints) *constraints =
819      [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:nil
820                                                           optionalConstraints:optionalConstraints];
821  return constraints;
822}
823
824#pragma mark - Errors
825
826+ (NSError *)errorForJoinResultType:(ARDJoinResultType)resultType {
827  NSError *error = nil;
828  switch (resultType) {
829    case kARDJoinResultTypeSuccess:
830      break;
831    case kARDJoinResultTypeUnknown: {
832      error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
833                                         code:kARDAppClientErrorUnknown
834                                     userInfo:@{
835        NSLocalizedDescriptionKey: @"Unknown error.",
836      }];
837      break;
838    }
839    case kARDJoinResultTypeFull: {
840      error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
841                                         code:kARDAppClientErrorRoomFull
842                                     userInfo:@{
843        NSLocalizedDescriptionKey: @"Room is full.",
844      }];
845      break;
846    }
847  }
848  return error;
849}
850
851+ (NSError *)errorForMessageResultType:(ARDMessageResultType)resultType {
852  NSError *error = nil;
853  switch (resultType) {
854    case kARDMessageResultTypeSuccess:
855      break;
856    case kARDMessageResultTypeUnknown:
857      error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
858                                         code:kARDAppClientErrorUnknown
859                                     userInfo:@{
860        NSLocalizedDescriptionKey: @"Unknown error.",
861      }];
862      break;
863    case kARDMessageResultTypeInvalidClient:
864      error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
865                                         code:kARDAppClientErrorInvalidClient
866                                     userInfo:@{
867        NSLocalizedDescriptionKey: @"Invalid client.",
868      }];
869      break;
870    case kARDMessageResultTypeInvalidRoom:
871      error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
872                                         code:kARDAppClientErrorInvalidRoom
873                                     userInfo:@{
874        NSLocalizedDescriptionKey: @"Invalid room.",
875      }];
876      break;
877  }
878  return error;
879}
880
881@end
882