2014-12-09 19:32:35 +00:00
|
|
|
/*
|
|
|
|
* 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.h"
|
|
|
|
|
|
|
|
#import <AVFoundation/AVFoundation.h>
|
|
|
|
|
|
|
|
#import "ARDMessageResponse.h"
|
|
|
|
#import "ARDRegisterResponse.h"
|
|
|
|
#import "ARDSignalingMessage.h"
|
|
|
|
#import "ARDUtilities.h"
|
|
|
|
#import "ARDWebSocketChannel.h"
|
|
|
|
#import "RTCICECandidate+JSON.h"
|
|
|
|
#import "RTCICEServer+JSON.h"
|
|
|
|
#import "RTCMediaConstraints.h"
|
|
|
|
#import "RTCMediaStream.h"
|
|
|
|
#import "RTCPair.h"
|
|
|
|
#import "RTCPeerConnection.h"
|
|
|
|
#import "RTCPeerConnectionDelegate.h"
|
|
|
|
#import "RTCPeerConnectionFactory.h"
|
|
|
|
#import "RTCSessionDescription+JSON.h"
|
|
|
|
#import "RTCSessionDescriptionDelegate.h"
|
|
|
|
#import "RTCVideoCapturer.h"
|
|
|
|
#import "RTCVideoTrack.h"
|
|
|
|
|
|
|
|
// TODO(tkchin): move these to a configuration object.
|
|
|
|
static NSString *kARDRoomServerHostUrl =
|
2014-12-31 00:26:20 +00:00
|
|
|
@"https://apprtc.appspot.com";
|
2014-12-09 19:32:35 +00:00
|
|
|
static NSString *kARDRoomServerRegisterFormat =
|
2014-12-31 00:26:20 +00:00
|
|
|
@"https://apprtc.appspot.com/register/%@";
|
2014-12-09 19:32:35 +00:00
|
|
|
static NSString *kARDRoomServerMessageFormat =
|
2014-12-31 00:26:20 +00:00
|
|
|
@"https://apprtc.appspot.com/message/%@/%@";
|
2014-12-09 19:32:35 +00:00
|
|
|
static NSString *kARDRoomServerByeFormat =
|
2014-12-31 00:26:20 +00:00
|
|
|
@"https://apprtc.appspot.com/bye/%@/%@";
|
2014-12-09 19:32:35 +00:00
|
|
|
|
|
|
|
static NSString *kARDDefaultSTUNServerUrl =
|
|
|
|
@"stun:stun.l.google.com:19302";
|
|
|
|
// TODO(tkchin): figure out a better username for CEOD statistics.
|
|
|
|
static NSString *kARDTurnRequestUrl =
|
|
|
|
@"https://computeengineondemand.appspot.com"
|
|
|
|
@"/turn?username=iapprtc&key=4080218913";
|
|
|
|
|
|
|
|
static NSString *kARDAppClientErrorDomain = @"ARDAppClient";
|
|
|
|
static NSInteger kARDAppClientErrorUnknown = -1;
|
|
|
|
static NSInteger kARDAppClientErrorRoomFull = -2;
|
|
|
|
static NSInteger kARDAppClientErrorCreateSDP = -3;
|
|
|
|
static NSInteger kARDAppClientErrorSetSDP = -4;
|
|
|
|
static NSInteger kARDAppClientErrorNetwork = -5;
|
|
|
|
static NSInteger kARDAppClientErrorInvalidClient = -6;
|
|
|
|
static NSInteger kARDAppClientErrorInvalidRoom = -7;
|
|
|
|
|
|
|
|
@interface ARDAppClient () <ARDWebSocketChannelDelegate,
|
|
|
|
RTCPeerConnectionDelegate, RTCSessionDescriptionDelegate>
|
|
|
|
@property(nonatomic, strong) ARDWebSocketChannel *channel;
|
|
|
|
@property(nonatomic, strong) RTCPeerConnection *peerConnection;
|
|
|
|
@property(nonatomic, strong) RTCPeerConnectionFactory *factory;
|
|
|
|
@property(nonatomic, strong) NSMutableArray *messageQueue;
|
|
|
|
|
|
|
|
@property(nonatomic, assign) BOOL isTurnComplete;
|
|
|
|
@property(nonatomic, assign) BOOL hasReceivedSdp;
|
|
|
|
@property(nonatomic, readonly) BOOL isRegisteredWithRoomServer;
|
|
|
|
|
|
|
|
@property(nonatomic, strong) NSString *roomId;
|
|
|
|
@property(nonatomic, strong) NSString *clientId;
|
|
|
|
@property(nonatomic, assign) BOOL isInitiator;
|
|
|
|
@property(nonatomic, strong) NSMutableArray *iceServers;
|
|
|
|
@property(nonatomic, strong) NSURL *webSocketURL;
|
|
|
|
@property(nonatomic, strong) NSURL *webSocketRestURL;
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation ARDAppClient
|
|
|
|
|
|
|
|
@synthesize delegate = _delegate;
|
|
|
|
@synthesize state = _state;
|
|
|
|
@synthesize channel = _channel;
|
|
|
|
@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;
|
|
|
|
|
|
|
|
- (instancetype)initWithDelegate:(id<ARDAppClientDelegate>)delegate {
|
|
|
|
if (self = [super init]) {
|
|
|
|
_delegate = delegate;
|
|
|
|
_factory = [[RTCPeerConnectionFactory alloc] init];
|
|
|
|
_messageQueue = [NSMutableArray array];
|
|
|
|
_iceServers = [NSMutableArray arrayWithObject:[self defaultSTUNServer]];
|
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (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;
|
|
|
|
NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl];
|
|
|
|
[self requestTURNServersWithURL:turnRequestURL
|
|
|
|
completionHandler:^(NSArray *turnServers) {
|
|
|
|
ARDAppClient *strongSelf = weakSelf;
|
|
|
|
[strongSelf.iceServers addObjectsFromArray:turnServers];
|
|
|
|
strongSelf.isTurnComplete = YES;
|
|
|
|
[strongSelf startSignalingIfReady];
|
|
|
|
}];
|
2014-12-31 00:26:20 +00:00
|
|
|
|
2014-12-09 19:32:35 +00:00
|
|
|
// Register with room server.
|
|
|
|
[self registerWithRoomServerForRoomId:roomId
|
|
|
|
completionHandler:^(ARDRegisterResponse *response) {
|
|
|
|
ARDAppClient *strongSelf = weakSelf;
|
|
|
|
if (!response || response.result != kARDRegisterResultTypeSuccess) {
|
|
|
|
NSLog(@"Failed to register with room server. Result:%d",
|
|
|
|
(int)response.result);
|
|
|
|
[strongSelf disconnect];
|
|
|
|
NSDictionary *userInfo = @{
|
|
|
|
NSLocalizedDescriptionKey: @"Room is full.",
|
|
|
|
};
|
|
|
|
NSError *error =
|
|
|
|
[[NSError alloc] initWithDomain:kARDAppClientErrorDomain
|
|
|
|
code:kARDAppClientErrorRoomFull
|
|
|
|
userInfo:userInfo];
|
|
|
|
[strongSelf.delegate appClient:strongSelf didError:error];
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
NSLog(@"Registered with room server.");
|
|
|
|
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.isRegisteredWithRoomServer) {
|
|
|
|
[self unregisterWithRoomServer];
|
|
|
|
}
|
|
|
|
if (_channel) {
|
|
|
|
if (_channel.state == kARDWebSocketChannelStateRegistered) {
|
|
|
|
// Tell the other client we're hanging up.
|
|
|
|
ARDByeMessage *byeMessage = [[ARDByeMessage alloc] init];
|
|
|
|
NSData *byeData = [byeMessage JSONData];
|
|
|
|
[_channel sendData:byeData];
|
|
|
|
}
|
|
|
|
// Disconnect from collider.
|
|
|
|
_channel = nil;
|
|
|
|
}
|
|
|
|
_clientId = nil;
|
|
|
|
_roomId = nil;
|
|
|
|
_isInitiator = NO;
|
|
|
|
_hasReceivedSdp = NO;
|
|
|
|
_messageQueue = [NSMutableArray array];
|
|
|
|
_peerConnection = nil;
|
|
|
|
self.state = kARDAppClientStateDisconnected;
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - ARDWebSocketChannelDelegate
|
|
|
|
|
|
|
|
- (void)channel:(ARDWebSocketChannel *)channel
|
|
|
|
didReceiveMessage:(ARDSignalingMessage *)message {
|
|
|
|
switch (message.type) {
|
|
|
|
case kARDSignalingMessageTypeOffer:
|
|
|
|
case kARDSignalingMessageTypeAnswer:
|
|
|
|
_hasReceivedSdp = YES;
|
|
|
|
[_messageQueue insertObject:message atIndex:0];
|
|
|
|
break;
|
|
|
|
case kARDSignalingMessageTypeCandidate:
|
|
|
|
[_messageQueue addObject:message];
|
|
|
|
break;
|
|
|
|
case kARDSignalingMessageTypeBye:
|
|
|
|
[self processSignalingMessage:message];
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
[self drainMessageQueueIfReady];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)channel:(ARDWebSocketChannel *)channel
|
|
|
|
didChangeState:(ARDWebSocketChannelState)state {
|
|
|
|
switch (state) {
|
|
|
|
case kARDWebSocketChannelStateOpen:
|
|
|
|
break;
|
|
|
|
case kARDWebSocketChannelStateRegistered:
|
|
|
|
break;
|
|
|
|
case kARDWebSocketChannelStateClosed:
|
|
|
|
case kARDWebSocketChannelStateError:
|
|
|
|
// TODO(tkchin): reconnection scenarios. Right now we just disconnect
|
|
|
|
// completely if the websocket connection fails.
|
|
|
|
[self disconnect];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - RTCPeerConnectionDelegate
|
|
|
|
|
|
|
|
- (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);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (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
|
|
|
|
|
|
|
|
- (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)isRegisteredWithRoomServer {
|
|
|
|
return _clientId.length;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)startSignalingIfReady {
|
|
|
|
if (!_isTurnComplete || !self.isRegisteredWithRoomServer) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
self.state = kARDAppClientStateConnected;
|
|
|
|
|
|
|
|
// Create peer connection.
|
|
|
|
RTCMediaConstraints *constraints = [self defaultPeerConnectionConstraints];
|
|
|
|
_peerConnection = [_factory peerConnectionWithICEServers:_iceServers
|
|
|
|
constraints:constraints
|
|
|
|
delegate:self];
|
|
|
|
RTCMediaStream *localStream = [self createLocalMediaStream];
|
|
|
|
[_peerConnection addStream:localStream];
|
|
|
|
if (_isInitiator) {
|
|
|
|
[self sendOffer];
|
|
|
|
} else {
|
|
|
|
[self waitForAnswer];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)sendOffer {
|
|
|
|
[_peerConnection createOfferWithDelegate:self
|
|
|
|
constraints:[self defaultOfferConstraints]];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)waitForAnswer {
|
|
|
|
[self drainMessageQueueIfReady];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)drainMessageQueueIfReady {
|
|
|
|
if (!_peerConnection || !_hasReceivedSdp) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
for (ARDSignalingMessage *message in _messageQueue) {
|
|
|
|
[self processSignalingMessage:message];
|
|
|
|
}
|
|
|
|
[_messageQueue removeAllObjects];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)sendSignalingMessage:(ARDSignalingMessage *)message {
|
|
|
|
if (_isInitiator) {
|
|
|
|
[self sendSignalingMessageToRoomServer:message completionHandler:nil];
|
|
|
|
} else {
|
|
|
|
[self sendSignalingMessageToCollider: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;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)requestTURNServersWithURL:(NSURL *)requestURL
|
|
|
|
completionHandler:(void (^)(NSArray *turnServers))completionHandler {
|
|
|
|
NSParameterAssert([requestURL absoluteString].length);
|
|
|
|
NSMutableURLRequest *request =
|
|
|
|
[NSMutableURLRequest requestWithURL:requestURL];
|
|
|
|
// We need to set origin because TURN provider whitelists requests based on
|
|
|
|
// origin.
|
|
|
|
[request addValue:@"Mozilla/5.0" forHTTPHeaderField:@"user-agent"];
|
|
|
|
[request addValue:kARDRoomServerHostUrl forHTTPHeaderField:@"origin"];
|
|
|
|
[NSURLConnection sendAsyncRequest:request
|
|
|
|
completionHandler:^(NSURLResponse *response,
|
|
|
|
NSData *data,
|
|
|
|
NSError *error) {
|
|
|
|
NSArray *turnServers = [NSArray array];
|
|
|
|
if (error) {
|
|
|
|
NSLog(@"Unable to get TURN server.");
|
|
|
|
completionHandler(turnServers);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
NSDictionary *dict = [NSDictionary dictionaryWithJSONData:data];
|
|
|
|
turnServers = [RTCICEServer serversFromCEODJSONDictionary:dict];
|
|
|
|
completionHandler(turnServers);
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - Room server methods
|
|
|
|
|
|
|
|
- (void)registerWithRoomServerForRoomId:(NSString *)roomId
|
|
|
|
completionHandler:(void (^)(ARDRegisterResponse *))completionHandler {
|
|
|
|
NSString *urlString =
|
|
|
|
[NSString stringWithFormat:kARDRoomServerRegisterFormat, roomId];
|
|
|
|
NSURL *roomURL = [NSURL URLWithString:urlString];
|
|
|
|
NSLog(@"Registering with room server.");
|
|
|
|
__weak ARDAppClient *weakSelf = self;
|
|
|
|
[NSURLConnection sendAsyncPostToURL:roomURL
|
|
|
|
withData:nil
|
|
|
|
completionHandler:^(BOOL succeeded, NSData *data) {
|
|
|
|
ARDAppClient *strongSelf = weakSelf;
|
|
|
|
if (!succeeded) {
|
|
|
|
NSError *error = [self roomServerNetworkError];
|
|
|
|
[strongSelf.delegate appClient:strongSelf didError:error];
|
|
|
|
completionHandler(nil);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ARDRegisterResponse *response =
|
|
|
|
[ARDRegisterResponse responseFromJSONData:data];
|
|
|
|
completionHandler(response);
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)sendSignalingMessageToRoomServer:(ARDSignalingMessage *)message
|
|
|
|
completionHandler:(void (^)(ARDMessageResponse *))completionHandler {
|
|
|
|
NSData *data = [message JSONData];
|
|
|
|
NSString *urlString =
|
|
|
|
[NSString stringWithFormat:
|
|
|
|
kARDRoomServerMessageFormat, _roomId, _clientId];
|
|
|
|
NSURL *url = [NSURL URLWithString:urlString];
|
|
|
|
NSLog(@"C->RS POST: %@", message);
|
|
|
|
__weak ARDAppClient *weakSelf = self;
|
|
|
|
[NSURLConnection sendAsyncPostToURL:url
|
|
|
|
withData:data
|
|
|
|
completionHandler:^(BOOL succeeded, NSData *data) {
|
|
|
|
ARDAppClient *strongSelf = weakSelf;
|
|
|
|
if (!succeeded) {
|
|
|
|
NSError *error = [self roomServerNetworkError];
|
|
|
|
[strongSelf.delegate appClient:strongSelf didError:error];
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ARDMessageResponse *response =
|
|
|
|
[ARDMessageResponse responseFromJSONData:data];
|
|
|
|
NSError *error = nil;
|
|
|
|
switch (response.result) {
|
|
|
|
case kARDMessageResultTypeSuccess:
|
|
|
|
break;
|
|
|
|
case kARDMessageResultTypeUnknown:
|
|
|
|
error =
|
|
|
|
[[NSError alloc] initWithDomain:kARDAppClientErrorDomain
|
|
|
|
code:kARDAppClientErrorUnknown
|
|
|
|
userInfo:@{
|
|
|
|
NSLocalizedDescriptionKey: @"Unknown error.",
|
|
|
|
}];
|
|
|
|
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;
|
|
|
|
};
|
|
|
|
if (error) {
|
|
|
|
[strongSelf.delegate appClient:strongSelf didError:error];
|
|
|
|
}
|
|
|
|
if (completionHandler) {
|
|
|
|
completionHandler(response);
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)unregisterWithRoomServer {
|
|
|
|
NSString *urlString =
|
|
|
|
[NSString stringWithFormat:kARDRoomServerByeFormat, _roomId, _clientId];
|
|
|
|
NSURL *url = [NSURL URLWithString:urlString];
|
|
|
|
NSURLRequest *request = [NSURLRequest requestWithURL:url];
|
|
|
|
NSURLResponse *response = nil;
|
|
|
|
// We want a synchronous request so that we know that we're unregistered from
|
|
|
|
// room server before we do any further unregistration.
|
|
|
|
NSLog(@"C->RS: BYE");
|
|
|
|
NSError *error = nil;
|
|
|
|
[NSURLConnection sendSynchronousRequest:request
|
|
|
|
returningResponse:&response
|
|
|
|
error:&error];
|
|
|
|
if (error) {
|
|
|
|
NSLog(@"Error unregistering from room server: %@", error);
|
|
|
|
}
|
|
|
|
NSLog(@"Unregistered from room server.");
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSError *)roomServerNetworkError {
|
|
|
|
NSError *error =
|
|
|
|
[[NSError alloc] initWithDomain:kARDAppClientErrorDomain
|
|
|
|
code:kARDAppClientErrorNetwork
|
|
|
|
userInfo:@{
|
|
|
|
NSLocalizedDescriptionKey: @"Room server network error",
|
|
|
|
}];
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - Collider methods
|
|
|
|
|
|
|
|
- (void)registerWithColliderIfReady {
|
|
|
|
if (!self.isRegisteredWithRoomServer) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Open WebSocket connection.
|
|
|
|
_channel =
|
|
|
|
[[ARDWebSocketChannel alloc] initWithURL:_websocketURL
|
|
|
|
restURL:_websocketRestURL
|
|
|
|
delegate:self];
|
|
|
|
[_channel registerForRoomId:_roomId clientId:_clientId];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)sendSignalingMessageToCollider:(ARDSignalingMessage *)message {
|
|
|
|
NSData *data = [message JSONData];
|
|
|
|
[_channel sendData:data];
|
|
|
|
}
|
|
|
|
|
|
|
|
#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 {
|
|
|
|
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:@""];
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|