summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/examples/objc/AppRTCMobile/ARDAppClient.m
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/examples/objc/AppRTCMobile/ARDAppClient.m')
-rw-r--r--third_party/libwebrtc/examples/objc/AppRTCMobile/ARDAppClient.m899
1 files changed, 899 insertions, 0 deletions
diff --git a/third_party/libwebrtc/examples/objc/AppRTCMobile/ARDAppClient.m b/third_party/libwebrtc/examples/objc/AppRTCMobile/ARDAppClient.m
new file mode 100644
index 0000000000..4420972598
--- /dev/null
+++ b/third_party/libwebrtc/examples/objc/AppRTCMobile/ARDAppClient.m
@@ -0,0 +1,899 @@
+/*
+ * Copyright 2014 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#import "ARDAppClient+Internal.h"
+
+#import "sdk/objc/api/peerconnection/RTCAudioTrack.h"
+#import "sdk/objc/api/peerconnection/RTCConfiguration.h"
+#import "sdk/objc/api/peerconnection/RTCFileLogger.h"
+#import "sdk/objc/api/peerconnection/RTCIceCandidateErrorEvent.h"
+#import "sdk/objc/api/peerconnection/RTCIceServer.h"
+#import "sdk/objc/api/peerconnection/RTCMediaConstraints.h"
+#import "sdk/objc/api/peerconnection/RTCMediaStream.h"
+#import "sdk/objc/api/peerconnection/RTCPeerConnectionFactory.h"
+#import "sdk/objc/api/peerconnection/RTCRtpSender.h"
+#import "sdk/objc/api/peerconnection/RTCRtpTransceiver.h"
+#import "sdk/objc/api/peerconnection/RTCTracing.h"
+#import "sdk/objc/api/peerconnection/RTCVideoSource.h"
+#import "sdk/objc/api/peerconnection/RTCVideoTrack.h"
+#import "sdk/objc/base/RTCLogging.h"
+#import "sdk/objc/components/capturer/RTCCameraVideoCapturer.h"
+#import "sdk/objc/components/capturer/RTCFileVideoCapturer.h"
+#import "sdk/objc/components/video_codec/RTCDefaultVideoDecoderFactory.h"
+#import "sdk/objc/components/video_codec/RTCDefaultVideoEncoderFactory.h"
+
+#import "ARDAppEngineClient.h"
+#import "ARDExternalSampleCapturer.h"
+#import "ARDJoinResponse.h"
+#import "ARDMessageResponse.h"
+#import "ARDSettingsModel.h"
+#import "ARDSignalingMessage.h"
+#import "ARDTURNClient+Internal.h"
+#import "ARDUtilities.h"
+#import "ARDWebSocketChannel.h"
+#import "RTCIceCandidate+JSON.h"
+#import "RTCSessionDescription+JSON.h"
+
+static NSString * const kARDIceServerRequestUrl = @"https://appr.tc/params";
+
+static NSString * const kARDAppClientErrorDomain = @"ARDAppClient";
+static NSInteger const kARDAppClientErrorUnknown = -1;
+static NSInteger const kARDAppClientErrorRoomFull = -2;
+static NSInteger const kARDAppClientErrorCreateSDP = -3;
+static NSInteger const kARDAppClientErrorSetSDP = -4;
+static NSInteger const kARDAppClientErrorInvalidClient = -5;
+static NSInteger const kARDAppClientErrorInvalidRoom = -6;
+static NSString * const kARDMediaStreamId = @"ARDAMS";
+static NSString * const kARDAudioTrackId = @"ARDAMSa0";
+static NSString * const kARDVideoTrackId = @"ARDAMSv0";
+static NSString * const kARDVideoTrackKind = @"video";
+
+// TODO(tkchin): Add these as UI options.
+#if defined(WEBRTC_IOS)
+static BOOL const kARDAppClientEnableTracing = NO;
+static BOOL const kARDAppClientEnableRtcEventLog = YES;
+static int64_t const kARDAppClientAecDumpMaxSizeInBytes = 5e6; // 5 MB.
+static int64_t const kARDAppClientRtcEventLogMaxSizeInBytes = 5e6; // 5 MB.
+#endif
+static int const kKbpsMultiplier = 1000;
+
+// We need a proxy to NSTimer because it causes a strong retain cycle. When
+// using the proxy, `invalidate` must be called before it properly deallocs.
+@interface ARDTimerProxy : NSObject
+
+- (instancetype)initWithInterval:(NSTimeInterval)interval
+ repeats:(BOOL)repeats
+ timerHandler:(void (^)(void))timerHandler;
+- (void)invalidate;
+
+@end
+
+@implementation ARDTimerProxy {
+ NSTimer *_timer;
+ void (^_timerHandler)(void);
+}
+
+- (instancetype)initWithInterval:(NSTimeInterval)interval
+ repeats:(BOOL)repeats
+ timerHandler:(void (^)(void))timerHandler {
+ NSParameterAssert(timerHandler);
+ if (self = [super init]) {
+ _timerHandler = timerHandler;
+ _timer = [NSTimer scheduledTimerWithTimeInterval:interval
+ target:self
+ selector:@selector(timerDidFire:)
+ userInfo:nil
+ repeats:repeats];
+ }
+ return self;
+}
+
+- (void)invalidate {
+ [_timer invalidate];
+}
+
+- (void)timerDidFire:(NSTimer *)timer {
+ _timerHandler();
+}
+
+@end
+
+@implementation ARDAppClient {
+ RTC_OBJC_TYPE(RTCFileLogger) * _fileLogger;
+ ARDTimerProxy *_statsTimer;
+ ARDSettingsModel *_settings;
+ RTC_OBJC_TYPE(RTCVideoTrack) * _localVideoTrack;
+}
+
+@synthesize shouldGetStats = _shouldGetStats;
+@synthesize state = _state;
+@synthesize delegate = _delegate;
+@synthesize roomServerClient = _roomServerClient;
+@synthesize channel = _channel;
+@synthesize loopbackChannel = _loopbackChannel;
+@synthesize turnClient = _turnClient;
+@synthesize peerConnection = _peerConnection;
+@synthesize factory = _factory;
+@synthesize messageQueue = _messageQueue;
+@synthesize isTurnComplete = _isTurnComplete;
+@synthesize hasReceivedSdp = _hasReceivedSdp;
+@synthesize roomId = _roomId;
+@synthesize clientId = _clientId;
+@synthesize isInitiator = _isInitiator;
+@synthesize iceServers = _iceServers;
+@synthesize webSocketURL = _websocketURL;
+@synthesize webSocketRestURL = _websocketRestURL;
+@synthesize defaultPeerConnectionConstraints =
+ _defaultPeerConnectionConstraints;
+@synthesize isLoopback = _isLoopback;
+@synthesize broadcast = _broadcast;
+
+- (instancetype)init {
+ return [self initWithDelegate:nil];
+}
+
+- (instancetype)initWithDelegate:(id<ARDAppClientDelegate>)delegate {
+ if (self = [super init]) {
+ _roomServerClient = [[ARDAppEngineClient alloc] init];
+ _delegate = delegate;
+ NSURL *turnRequestURL = [NSURL URLWithString:kARDIceServerRequestUrl];
+ _turnClient = [[ARDTURNClient alloc] initWithURL:turnRequestURL];
+ [self configure];
+ }
+ return self;
+}
+
+// TODO(tkchin): Provide signaling channel factory interface so we can recreate
+// channel if we need to on network failure. Also, make this the default public
+// constructor.
+- (instancetype)initWithRoomServerClient:(id<ARDRoomServerClient>)rsClient
+ signalingChannel:(id<ARDSignalingChannel>)channel
+ turnClient:(id<ARDTURNClient>)turnClient
+ delegate:(id<ARDAppClientDelegate>)delegate {
+ NSParameterAssert(rsClient);
+ NSParameterAssert(channel);
+ NSParameterAssert(turnClient);
+ if (self = [super init]) {
+ _roomServerClient = rsClient;
+ _channel = channel;
+ _turnClient = turnClient;
+ _delegate = delegate;
+ [self configure];
+ }
+ return self;
+}
+
+- (void)configure {
+ _messageQueue = [NSMutableArray array];
+ _iceServers = [NSMutableArray array];
+ _fileLogger = [[RTC_OBJC_TYPE(RTCFileLogger) alloc] init];
+ [_fileLogger start];
+}
+
+- (void)dealloc {
+ self.shouldGetStats = NO;
+ [self disconnect];
+}
+
+- (void)setShouldGetStats:(BOOL)shouldGetStats {
+ if (_shouldGetStats == shouldGetStats) {
+ return;
+ }
+ if (shouldGetStats) {
+ __weak ARDAppClient *weakSelf = self;
+ _statsTimer = [[ARDTimerProxy alloc] initWithInterval:1
+ repeats:YES
+ timerHandler:^{
+ ARDAppClient *strongSelf = weakSelf;
+ [strongSelf.peerConnection statisticsWithCompletionHandler:^(
+ RTC_OBJC_TYPE(RTCStatisticsReport) * stats) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ ARDAppClient *strongSelf = weakSelf;
+ [strongSelf.delegate appClient:strongSelf didGetStats:stats];
+ });
+ }];
+ }];
+ } else {
+ [_statsTimer invalidate];
+ _statsTimer = nil;
+ }
+ _shouldGetStats = shouldGetStats;
+}
+
+- (void)setState:(ARDAppClientState)state {
+ if (_state == state) {
+ return;
+ }
+ _state = state;
+ [_delegate appClient:self didChangeState:_state];
+}
+
+- (void)connectToRoomWithId:(NSString *)roomId
+ settings:(ARDSettingsModel *)settings
+ isLoopback:(BOOL)isLoopback {
+ NSParameterAssert(roomId.length);
+ NSParameterAssert(_state == kARDAppClientStateDisconnected);
+ _settings = settings;
+ _isLoopback = isLoopback;
+ self.state = kARDAppClientStateConnecting;
+
+ RTC_OBJC_TYPE(RTCDefaultVideoDecoderFactory) *decoderFactory =
+ [[RTC_OBJC_TYPE(RTCDefaultVideoDecoderFactory) alloc] init];
+ RTC_OBJC_TYPE(RTCDefaultVideoEncoderFactory) *encoderFactory =
+ [[RTC_OBJC_TYPE(RTCDefaultVideoEncoderFactory) alloc] init];
+ encoderFactory.preferredCodec = [settings currentVideoCodecSettingFromStore];
+ _factory =
+ [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] initWithEncoderFactory:encoderFactory
+ decoderFactory:decoderFactory];
+
+#if defined(WEBRTC_IOS)
+ if (kARDAppClientEnableTracing) {
+ NSString *filePath = [self documentsFilePathForFileName:@"webrtc-trace.txt"];
+ RTCStartInternalCapture(filePath);
+ }
+#endif
+
+ // Request TURN.
+ __weak ARDAppClient *weakSelf = self;
+ [_turnClient requestServersWithCompletionHandler:^(NSArray *turnServers,
+ NSError *error) {
+ if (error) {
+ RTCLogError(@"Error retrieving TURN servers: %@", error.localizedDescription);
+ }
+ ARDAppClient *strongSelf = weakSelf;
+ [strongSelf.iceServers addObjectsFromArray:turnServers];
+ strongSelf.isTurnComplete = YES;
+ [strongSelf startSignalingIfReady];
+ }];
+
+ // Join room on room server.
+ [_roomServerClient joinRoomWithRoomId:roomId
+ isLoopback:isLoopback
+ completionHandler:^(ARDJoinResponse *response, NSError *error) {
+ ARDAppClient *strongSelf = weakSelf;
+ if (error) {
+ [strongSelf.delegate appClient:strongSelf didError:error];
+ return;
+ }
+ NSError *joinError =
+ [[strongSelf class] errorForJoinResultType:response.result];
+ if (joinError) {
+ RTCLogError(@"Failed to join room:%@ on room server.", roomId);
+ [strongSelf disconnect];
+ [strongSelf.delegate appClient:strongSelf didError:joinError];
+ return;
+ }
+ RTCLog(@"Joined room:%@ on room server.", roomId);
+ strongSelf.roomId = response.roomId;
+ strongSelf.clientId = response.clientId;
+ strongSelf.isInitiator = response.isInitiator;
+ for (ARDSignalingMessage *message in response.messages) {
+ if (message.type == kARDSignalingMessageTypeOffer ||
+ message.type == kARDSignalingMessageTypeAnswer) {
+ strongSelf.hasReceivedSdp = YES;
+ [strongSelf.messageQueue insertObject:message atIndex:0];
+ } else {
+ [strongSelf.messageQueue addObject:message];
+ }
+ }
+ strongSelf.webSocketURL = response.webSocketURL;
+ strongSelf.webSocketRestURL = response.webSocketRestURL;
+ [strongSelf registerWithColliderIfReady];
+ [strongSelf startSignalingIfReady];
+ }];
+}
+
+- (void)disconnect {
+ if (_state == kARDAppClientStateDisconnected) {
+ return;
+ }
+ if (self.hasJoinedRoomServerRoom) {
+ [_roomServerClient leaveRoomWithRoomId:_roomId
+ clientId:_clientId
+ completionHandler:nil];
+ }
+ if (_channel) {
+ if (_channel.state == kARDSignalingChannelStateRegistered) {
+ // Tell the other client we're hanging up.
+ ARDByeMessage *byeMessage = [[ARDByeMessage alloc] init];
+ [_channel sendMessage:byeMessage];
+ }
+ // Disconnect from collider.
+ _channel = nil;
+ }
+ _clientId = nil;
+ _roomId = nil;
+ _isInitiator = NO;
+ _hasReceivedSdp = NO;
+ _messageQueue = [NSMutableArray array];
+ _localVideoTrack = nil;
+#if defined(WEBRTC_IOS)
+ [_factory stopAecDump];
+ [_peerConnection stopRtcEventLog];
+#endif
+ [_peerConnection close];
+ _peerConnection = nil;
+ self.state = kARDAppClientStateDisconnected;
+#if defined(WEBRTC_IOS)
+ if (kARDAppClientEnableTracing) {
+ RTCStopInternalCapture();
+ }
+#endif
+}
+
+#pragma mark - ARDSignalingChannelDelegate
+
+- (void)channel:(id<ARDSignalingChannel>)channel
+ didReceiveMessage:(ARDSignalingMessage *)message {
+ switch (message.type) {
+ case kARDSignalingMessageTypeOffer:
+ case kARDSignalingMessageTypeAnswer:
+ // Offers and answers must be processed before any other message, so we
+ // place them at the front of the queue.
+ _hasReceivedSdp = YES;
+ [_messageQueue insertObject:message atIndex:0];
+ break;
+ case kARDSignalingMessageTypeCandidate:
+ case kARDSignalingMessageTypeCandidateRemoval:
+ [_messageQueue addObject:message];
+ break;
+ case kARDSignalingMessageTypeBye:
+ // Disconnects can be processed immediately.
+ [self processSignalingMessage:message];
+ return;
+ }
+ [self drainMessageQueueIfReady];
+}
+
+- (void)channel:(id<ARDSignalingChannel>)channel
+ didChangeState:(ARDSignalingChannelState)state {
+ switch (state) {
+ case kARDSignalingChannelStateOpen:
+ break;
+ case kARDSignalingChannelStateRegistered:
+ break;
+ case kARDSignalingChannelStateClosed:
+ case kARDSignalingChannelStateError:
+ // TODO(tkchin): reconnection scenarios. Right now we just disconnect
+ // completely if the websocket connection fails.
+ [self disconnect];
+ break;
+ }
+}
+
+#pragma mark - RTC_OBJC_TYPE(RTCPeerConnectionDelegate)
+// Callbacks for this delegate occur on non-main thread and need to be
+// dispatched back to main queue as needed.
+
+- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
+ didChangeSignalingState:(RTCSignalingState)stateChanged {
+ RTCLog(@"Signaling state changed: %ld", (long)stateChanged);
+}
+
+- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
+ didAddStream:(RTC_OBJC_TYPE(RTCMediaStream) *)stream {
+ RTCLog(@"Stream with %lu video tracks and %lu audio tracks was added.",
+ (unsigned long)stream.videoTracks.count,
+ (unsigned long)stream.audioTracks.count);
+}
+
+- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
+ didStartReceivingOnTransceiver:(RTC_OBJC_TYPE(RTCRtpTransceiver) *)transceiver {
+ RTC_OBJC_TYPE(RTCMediaStreamTrack) *track = transceiver.receiver.track;
+ RTCLog(@"Now receiving %@ on track %@.", track.kind, track.trackId);
+}
+
+- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
+ didRemoveStream:(RTC_OBJC_TYPE(RTCMediaStream) *)stream {
+ RTCLog(@"Stream was removed.");
+}
+
+- (void)peerConnectionShouldNegotiate:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection {
+ RTCLog(@"WARNING: Renegotiation needed but unimplemented.");
+}
+
+- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
+ didChangeIceConnectionState:(RTCIceConnectionState)newState {
+ RTCLog(@"ICE state changed: %ld", (long)newState);
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [self.delegate appClient:self didChangeConnectionState:newState];
+ });
+}
+
+- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
+ didChangeConnectionState:(RTCPeerConnectionState)newState {
+ RTCLog(@"ICE+DTLS state changed: %ld", (long)newState);
+}
+
+- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
+ didChangeIceGatheringState:(RTCIceGatheringState)newState {
+ RTCLog(@"ICE gathering state changed: %ld", (long)newState);
+}
+
+- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
+ didGenerateIceCandidate:(RTC_OBJC_TYPE(RTCIceCandidate) *)candidate {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ ARDICECandidateMessage *message =
+ [[ARDICECandidateMessage alloc] initWithCandidate:candidate];
+ [self sendSignalingMessage:message];
+ });
+}
+
+- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
+ didFailToGatherIceCandidate:(RTC_OBJC_TYPE(RTCIceCandidateErrorEvent) *)event {
+ RTCLog(@"Failed to gather ICE candidate. address: %@, port: %d, url: %@, errorCode: %d, "
+ @"errorText: %@",
+ event.address,
+ event.port,
+ event.url,
+ event.errorCode,
+ event.errorText);
+}
+
+- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
+ didRemoveIceCandidates:(NSArray<RTC_OBJC_TYPE(RTCIceCandidate) *> *)candidates {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ ARDICECandidateRemovalMessage *message =
+ [[ARDICECandidateRemovalMessage alloc]
+ initWithRemovedCandidates:candidates];
+ [self sendSignalingMessage:message];
+ });
+}
+
+- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
+ didChangeLocalCandidate:(RTC_OBJC_TYPE(RTCIceCandidate) *)local
+ didChangeRemoteCandidate:(RTC_OBJC_TYPE(RTCIceCandidate) *)remote
+ lastReceivedMs:(int)lastDataReceivedMs
+ didHaveReason:(NSString *)reason {
+ RTCLog(@"ICE candidate pair changed because: %@", reason);
+}
+
+- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
+ didOpenDataChannel:(RTC_OBJC_TYPE(RTCDataChannel) *)dataChannel {
+}
+
+#pragma mark - RTCSessionDescriptionDelegate
+// Callbacks for this delegate occur on non-main thread and need to be
+// dispatched back to main queue as needed.
+
+- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
+ didCreateSessionDescription:(RTC_OBJC_TYPE(RTCSessionDescription) *)sdp
+ error:(NSError *)error {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (error) {
+ RTCLogError(@"Failed to create session description. Error: %@", error);
+ [self disconnect];
+ NSDictionary *userInfo = @{
+ NSLocalizedDescriptionKey: @"Failed to create session description.",
+ };
+ NSError *sdpError =
+ [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
+ code:kARDAppClientErrorCreateSDP
+ userInfo:userInfo];
+ [self.delegate appClient:self didError:sdpError];
+ return;
+ }
+ __weak ARDAppClient *weakSelf = self;
+ [self.peerConnection setLocalDescription:sdp
+ completionHandler:^(NSError *error) {
+ ARDAppClient *strongSelf = weakSelf;
+ [strongSelf peerConnection:strongSelf.peerConnection
+ didSetSessionDescriptionWithError:error];
+ }];
+ ARDSessionDescriptionMessage *message =
+ [[ARDSessionDescriptionMessage alloc] initWithDescription:sdp];
+ [self sendSignalingMessage:message];
+ [self setMaxBitrateForPeerConnectionVideoSender];
+ });
+}
+
+- (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection
+ didSetSessionDescriptionWithError:(NSError *)error {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (error) {
+ RTCLogError(@"Failed to set session description. Error: %@", error);
+ [self disconnect];
+ NSDictionary *userInfo = @{
+ NSLocalizedDescriptionKey: @"Failed to set session description.",
+ };
+ NSError *sdpError =
+ [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
+ code:kARDAppClientErrorSetSDP
+ userInfo:userInfo];
+ [self.delegate appClient:self didError:sdpError];
+ return;
+ }
+ // If we're answering and we've just set the remote offer we need to create
+ // an answer and set the local description.
+ if (!self.isInitiator && !self.peerConnection.localDescription) {
+ RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = [self defaultAnswerConstraints];
+ __weak ARDAppClient *weakSelf = self;
+ [self.peerConnection
+ answerForConstraints:constraints
+ completionHandler:^(RTC_OBJC_TYPE(RTCSessionDescription) * sdp, NSError * error) {
+ ARDAppClient *strongSelf = weakSelf;
+ [strongSelf peerConnection:strongSelf.peerConnection
+ didCreateSessionDescription:sdp
+ error:error];
+ }];
+ }
+ });
+}
+
+#pragma mark - Private
+
+#if defined(WEBRTC_IOS)
+
+- (NSString *)documentsFilePathForFileName:(NSString *)fileName {
+ NSParameterAssert(fileName.length);
+ NSArray *paths = NSSearchPathForDirectoriesInDomains(
+ NSDocumentDirectory, NSUserDomainMask, YES);
+ NSString *documentsDirPath = paths.firstObject;
+ NSString *filePath =
+ [documentsDirPath stringByAppendingPathComponent:fileName];
+ return filePath;
+}
+
+#endif
+
+- (BOOL)hasJoinedRoomServerRoom {
+ return _clientId.length;
+}
+
+// Begins the peer connection connection process if we have both joined a room
+// on the room server and tried to obtain a TURN server. Otherwise does nothing.
+// A peer connection object will be created with a stream that contains local
+// audio and video capture. If this client is the caller, an offer is created as
+// well, otherwise the client will wait for an offer to arrive.
+- (void)startSignalingIfReady {
+ if (!_isTurnComplete || !self.hasJoinedRoomServerRoom) {
+ return;
+ }
+ self.state = kARDAppClientStateConnected;
+
+ // Create peer connection.
+ RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = [self defaultPeerConnectionConstraints];
+ RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init];
+ RTC_OBJC_TYPE(RTCCertificate) *pcert = [RTC_OBJC_TYPE(RTCCertificate)
+ generateCertificateWithParams:@{@"expires" : @100000, @"name" : @"RSASSA-PKCS1-v1_5"}];
+ config.iceServers = _iceServers;
+ config.sdpSemantics = RTCSdpSemanticsUnifiedPlan;
+ config.certificate = pcert;
+
+ _peerConnection = [_factory peerConnectionWithConfiguration:config
+ constraints:constraints
+ delegate:self];
+ // Create AV senders.
+ [self createMediaSenders];
+ if (_isInitiator) {
+ // Send offer.
+ __weak ARDAppClient *weakSelf = self;
+ [_peerConnection
+ offerForConstraints:[self defaultOfferConstraints]
+ completionHandler:^(RTC_OBJC_TYPE(RTCSessionDescription) * sdp, NSError * error) {
+ ARDAppClient *strongSelf = weakSelf;
+ [strongSelf peerConnection:strongSelf.peerConnection
+ didCreateSessionDescription:sdp
+ error:error];
+ }];
+ } else {
+ // Check if we've received an offer.
+ [self drainMessageQueueIfReady];
+ }
+#if defined(WEBRTC_IOS)
+ // Start event log.
+ if (kARDAppClientEnableRtcEventLog) {
+ NSString *filePath = [self documentsFilePathForFileName:@"webrtc-rtceventlog"];
+ if (![_peerConnection startRtcEventLogWithFilePath:filePath
+ maxSizeInBytes:kARDAppClientRtcEventLogMaxSizeInBytes]) {
+ RTCLogError(@"Failed to start event logging.");
+ }
+ }
+
+ // Start aecdump diagnostic recording.
+ if ([_settings currentCreateAecDumpSettingFromStore]) {
+ NSString *filePath = [self documentsFilePathForFileName:@"webrtc-audio.aecdump"];
+ if (![_factory startAecDumpWithFilePath:filePath
+ maxSizeInBytes:kARDAppClientAecDumpMaxSizeInBytes]) {
+ RTCLogError(@"Failed to start aec dump.");
+ }
+ }
+#endif
+}
+
+// Processes the messages that we've received from the room server and the
+// signaling channel. The offer or answer message must be processed before other
+// signaling messages, however they can arrive out of order. Hence, this method
+// only processes pending messages if there is a peer connection object and
+// if we have received either an offer or answer.
+- (void)drainMessageQueueIfReady {
+ if (!_peerConnection || !_hasReceivedSdp) {
+ return;
+ }
+ for (ARDSignalingMessage *message in _messageQueue) {
+ [self processSignalingMessage:message];
+ }
+ [_messageQueue removeAllObjects];
+}
+
+// Processes the given signaling message based on its type.
+- (void)processSignalingMessage:(ARDSignalingMessage *)message {
+ NSParameterAssert(_peerConnection ||
+ message.type == kARDSignalingMessageTypeBye);
+ switch (message.type) {
+ case kARDSignalingMessageTypeOffer:
+ case kARDSignalingMessageTypeAnswer: {
+ ARDSessionDescriptionMessage *sdpMessage =
+ (ARDSessionDescriptionMessage *)message;
+ RTC_OBJC_TYPE(RTCSessionDescription) *description = sdpMessage.sessionDescription;
+ __weak ARDAppClient *weakSelf = self;
+ [_peerConnection setRemoteDescription:description
+ completionHandler:^(NSError *error) {
+ ARDAppClient *strongSelf = weakSelf;
+ [strongSelf peerConnection:strongSelf.peerConnection
+ didSetSessionDescriptionWithError:error];
+ }];
+ break;
+ }
+ case kARDSignalingMessageTypeCandidate: {
+ ARDICECandidateMessage *candidateMessage =
+ (ARDICECandidateMessage *)message;
+ __weak ARDAppClient *weakSelf = self;
+ [_peerConnection addIceCandidate:candidateMessage.candidate
+ completionHandler:^(NSError *error) {
+ ARDAppClient *strongSelf = weakSelf;
+ if (error) {
+ [strongSelf.delegate appClient:strongSelf didError:error];
+ }
+ }];
+ break;
+ }
+ case kARDSignalingMessageTypeCandidateRemoval: {
+ ARDICECandidateRemovalMessage *candidateMessage =
+ (ARDICECandidateRemovalMessage *)message;
+ [_peerConnection removeIceCandidates:candidateMessage.candidates];
+ break;
+ }
+ case kARDSignalingMessageTypeBye:
+ // Other client disconnected.
+ // TODO(tkchin): support waiting in room for next client. For now just
+ // disconnect.
+ [self disconnect];
+ break;
+ }
+}
+
+// Sends a signaling message to the other client. The caller will send messages
+// through the room server, whereas the callee will send messages over the
+// signaling channel.
+- (void)sendSignalingMessage:(ARDSignalingMessage *)message {
+ if (_isInitiator) {
+ __weak ARDAppClient *weakSelf = self;
+ [_roomServerClient sendMessage:message
+ forRoomId:_roomId
+ clientId:_clientId
+ completionHandler:^(ARDMessageResponse *response,
+ NSError *error) {
+ ARDAppClient *strongSelf = weakSelf;
+ if (error) {
+ [strongSelf.delegate appClient:strongSelf didError:error];
+ return;
+ }
+ NSError *messageError =
+ [[strongSelf class] errorForMessageResultType:response.result];
+ if (messageError) {
+ [strongSelf.delegate appClient:strongSelf didError:messageError];
+ return;
+ }
+ }];
+ } else {
+ [_channel sendMessage:message];
+ }
+}
+
+- (void)setMaxBitrateForPeerConnectionVideoSender {
+ for (RTC_OBJC_TYPE(RTCRtpSender) * sender in _peerConnection.senders) {
+ if (sender.track != nil) {
+ if ([sender.track.kind isEqualToString:kARDVideoTrackKind]) {
+ [self setMaxBitrate:[_settings currentMaxBitrateSettingFromStore] forVideoSender:sender];
+ }
+ }
+ }
+}
+
+- (void)setMaxBitrate:(NSNumber *)maxBitrate forVideoSender:(RTC_OBJC_TYPE(RTCRtpSender) *)sender {
+ if (maxBitrate.intValue <= 0) {
+ return;
+ }
+
+ RTC_OBJC_TYPE(RTCRtpParameters) *parametersToModify = sender.parameters;
+ for (RTC_OBJC_TYPE(RTCRtpEncodingParameters) * encoding in parametersToModify.encodings) {
+ encoding.maxBitrateBps = @(maxBitrate.intValue * kKbpsMultiplier);
+ }
+ [sender setParameters:parametersToModify];
+}
+
+- (RTC_OBJC_TYPE(RTCRtpTransceiver) *)videoTransceiver {
+ for (RTC_OBJC_TYPE(RTCRtpTransceiver) * transceiver in _peerConnection.transceivers) {
+ if (transceiver.mediaType == RTCRtpMediaTypeVideo) {
+ return transceiver;
+ }
+ }
+ return nil;
+}
+
+- (void)createMediaSenders {
+ RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = [self defaultMediaAudioConstraints];
+ RTC_OBJC_TYPE(RTCAudioSource) *source = [_factory audioSourceWithConstraints:constraints];
+ RTC_OBJC_TYPE(RTCAudioTrack) *track = [_factory audioTrackWithSource:source
+ trackId:kARDAudioTrackId];
+ [_peerConnection addTrack:track streamIds:@[ kARDMediaStreamId ]];
+ _localVideoTrack = [self createLocalVideoTrack];
+ if (_localVideoTrack) {
+ [_peerConnection addTrack:_localVideoTrack streamIds:@[ kARDMediaStreamId ]];
+ [_delegate appClient:self didReceiveLocalVideoTrack:_localVideoTrack];
+ // We can set up rendering for the remote track right away since the transceiver already has an
+ // RTC_OBJC_TYPE(RTCRtpReceiver) with a track. The track will automatically get unmuted and
+ // produce frames once RTP is received.
+ RTC_OBJC_TYPE(RTCVideoTrack) *track =
+ (RTC_OBJC_TYPE(RTCVideoTrack) *)([self videoTransceiver].receiver.track);
+ [_delegate appClient:self didReceiveRemoteVideoTrack:track];
+ }
+}
+
+- (RTC_OBJC_TYPE(RTCVideoTrack) *)createLocalVideoTrack {
+ if ([_settings currentAudioOnlySettingFromStore]) {
+ return nil;
+ }
+
+ RTC_OBJC_TYPE(RTCVideoSource) *source = [_factory videoSource];
+
+#if !TARGET_IPHONE_SIMULATOR
+ if (self.isBroadcast) {
+ ARDExternalSampleCapturer *capturer =
+ [[ARDExternalSampleCapturer alloc] initWithDelegate:source];
+ [_delegate appClient:self didCreateLocalExternalSampleCapturer:capturer];
+ } else {
+ RTC_OBJC_TYPE(RTCCameraVideoCapturer) *capturer =
+ [[RTC_OBJC_TYPE(RTCCameraVideoCapturer) alloc] initWithDelegate:source];
+ [_delegate appClient:self didCreateLocalCapturer:capturer];
+ }
+#else
+#if defined(__IPHONE_11_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0)
+ if (@available(iOS 10, *)) {
+ RTC_OBJC_TYPE(RTCFileVideoCapturer) *fileCapturer =
+ [[RTC_OBJC_TYPE(RTCFileVideoCapturer) alloc] initWithDelegate:source];
+ [_delegate appClient:self didCreateLocalFileCapturer:fileCapturer];
+ }
+#endif
+#endif
+
+ return [_factory videoTrackWithSource:source trackId:kARDVideoTrackId];
+}
+
+#pragma mark - Collider methods
+
+- (void)registerWithColliderIfReady {
+ if (!self.hasJoinedRoomServerRoom) {
+ return;
+ }
+ // Open WebSocket connection.
+ if (!_channel) {
+ _channel =
+ [[ARDWebSocketChannel alloc] initWithURL:_websocketURL
+ restURL:_websocketRestURL
+ delegate:self];
+ if (_isLoopback) {
+ _loopbackChannel =
+ [[ARDLoopbackWebSocketChannel alloc] initWithURL:_websocketURL
+ restURL:_websocketRestURL];
+ }
+ }
+ [_channel registerForRoomId:_roomId clientId:_clientId];
+ if (_isLoopback) {
+ [_loopbackChannel registerForRoomId:_roomId clientId:@"LOOPBACK_CLIENT_ID"];
+ }
+}
+
+#pragma mark - Defaults
+
+- (RTC_OBJC_TYPE(RTCMediaConstraints) *)defaultMediaAudioConstraints {
+ NSDictionary *mandatoryConstraints = @{};
+ RTC_OBJC_TYPE(RTCMediaConstraints) *constraints =
+ [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:mandatoryConstraints
+ optionalConstraints:nil];
+ return constraints;
+}
+
+- (RTC_OBJC_TYPE(RTCMediaConstraints) *)defaultAnswerConstraints {
+ return [self defaultOfferConstraints];
+}
+
+- (RTC_OBJC_TYPE(RTCMediaConstraints) *)defaultOfferConstraints {
+ NSDictionary *mandatoryConstraints = @{
+ @"OfferToReceiveAudio" : @"true",
+ @"OfferToReceiveVideo" : @"true"
+ };
+ RTC_OBJC_TYPE(RTCMediaConstraints) *constraints =
+ [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:mandatoryConstraints
+ optionalConstraints:nil];
+ return constraints;
+}
+
+- (RTC_OBJC_TYPE(RTCMediaConstraints) *)defaultPeerConnectionConstraints {
+ if (_defaultPeerConnectionConstraints) {
+ return _defaultPeerConnectionConstraints;
+ }
+ NSString *value = _isLoopback ? @"false" : @"true";
+ NSDictionary *optionalConstraints = @{ @"DtlsSrtpKeyAgreement" : value };
+ RTC_OBJC_TYPE(RTCMediaConstraints) *constraints =
+ [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:nil
+ optionalConstraints:optionalConstraints];
+ return constraints;
+}
+
+#pragma mark - Errors
+
++ (NSError *)errorForJoinResultType:(ARDJoinResultType)resultType {
+ NSError *error = nil;
+ switch (resultType) {
+ case kARDJoinResultTypeSuccess:
+ break;
+ case kARDJoinResultTypeUnknown: {
+ error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
+ code:kARDAppClientErrorUnknown
+ userInfo:@{
+ NSLocalizedDescriptionKey: @"Unknown error.",
+ }];
+ break;
+ }
+ case kARDJoinResultTypeFull: {
+ error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
+ code:kARDAppClientErrorRoomFull
+ userInfo:@{
+ NSLocalizedDescriptionKey: @"Room is full.",
+ }];
+ break;
+ }
+ }
+ return error;
+}
+
++ (NSError *)errorForMessageResultType:(ARDMessageResultType)resultType {
+ NSError *error = nil;
+ switch (resultType) {
+ case kARDMessageResultTypeSuccess:
+ break;
+ case kARDMessageResultTypeUnknown:
+ error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
+ code:kARDAppClientErrorUnknown
+ userInfo:@{
+ NSLocalizedDescriptionKey: @"Unknown error.",
+ }];
+ break;
+ case kARDMessageResultTypeInvalidClient:
+ error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
+ code:kARDAppClientErrorInvalidClient
+ userInfo:@{
+ NSLocalizedDescriptionKey: @"Invalid client.",
+ }];
+ break;
+ case kARDMessageResultTypeInvalidRoom:
+ error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain
+ code:kARDAppClientErrorInvalidRoom
+ userInfo:@{
+ NSLocalizedDescriptionKey: @"Invalid room.",
+ }];
+ break;
+ }
+ return error;
+}
+
+@end