 8cc47e926c
			
		
	
	8cc47e926c
	
	
	
		
			
			BUG= R=rsesek@chromium.org Review URL: https://webrtc-codereview.appspot.com/34679004 Cr-Commit-Position: refs/heads/master@{#8784} git-svn-id: http://webrtc.googlecode.com/svn/trunk@8784 4adac7df-926f-26a2-2b94-8c16560cd09d
		
			
				
	
	
		
			643 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
			
		
		
	
	
			643 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
| /*
 | |
|  * libjingle
 | |
|  * Copyright 2014 Google Inc.
 | |
|  *
 | |
|  * Redistribution and use in source and binary forms, with or without
 | |
|  * modification, are permitted provided that the following conditions are met:
 | |
|  *
 | |
|  *  1. Redistributions of source code must retain the above copyright notice,
 | |
|  *     this list of conditions and the following disclaimer.
 | |
|  *  2. Redistributions in binary form must reproduce the above copyright notice,
 | |
|  *     this list of conditions and the following disclaimer in the documentation
 | |
|  *     and/or other materials provided with the distribution.
 | |
|  *  3. The name of the author may not be used to endorse or promote products
 | |
|  *     derived from this software without specific prior written permission.
 | |
|  *
 | |
|  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 | |
|  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 | |
|  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 | |
|  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 | |
|  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 | |
|  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 | |
|  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 | |
|  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 | |
|  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 | |
|  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | |
|  */
 | |
| 
 | |
| #import "ARDAppClient+Internal.h"
 | |
| 
 | |
| #import <AVFoundation/AVFoundation.h>
 | |
| 
 | |
| #import "ARDAppEngineClient.h"
 | |
| #import "ARDCEODTURNClient.h"
 | |
| #import "ARDJoinResponse.h"
 | |
| #import "ARDMessageResponse.h"
 | |
| #import "ARDSignalingMessage.h"
 | |
| #import "ARDUtilities.h"
 | |
| #import "ARDWebSocketChannel.h"
 | |
| #import "RTCICECandidate+JSON.h"
 | |
| #import "RTCICEServer.h"
 | |
| #import "RTCMediaConstraints.h"
 | |
| #import "RTCMediaStream.h"
 | |
| #import "RTCPair.h"
 | |
| #import "RTCSessionDescription+JSON.h"
 | |
| #import "RTCVideoCapturer.h"
 | |
| #import "RTCVideoTrack.h"
 | |
| 
 | |
| static NSString * const kARDDefaultSTUNServerUrl =
 | |
|     @"stun:stun.l.google.com:19302";
 | |
| // TODO(tkchin): figure out a better username for CEOD statistics.
 | |
| static NSString * const kARDTurnRequestUrl =
 | |
|     @"https://computeengineondemand.appspot.com"
 | |
|     @"/turn?username=iapprtc&key=4080218913";
 | |
| 
 | |
| 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;
 | |
| 
 | |
| @implementation ARDAppClient
 | |
| 
 | |
| @synthesize delegate = _delegate;
 | |
| @synthesize state = _state;
 | |
| @synthesize roomServerClient = _roomServerClient;
 | |
| @synthesize channel = _channel;
 | |
| @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;
 | |
| 
 | |
| - (instancetype)init {
 | |
|   if (self = [super init]) {
 | |
|     _roomServerClient = [[ARDAppEngineClient alloc] init];
 | |
|     NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl];
 | |
|     _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL];
 | |
|     [self configure];
 | |
|   }
 | |
|   return self;
 | |
| }
 | |
| 
 | |
| - (instancetype)initWithDelegate:(id<ARDAppClientDelegate>)delegate {
 | |
|   if (self = [super init]) {
 | |
|     _roomServerClient = [[ARDAppEngineClient alloc] init];
 | |
|     _delegate = delegate;
 | |
|     NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl];
 | |
|     _turnClient = [[ARDCEODTURNClient 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 {
 | |
|   _factory = [[RTCPeerConnectionFactory alloc] init];
 | |
|   _messageQueue = [NSMutableArray array];
 | |
|   _iceServers = [NSMutableArray arrayWithObject:[self defaultSTUNServer]];
 | |
| }
 | |
| 
 | |
| - (void)dealloc {
 | |
|   [self disconnect];
 | |
| }
 | |
| 
 | |
| - (void)setState:(ARDAppClientState)state {
 | |
|   if (_state == state) {
 | |
|     return;
 | |
|   }
 | |
|   _state = state;
 | |
|   [_delegate appClient:self didChangeState:_state];
 | |
| }
 | |
| 
 | |
| - (void)connectToRoomWithId:(NSString *)roomId
 | |
|                     options:(NSDictionary *)options {
 | |
|   NSParameterAssert(roomId.length);
 | |
|   NSParameterAssert(_state == kARDAppClientStateDisconnected);
 | |
|   self.state = kARDAppClientStateConnecting;
 | |
| 
 | |
|   // Request TURN.
 | |
|   __weak ARDAppClient *weakSelf = self;
 | |
|   [_turnClient requestServersWithCompletionHandler:^(NSArray *turnServers,
 | |
|                                                      NSError *error) {
 | |
|     if (error) {
 | |
|       NSLog(@"Error retrieving TURN servers: %@", error);
 | |
|     }
 | |
|     ARDAppClient *strongSelf = weakSelf;
 | |
|     [strongSelf.iceServers addObjectsFromArray:turnServers];
 | |
|     strongSelf.isTurnComplete = YES;
 | |
|     [strongSelf startSignalingIfReady];
 | |
|   }];
 | |
| 
 | |
|   // Join room on room server.
 | |
|   [_roomServerClient joinRoomWithRoomId:roomId
 | |
|       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) {
 | |
|       NSLog(@"Failed to join room:%@ on room server.", roomId);
 | |
|       [strongSelf disconnect];
 | |
|       [strongSelf.delegate appClient:strongSelf didError:joinError];
 | |
|       return;
 | |
|     }
 | |
|     NSLog(@"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];
 | |
|   _peerConnection = nil;
 | |
|   self.state = kARDAppClientStateDisconnected;
 | |
| }
 | |
| 
 | |
| #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:
 | |
|       [_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 - RTCPeerConnectionDelegate
 | |
| // Callbacks for this delegate occur on non-main thread and need to be
 | |
| // dispatched back to main queue as needed.
 | |
| 
 | |
| - (void)peerConnection:(RTCPeerConnection *)peerConnection
 | |
|     signalingStateChanged:(RTCSignalingState)stateChanged {
 | |
|   NSLog(@"Signaling state changed: %d", stateChanged);
 | |
| }
 | |
| 
 | |
| - (void)peerConnection:(RTCPeerConnection *)peerConnection
 | |
|            addedStream:(RTCMediaStream *)stream {
 | |
|   dispatch_async(dispatch_get_main_queue(), ^{
 | |
|     NSLog(@"Received %lu video tracks and %lu audio tracks",
 | |
|         (unsigned long)stream.videoTracks.count,
 | |
|         (unsigned long)stream.audioTracks.count);
 | |
|     if (stream.videoTracks.count) {
 | |
|       RTCVideoTrack *videoTrack = stream.videoTracks[0];
 | |
|       [_delegate appClient:self didReceiveRemoteVideoTrack:videoTrack];
 | |
|     }
 | |
|   });
 | |
| }
 | |
| 
 | |
| - (void)peerConnection:(RTCPeerConnection *)peerConnection
 | |
|         removedStream:(RTCMediaStream *)stream {
 | |
|   NSLog(@"Stream was removed.");
 | |
| }
 | |
| 
 | |
| - (void)peerConnectionOnRenegotiationNeeded:
 | |
|     (RTCPeerConnection *)peerConnection {
 | |
|   NSLog(@"WARNING: Renegotiation needed but unimplemented.");
 | |
| }
 | |
| 
 | |
| - (void)peerConnection:(RTCPeerConnection *)peerConnection
 | |
|     iceConnectionChanged:(RTCICEConnectionState)newState {
 | |
|   NSLog(@"ICE state changed: %d", newState);
 | |
|   dispatch_async(dispatch_get_main_queue(), ^{
 | |
|     [_delegate appClient:self didChangeConnectionState:newState];
 | |
|   });
 | |
| }
 | |
| 
 | |
| - (void)peerConnection:(RTCPeerConnection *)peerConnection
 | |
|     iceGatheringChanged:(RTCICEGatheringState)newState {
 | |
|   NSLog(@"ICE gathering state changed: %d", newState);
 | |
| }
 | |
| 
 | |
| - (void)peerConnection:(RTCPeerConnection *)peerConnection
 | |
|        gotICECandidate:(RTCICECandidate *)candidate {
 | |
|   dispatch_async(dispatch_get_main_queue(), ^{
 | |
|     ARDICECandidateMessage *message =
 | |
|         [[ARDICECandidateMessage alloc] initWithCandidate:candidate];
 | |
|     [self sendSignalingMessage:message];
 | |
|   });
 | |
| }
 | |
| 
 | |
| - (void)peerConnection:(RTCPeerConnection*)peerConnection
 | |
|     didOpenDataChannel:(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:(RTCPeerConnection *)peerConnection
 | |
|     didCreateSessionDescription:(RTCSessionDescription *)sdp
 | |
|                           error:(NSError *)error {
 | |
|   dispatch_async(dispatch_get_main_queue(), ^{
 | |
|     if (error) {
 | |
|       NSLog(@"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];
 | |
|       [_delegate appClient:self didError:sdpError];
 | |
|       return;
 | |
|     }
 | |
|     [_peerConnection setLocalDescriptionWithDelegate:self
 | |
|                                   sessionDescription:sdp];
 | |
|     ARDSessionDescriptionMessage *message =
 | |
|         [[ARDSessionDescriptionMessage alloc] initWithDescription:sdp];
 | |
|     [self sendSignalingMessage:message];
 | |
|   });
 | |
| }
 | |
| 
 | |
| - (void)peerConnection:(RTCPeerConnection *)peerConnection
 | |
|     didSetSessionDescriptionWithError:(NSError *)error {
 | |
|   dispatch_async(dispatch_get_main_queue(), ^{
 | |
|     if (error) {
 | |
|       NSLog(@"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];
 | |
|       [_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 (!_isInitiator && !_peerConnection.localDescription) {
 | |
|       RTCMediaConstraints *constraints = [self defaultAnswerConstraints];
 | |
|       [_peerConnection createAnswerWithDelegate:self
 | |
|                                     constraints:constraints];
 | |
| 
 | |
|     }
 | |
|   });
 | |
| }
 | |
| 
 | |
| #pragma mark - Private
 | |
| 
 | |
| - (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.
 | |
|   RTCMediaConstraints *constraints = [self defaultPeerConnectionConstraints];
 | |
|   _peerConnection = [_factory peerConnectionWithICEServers:_iceServers
 | |
|                                                constraints:constraints
 | |
|                                                   delegate:self];
 | |
|   // Create AV media stream and add it to the peer connection.
 | |
|   RTCMediaStream *localStream = [self createLocalMediaStream];
 | |
|   [_peerConnection addStream:localStream];
 | |
|   if (_isInitiator) {
 | |
|     // Send offer.
 | |
|     [_peerConnection createOfferWithDelegate:self
 | |
|                                  constraints:[self defaultOfferConstraints]];
 | |
|   } else {
 | |
|     // Check if we've received an offer.
 | |
|     [self drainMessageQueueIfReady];
 | |
|   }
 | |
| }
 | |
| 
 | |
| // 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;
 | |
|       RTCSessionDescription *description = sdpMessage.sessionDescription;
 | |
|       [_peerConnection setRemoteDescriptionWithDelegate:self
 | |
|                                      sessionDescription:description];
 | |
|       break;
 | |
|     }
 | |
|     case kARDSignalingMessageTypeCandidate: {
 | |
|       ARDICECandidateMessage *candidateMessage =
 | |
|           (ARDICECandidateMessage *)message;
 | |
|       [_peerConnection addICECandidate:candidateMessage.candidate];
 | |
|       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];
 | |
|   }
 | |
| }
 | |
| 
 | |
| - (RTCMediaStream *)createLocalMediaStream {
 | |
|   RTCMediaStream* localStream = [_factory mediaStreamWithLabel:@"ARDAMS"];
 | |
|   RTCVideoTrack* localVideoTrack = nil;
 | |
| 
 | |
|   // The iOS simulator doesn't provide any sort of camera capture
 | |
|   // support or emulation (http://goo.gl/rHAnC1) so don't bother
 | |
|   // trying to open a local stream.
 | |
|   // TODO(tkchin): local video capture for OSX. See
 | |
|   // https://code.google.com/p/webrtc/issues/detail?id=3417.
 | |
| #if !TARGET_IPHONE_SIMULATOR && TARGET_OS_IPHONE
 | |
|   NSString *cameraID = nil;
 | |
|   for (AVCaptureDevice *captureDevice in
 | |
|        [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) {
 | |
|     if (captureDevice.position == AVCaptureDevicePositionFront) {
 | |
|       cameraID = [captureDevice localizedName];
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   NSAssert(cameraID, @"Unable to get the front camera id");
 | |
| 
 | |
|   RTCVideoCapturer *capturer =
 | |
|       [RTCVideoCapturer capturerWithDeviceName:cameraID];
 | |
|   RTCMediaConstraints *mediaConstraints = [self defaultMediaStreamConstraints];
 | |
|   RTCVideoSource *videoSource =
 | |
|       [_factory videoSourceWithCapturer:capturer
 | |
|                             constraints:mediaConstraints];
 | |
|   localVideoTrack =
 | |
|       [_factory videoTrackWithID:@"ARDAMSv0" source:videoSource];
 | |
|   if (localVideoTrack) {
 | |
|     [localStream addVideoTrack:localVideoTrack];
 | |
|   }
 | |
|   [_delegate appClient:self didReceiveLocalVideoTrack:localVideoTrack];
 | |
| #endif
 | |
|   [localStream addAudioTrack:[_factory audioTrackWithID:@"ARDAMSa0"]];
 | |
|   return localStream;
 | |
| }
 | |
| 
 | |
| #pragma mark - Collider methods
 | |
| 
 | |
| - (void)registerWithColliderIfReady {
 | |
|   if (!self.hasJoinedRoomServerRoom) {
 | |
|     return;
 | |
|   }
 | |
|   // Open WebSocket connection.
 | |
|   if (!_channel) {
 | |
|     _channel =
 | |
|         [[ARDWebSocketChannel alloc] initWithURL:_websocketURL
 | |
|                                          restURL:_websocketRestURL
 | |
|                                         delegate:self];
 | |
|   }
 | |
|   [_channel registerForRoomId:_roomId clientId:_clientId];
 | |
| }
 | |
| 
 | |
| #pragma mark - Defaults
 | |
| 
 | |
| - (RTCMediaConstraints *)defaultMediaStreamConstraints {
 | |
|   RTCMediaConstraints* constraints =
 | |
|       [[RTCMediaConstraints alloc]
 | |
|           initWithMandatoryConstraints:nil
 | |
|                    optionalConstraints:nil];
 | |
|   return constraints;
 | |
| }
 | |
| 
 | |
| - (RTCMediaConstraints *)defaultAnswerConstraints {
 | |
|   return [self defaultOfferConstraints];
 | |
| }
 | |
| 
 | |
| - (RTCMediaConstraints *)defaultOfferConstraints {
 | |
|   NSArray *mandatoryConstraints = @[
 | |
|       [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" value:@"true"],
 | |
|       [[RTCPair alloc] initWithKey:@"OfferToReceiveVideo" value:@"true"]
 | |
|   ];
 | |
|   RTCMediaConstraints* constraints =
 | |
|       [[RTCMediaConstraints alloc]
 | |
|           initWithMandatoryConstraints:mandatoryConstraints
 | |
|                    optionalConstraints:nil];
 | |
|   return constraints;
 | |
| }
 | |
| 
 | |
| - (RTCMediaConstraints *)defaultPeerConnectionConstraints {
 | |
|   if (_defaultPeerConnectionConstraints) {
 | |
|     return _defaultPeerConnectionConstraints;
 | |
|   }
 | |
|   NSArray *optionalConstraints = @[
 | |
|       [[RTCPair alloc] initWithKey:@"DtlsSrtpKeyAgreement" value:@"true"]
 | |
|   ];
 | |
|   RTCMediaConstraints* constraints =
 | |
|       [[RTCMediaConstraints alloc]
 | |
|           initWithMandatoryConstraints:nil
 | |
|                    optionalConstraints:optionalConstraints];
 | |
|   return constraints;
 | |
| }
 | |
| 
 | |
| - (RTCICEServer *)defaultSTUNServer {
 | |
|   NSURL *defaultSTUNServerURL = [NSURL URLWithString:kARDDefaultSTUNServerUrl];
 | |
|   return [[RTCICEServer alloc] initWithURI:defaultSTUNServerURL
 | |
|                                   username:@""
 | |
|                                   password:@""];
 | |
| }
 | |
| 
 | |
| #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
 |