iAppRTCDemo: WebSocket based signaling.

Updates the iOS code to use the new signaling model. Removes old Channel API
code. Note that this no longer logs messages to UI. UI update forthcoming.

BUG=
R=glaznev@webrtc.org, jiayl@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/35369004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@7852 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
tkchin@webrtc.org 2014-12-09 19:32:35 +00:00
parent 0babb4a4e6
commit 87776a8935
26 changed files with 3481 additions and 1201 deletions

View File

@ -1,223 +0,0 @@
/*
* libjingle
* Copyright 2013, 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.
*/
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
#import "APPRTCAppClient.h"
#import <dispatch/dispatch.h>
#import "ARDSignalingParams.h"
#import "ARDUtilities.h"
#import "GAEChannelClient.h"
#import "RTCICEServer.h"
#import "RTCICEServer+JSON.h"
#import "RTCMediaConstraints.h"
#import "RTCPair.h"
@implementation APPRTCAppClient {
dispatch_queue_t _backgroundQueue;
GAEChannelClient* _gaeChannel;
NSURL* _postMessageURL;
BOOL _verboseLogging;
__weak id<GAEMessageHandler> _messageHandler;
}
- (instancetype)initWithDelegate:(id<APPRTCAppClientDelegate>)delegate
messageHandler:(id<GAEMessageHandler>)handler {
if (self = [super init]) {
_delegate = delegate;
_messageHandler = handler;
_backgroundQueue = dispatch_queue_create("RTCBackgroundQueue",
DISPATCH_QUEUE_SERIAL);
// Uncomment to see Request/Response logging.
// _verboseLogging = YES;
}
return self;
}
- (void)connectToRoom:(NSURL*)url {
NSString *urlString =
[[url absoluteString] stringByAppendingString:@"&t=json"];
NSURL *requestURL = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:requestURL];
[NSURLConnection sendAsynchronousRequest:request
completionHandler:^(NSURLResponse *response,
NSData *data,
NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
int statusCode = [httpResponse statusCode];
[self logVerbose:[NSString stringWithFormat:
@"Response received\nURL\n%@\nStatus [%d]\nHeaders\n%@",
[httpResponse URL],
statusCode,
[httpResponse allHeaderFields]]];
NSAssert(statusCode == 200,
@"Invalid response of %d received while connecting to: %@",
statusCode,
urlString);
if (statusCode != 200) {
return;
}
[self handleResponseData:data forRoomRequest:request];
}];
}
- (void)sendData:(NSData*)data {
NSParameterAssert([data length] > 0);
NSString *message = [NSString stringWithUTF8String:[data bytes]];
[self logVerbose:[NSString stringWithFormat:@"Send message:\n%@", message]];
if (!_postMessageURL) {
return;
}
NSMutableURLRequest *request =
[NSMutableURLRequest requestWithURL:_postMessageURL];
request.HTTPMethod = @"POST";
[request setHTTPBody:data];
[NSURLConnection sendAsynchronousRequest:request
completionHandler:^(NSURLResponse *response,
NSData *data,
NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
int status = [httpResponse statusCode];
NSString *responseString = [data length] > 0 ?
[NSString stringWithUTF8String:[data bytes]] :
nil;
NSAssert(status == 200,
@"Bad response [%d] to message: %@\n\n%@",
status,
message,
responseString);
}];
}
#pragma mark - Private
- (void)logVerbose:(NSString *)message {
if (_verboseLogging) {
NSLog(@"%@", message);
}
}
- (void)handleResponseData:(NSData *)responseData
forRoomRequest:(NSURLRequest *)request {
ARDSignalingParams *params =
[ARDSignalingParams paramsFromJSONData:responseData];
if (params.errorMessages.count > 0) {
NSMutableString *message = [NSMutableString string];
for (NSString *errorMessage in params.errorMessages) {
[message appendFormat:@"%@\n", errorMessage];
}
[self.delegate appClient:self didErrorWithMessage:message];
return;
}
[self requestTURNServerForICEServers:params.iceServers
turnServerUrl:[params.turnRequestURL absoluteString]];
NSString *token = params.channelToken;
[self logVerbose:
[NSString stringWithFormat:@"About to open GAE with token: %@",
token]];
_gaeChannel =
[[GAEChannelClient alloc] initWithToken:token
delegate:_messageHandler];
_params = params;
// Generate URL for posting data.
NSDictionary *roomJSON = [NSDictionary dictionaryWithJSONData:responseData];
_postMessageURL = [self parsePostMessageURLForRoomJSON:roomJSON
request:request];
[self logVerbose:[NSString stringWithFormat:@"POST message URL:\n%@",
_postMessageURL]];
}
- (NSURL*)parsePostMessageURLForRoomJSON:(NSDictionary*)roomJSON
request:(NSURLRequest*)request {
NSString* requestUrl = [[request URL] absoluteString];
NSRange queryRange = [requestUrl rangeOfString:@"?"];
NSString* baseUrl = [requestUrl substringToIndex:queryRange.location];
NSString* roomKey = roomJSON[@"room_key"];
NSParameterAssert([roomKey length] > 0);
NSString* me = roomJSON[@"me"];
NSParameterAssert([me length] > 0);
NSString* postMessageUrl =
[NSString stringWithFormat:@"%@/message?r=%@&u=%@", baseUrl, roomKey, me];
return [NSURL URLWithString:postMessageUrl];
}
- (void)requestTURNServerWithUrl:(NSString *)turnServerUrl
completionHandler:
(void (^)(RTCICEServer *turnServer))completionHandler {
NSURL *turnServerURL = [NSURL URLWithString:turnServerUrl];
NSMutableURLRequest *request =
[NSMutableURLRequest requestWithURL:turnServerURL];
[request addValue:@"Mozilla/5.0" forHTTPHeaderField:@"user-agent"];
[request addValue:@"https://apprtc.appspot.com"
forHTTPHeaderField:@"origin"];
[NSURLConnection sendAsynchronousRequest:request
completionHandler:^(NSURLResponse *response,
NSData *data,
NSError *error) {
if (error) {
NSLog(@"Unable to get TURN server.");
completionHandler(nil);
return;
}
NSDictionary *json = [NSDictionary dictionaryWithJSONData:data];
RTCICEServer *turnServer = [RTCICEServer serverFromCEODJSONDictionary:json];
completionHandler(turnServer);
}];
}
- (void)requestTURNServerForICEServers:(NSArray*)iceServers
turnServerUrl:(NSString*)turnServerUrl {
BOOL isTurnPresent = NO;
for (RTCICEServer* iceServer in iceServers) {
if ([[iceServer.URI scheme] isEqualToString:@"turn"]) {
isTurnPresent = YES;
break;
}
}
if (!isTurnPresent) {
[self requestTURNServerWithUrl:turnServerUrl
completionHandler:^(RTCICEServer* turnServer) {
NSArray* servers = iceServers;
if (turnServer) {
servers = [servers arrayByAddingObject:turnServer];
}
NSLog(@"ICE servers:\n%@", servers);
[self.delegate appClient:self didReceiveICEServers:servers];
}];
} else {
NSLog(@"ICE servers:\n%@", iceServers);
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate appClient:self didReceiveICEServers:iceServers];
});
}
}
@end

View File

@ -1,390 +0,0 @@
/*
* 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 "APPRTCConnectionManager.h"
#import <AVFoundation/AVFoundation.h>
#import "APPRTCAppClient.h"
#import "GAEChannelClient.h"
#import "RTCICECandidate.h"
#import "RTCICECandidate+JSON.h"
#import "RTCMediaConstraints.h"
#import "RTCMediaStream.h"
#import "RTCPair.h"
#import "RTCPeerConnection.h"
#import "RTCPeerConnectionDelegate.h"
#import "RTCPeerConnectionFactory.h"
#import "RTCSessionDescription.h"
#import "RTCSessionDescription+JSON.h"
#import "RTCSessionDescriptionDelegate.h"
#import "RTCStatsDelegate.h"
#import "RTCVideoCapturer.h"
#import "RTCVideoSource.h"
@interface APPRTCConnectionManager ()
<APPRTCAppClientDelegate, GAEMessageHandler, RTCPeerConnectionDelegate,
RTCSessionDescriptionDelegate, RTCStatsDelegate>
@property(nonatomic, strong) APPRTCAppClient* client;
@property(nonatomic, strong) RTCPeerConnection* peerConnection;
@property(nonatomic, strong) RTCPeerConnectionFactory* peerConnectionFactory;
@property(nonatomic, strong) RTCVideoSource* videoSource;
@property(nonatomic, strong) NSMutableArray* queuedRemoteCandidates;
@end
@implementation APPRTCConnectionManager {
NSTimer* _statsTimer;
}
- (instancetype)initWithDelegate:(id<APPRTCConnectionManagerDelegate>)delegate
logger:(id<APPRTCLogger>)logger {
if (self = [super init]) {
self.delegate = delegate;
self.logger = logger;
self.peerConnectionFactory = [[RTCPeerConnectionFactory alloc] init];
// TODO(tkchin): turn this into a button.
// Uncomment for stat logs.
// _statsTimer =
// [NSTimer scheduledTimerWithTimeInterval:10
// target:self
// selector:@selector(didFireStatsTimer:)
// userInfo:nil
// repeats:YES];
}
return self;
}
- (void)dealloc {
[self disconnect];
}
- (BOOL)connectToRoomWithURL:(NSURL*)url {
if (self.client) {
// Already have a connection.
return NO;
}
self.client = [[APPRTCAppClient alloc] initWithDelegate:self
messageHandler:self];
[self.client connectToRoom:url];
return YES;
}
- (void)disconnect {
if (!self.client) {
return;
}
[self.client
sendData:[@"{\"type\": \"bye\"}" dataUsingEncoding:NSUTF8StringEncoding]];
[self.peerConnection close];
self.peerConnection = nil;
self.client = nil;
self.videoSource = nil;
self.queuedRemoteCandidates = nil;
}
#pragma mark - APPRTCAppClientDelegate
- (void)appClient:(APPRTCAppClient*)appClient
didErrorWithMessage:(NSString*)message {
[self.delegate connectionManager:self
didErrorWithMessage:message];
}
- (void)appClient:(APPRTCAppClient*)appClient
didReceiveICEServers:(NSArray*)servers {
self.queuedRemoteCandidates = [NSMutableArray array];
RTCMediaConstraints* constraints = [[RTCMediaConstraints alloc]
initWithMandatoryConstraints:
@[
[[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" value:@"true"],
[[RTCPair alloc] initWithKey:@"OfferToReceiveVideo" value:@"true"]
]
optionalConstraints:
@[
[[RTCPair alloc] initWithKey:@"internalSctpDataChannels"
value:@"true"],
[[RTCPair alloc] initWithKey:@"DtlsSrtpKeyAgreement"
value:@"true"]
]];
self.peerConnection =
[self.peerConnectionFactory peerConnectionWithICEServers:servers
constraints:constraints
delegate:self];
RTCMediaStream* lms =
[self.peerConnectionFactory mediaStreamWithLabel:@"ARDAMS"];
// 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.
RTCVideoTrack* localVideoTrack;
// 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];
self.videoSource = [self.peerConnectionFactory
videoSourceWithCapturer:capturer
constraints:self.client.params.mediaConstraints];
localVideoTrack =
[self.peerConnectionFactory videoTrackWithID:@"ARDAMSv0"
source:self.videoSource];
if (localVideoTrack) {
[lms addVideoTrack:localVideoTrack];
}
[self.delegate connectionManager:self
didReceiveLocalVideoTrack:localVideoTrack];
#endif
[lms addAudioTrack:[self.peerConnectionFactory audioTrackWithID:@"ARDAMSa0"]];
[self.peerConnection addStream:lms];
[self.logger logMessage:@"onICEServers - added local stream."];
}
#pragma mark - GAEMessageHandler methods
- (void)onOpen {
if (!self.client.params.isInitiator) {
[self.logger logMessage:@"Callee; waiting for remote offer"];
return;
}
[self.logger logMessage:@"GAE onOpen - create offer."];
RTCPair* audio =
[[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" value:@"true"];
RTCPair* video =
[[RTCPair alloc] initWithKey:@"OfferToReceiveVideo" value:@"true"];
NSArray* mandatory = @[ audio, video ];
RTCMediaConstraints* constraints =
[[RTCMediaConstraints alloc] initWithMandatoryConstraints:mandatory
optionalConstraints:nil];
[self.peerConnection createOfferWithDelegate:self constraints:constraints];
[self.logger logMessage:@"PC - createOffer."];
}
- (void)onMessage:(NSDictionary*)messageData {
NSString* type = messageData[@"type"];
NSAssert(type, @"Missing type: %@", messageData);
[self.logger logMessage:[NSString stringWithFormat:@"GAE onMessage type - %@",
type]];
if ([type isEqualToString:@"candidate"]) {
RTCICECandidate* candidate =
[RTCICECandidate candidateFromJSONDictionary:messageData];
if (self.queuedRemoteCandidates) {
[self.queuedRemoteCandidates addObject:candidate];
} else {
[self.peerConnection addICECandidate:candidate];
}
} else if ([type isEqualToString:@"offer"] ||
[type isEqualToString:@"answer"]) {
RTCSessionDescription* sdp =
[RTCSessionDescription descriptionFromJSONDictionary:messageData];
[self.peerConnection setRemoteDescriptionWithDelegate:self
sessionDescription:sdp];
[self.logger logMessage:@"PC - setRemoteDescription."];
} else if ([type isEqualToString:@"bye"]) {
[self.delegate connectionManagerDidReceiveHangup:self];
} else {
NSAssert(NO, @"Invalid message: %@", messageData);
}
}
- (void)onClose {
[self.logger logMessage:@"GAE onClose."];
[self.delegate connectionManagerDidReceiveHangup:self];
}
- (void)onError:(int)code withDescription:(NSString*)description {
NSString* message = [NSString stringWithFormat:@"GAE onError: %d, %@",
code, description];
[self.logger logMessage:message];
[self.delegate connectionManager:self
didErrorWithMessage:message];
}
#pragma mark - RTCPeerConnectionDelegate
- (void)peerConnection:(RTCPeerConnection*)peerConnection
signalingStateChanged:(RTCSignalingState)stateChanged {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"PCO onSignalingStateChange: %d", stateChanged);
});
}
- (void)peerConnection:(RTCPeerConnection*)peerConnection
addedStream:(RTCMediaStream*)stream {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"PCO onAddStream.");
NSAssert([stream.audioTracks count] == 1 || [stream.videoTracks count] == 1,
@"Expected audio or video track");
NSAssert([stream.audioTracks count] <= 1,
@"Expected at most 1 audio stream");
NSAssert([stream.videoTracks count] <= 1,
@"Expected at most 1 video stream");
if ([stream.videoTracks count] != 0) {
[self.delegate connectionManager:self
didReceiveRemoteVideoTrack:stream.videoTracks[0]];
}
});
}
- (void)peerConnection:(RTCPeerConnection*)peerConnection
removedStream:(RTCMediaStream*)stream {
dispatch_async(dispatch_get_main_queue(),
^{ NSLog(@"PCO onRemoveStream."); });
}
- (void)peerConnectionOnRenegotiationNeeded:(RTCPeerConnection*)peerConnection {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"PCO onRenegotiationNeeded - ignoring because AppRTC has a "
"predefined negotiation strategy");
});
}
- (void)peerConnection:(RTCPeerConnection*)peerConnection
gotICECandidate:(RTCICECandidate*)candidate {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"PCO onICECandidate.\n%@", candidate);
[self.client sendData:[candidate JSONData]];
});
}
- (void)peerConnection:(RTCPeerConnection*)peerConnection
iceGatheringChanged:(RTCICEGatheringState)newState {
dispatch_async(dispatch_get_main_queue(),
^{ NSLog(@"PCO onIceGatheringChange. %d", newState); });
}
- (void)peerConnection:(RTCPeerConnection*)peerConnection
iceConnectionChanged:(RTCICEConnectionState)newState {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"PCO onIceConnectionChange. %d", newState);
if (newState == RTCICEConnectionConnected)
[self.logger logMessage:@"ICE Connection Connected."];
NSAssert(newState != RTCICEConnectionFailed, @"ICE Connection failed!");
});
}
- (void)peerConnection:(RTCPeerConnection*)peerConnection
didOpenDataChannel:(RTCDataChannel*)dataChannel {
NSAssert(NO, @"AppRTC doesn't use DataChannels");
}
#pragma mark - RTCSessionDescriptionDelegate
- (void)peerConnection:(RTCPeerConnection*)peerConnection
didCreateSessionDescription:(RTCSessionDescription*)sdp
error:(NSError*)error {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
[self.logger logMessage:@"SDP onFailure."];
NSAssert(NO, error.description);
return;
}
[self.logger logMessage:@"SDP onSuccess(SDP) - set local description."];
[self.peerConnection setLocalDescriptionWithDelegate:self
sessionDescription:sdp];
[self.logger logMessage:@"PC setLocalDescription."];
[self.client sendData:[sdp JSONData]];
});
}
- (void)peerConnection:(RTCPeerConnection*)peerConnection
didSetSessionDescriptionWithError:(NSError*)error {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
[self.logger logMessage:@"SDP onFailure."];
NSAssert(NO, error.description);
return;
}
[self.logger logMessage:@"SDP onSuccess() - possibly drain candidates"];
if (!self.client.params.isInitiator) {
if (self.peerConnection.remoteDescription &&
!self.peerConnection.localDescription) {
[self.logger logMessage:@"Callee, setRemoteDescription succeeded"];
RTCPair* audio = [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio"
value:@"true"];
RTCPair* video = [[RTCPair alloc] initWithKey:@"OfferToReceiveVideo"
value:@"true"];
NSArray* mandatory = @[ audio, video ];
RTCMediaConstraints* constraints = [[RTCMediaConstraints alloc]
initWithMandatoryConstraints:mandatory
optionalConstraints:nil];
[self.peerConnection createAnswerWithDelegate:self
constraints:constraints];
[self.logger logMessage:@"PC - createAnswer."];
} else {
[self.logger logMessage:@"SDP onSuccess - drain candidates"];
[self drainRemoteCandidates];
}
} else {
if (self.peerConnection.remoteDescription) {
[self.logger logMessage:@"SDP onSuccess - drain candidates"];
[self drainRemoteCandidates];
}
}
});
}
#pragma mark - RTCStatsDelegate methods
- (void)peerConnection:(RTCPeerConnection*)peerConnection
didGetStats:(NSArray*)stats {
dispatch_async(dispatch_get_main_queue(), ^{
NSString* message = [NSString stringWithFormat:@"Stats:\n %@", stats];
[self.logger logMessage:message];
});
}
#pragma mark - Private
- (void)drainRemoteCandidates {
for (RTCICECandidate* candidate in self.queuedRemoteCandidates) {
[self.peerConnection addICECandidate:candidate];
}
self.queuedRemoteCandidates = nil;
}
- (void)didFireStatsTimer:(NSTimer*)timer {
if (self.peerConnection) {
[self.peerConnection getStatsWithDelegate:self
mediaStreamTrack:nil
statsOutputLevel:RTCStatsOutputLevelDebug];
}
}
@end

View File

@ -1,6 +1,6 @@
/*
* libjingle
* Copyright 2013, Google Inc.
* 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:
@ -27,42 +27,50 @@
#import <Foundation/Foundation.h>
#import "ARDSignalingParams.h"
#import "GAEChannelClient.h"
#import "RTCVideoTrack.h"
@class APPRTCAppClient;
@protocol APPRTCAppClientDelegate
typedef NS_ENUM(NSInteger, ARDAppClientState) {
// Disconnected from servers.
kARDAppClientStateDisconnected,
// Connecting to servers.
kARDAppClientStateConnecting,
// Connected to servers.
kARDAppClientStateConnected,
};
- (void)appClient:(APPRTCAppClient*)appClient
didErrorWithMessage:(NSString*)message;
- (void)appClient:(APPRTCAppClient*)appClient
didReceiveICEServers:(NSArray*)servers;
@class ARDAppClient;
@protocol ARDAppClientDelegate <NSObject>
- (void)appClient:(ARDAppClient *)client
didChangeState:(ARDAppClientState)state;
- (void)appClient:(ARDAppClient *)client
didReceiveLocalVideoTrack:(RTCVideoTrack *)localVideoTrack;
- (void)appClient:(ARDAppClient *)client
didReceiveRemoteVideoTrack:(RTCVideoTrack *)remoteVideoTrack;
- (void)appClient:(ARDAppClient *)client
didError:(NSError *)error;
@end
@class RTCMediaConstraints;
// Handles connections to the AppRTC server for a given room.
@interface ARDAppClient : NSObject
// Negotiates signaling for chatting with apprtc.appspot.com "rooms".
// Uses the client<->server specifics of the apprtc AppEngine webapp.
//
// To use: create an instance of this object (registering a message handler) and
// call connectToRoom(). apprtc.appspot.com will signal that is successful via
// onOpen through the browser channel. Then you should call sendData() and wait
// for the registered handler to be called with received messages.
@interface APPRTCAppClient : NSObject
@property(nonatomic, readonly) ARDAppClientState state;
@property(nonatomic, weak) id<ARDAppClientDelegate> delegate;
@property(nonatomic, readonly) ARDSignalingParams *params;
@property(nonatomic, weak) id<APPRTCAppClientDelegate> delegate;
- (instancetype)initWithDelegate:(id<ARDAppClientDelegate>)delegate;
- (instancetype)initWithDelegate:(id<APPRTCAppClientDelegate>)delegate
messageHandler:(id<GAEMessageHandler>)handler;
- (void)connectToRoom:(NSURL *)room;
- (void)sendData:(NSData *)data;
// Establishes a connection with the AppRTC servers for the given room id.
// TODO(tkchin): provide available keys/values for options. This will be used
// for call configurations such as overriding server choice, specifying codecs
// and so on.
- (void)connectToRoomWithId:(NSString *)roomId
options:(NSDictionary *)options;
#ifndef DOXYGEN_SHOULD_SKIP_THIS
// Disallow init and don't add to documentation
- (instancetype)init __attribute__((
unavailable("init is not a supported initializer for this class.")));
#endif /* DOXYGEN_SHOULD_SKIP_THIS */
// Disconnects from the AppRTC servers and any connected clients.
- (void)disconnect;
@end

View File

@ -0,0 +1,675 @@
/*
* 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 =
@"https://3-dot-apprtc.appspot.com";
static NSString *kARDRoomServerRegisterFormat =
@"https://3-dot-apprtc.appspot.com/register/%@";
static NSString *kARDRoomServerMessageFormat =
@"https://3-dot-apprtc.appspot.com/message/%@/%@";
static NSString *kARDRoomServerByeFormat =
@"https://3-dot-apprtc.appspot.com/bye/%@/%@";
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];
}];
// 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

View File

@ -27,20 +27,17 @@
#import <Foundation/Foundation.h>
#import "RTCMediaConstraints.h"
typedef NS_ENUM(NSInteger, ARDMessageResultType) {
kARDMessageResultTypeUnknown,
kARDMessageResultTypeSuccess,
kARDMessageResultTypeInvalidRoom,
kARDMessageResultTypeInvalidClient
};
// Struct for holding the signaling parameters of an AppRTC room.
@interface ARDSignalingParams : NSObject
@interface ARDMessageResponse : NSObject
@property(nonatomic, assign) BOOL isInitiator;
@property(nonatomic, readonly) NSArray *errorMessages;
@property(nonatomic, readonly) RTCMediaConstraints *offerConstraints;
@property(nonatomic, readonly) RTCMediaConstraints *mediaConstraints;
@property(nonatomic, readonly) NSMutableArray *iceServers;
@property(nonatomic, readonly) NSURL *signalingServerURL;
@property(nonatomic, readonly) NSURL *turnRequestURL;
@property(nonatomic, readonly) NSString *channelToken;
@property(nonatomic, readonly) ARDMessageResultType result;
+ (ARDSignalingParams *)paramsFromJSONData:(NSData *)data;
+ (ARDMessageResponse *)responseFromJSONData:(NSData *)data;
@end

View File

@ -0,0 +1,69 @@
/*
* 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 "ARDMessageResponse.h"
#import "ARDUtilities.h"
static NSString const *kARDMessageResultKey = @"result";
@interface ARDMessageResponse ()
@property(nonatomic, assign) ARDMessageResultType result;
@end
@implementation ARDMessageResponse
@synthesize result = _result;
+ (ARDMessageResponse *)responseFromJSONData:(NSData *)data {
NSDictionary *responseJSON = [NSDictionary dictionaryWithJSONData:data];
if (!responseJSON) {
return nil;
}
ARDMessageResponse *response = [[ARDMessageResponse alloc] init];
response.result =
[[self class] resultTypeFromString:responseJSON[kARDMessageResultKey]];
return response;
}
#pragma mark - Private
+ (ARDMessageResultType)resultTypeFromString:(NSString *)resultString {
ARDMessageResultType result = kARDMessageResultTypeUnknown;
if ([resultString isEqualToString:@"SUCCESS"]) {
result = kARDMessageResultTypeSuccess;
} else if ([resultString isEqualToString:@"INVALID_CLIENT"]) {
result = kARDMessageResultTypeInvalidClient;
} else if ([resultString isEqualToString:@"INVALID_ROOM"]) {
result = kARDMessageResultTypeInvalidRoom;
}
return result;
}
@end

View File

@ -1,6 +1,6 @@
/*
* libjingle
* Copyright 2013, Google Inc.
* 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:
@ -27,26 +27,23 @@
#import <Foundation/Foundation.h>
// These methods will be called by the AppEngine chanel. The documentation
// for these methods is found here. (Yes, it is a JS API.)
// https://developers.google.com/appengine/docs/java/channel/javascript
@protocol GAEMessageHandler<NSObject>
typedef NS_ENUM(NSInteger, ARDRegisterResultType) {
kARDRegisterResultTypeUnknown,
kARDRegisterResultTypeSuccess,
kARDRegisterResultTypeFull
};
- (void)onOpen;
- (void)onMessage:(NSDictionary*)data;
- (void)onClose;
- (void)onError:(int)code withDescription:(NSString*)description;
@end
// Initialize with a token for an AppRTC data channel. This will load
// ios_channel.html and use the token to establish a data channel between the
// application and AppEngine.
@interface GAEChannelClient : NSObject
@property(nonatomic, weak) id<GAEMessageHandler> delegate;
- (instancetype)initWithToken:(NSString*)token
delegate:(id<GAEMessageHandler>)delegate;
// Result of registering with the GAE server.
@interface ARDRegisterResponse : NSObject
@property(nonatomic, readonly) ARDRegisterResultType result;
@property(nonatomic, readonly) BOOL isInitiator;
@property(nonatomic, readonly) NSString *roomId;
@property(nonatomic, readonly) NSString *clientId;
@property(nonatomic, readonly) NSArray *messages;
@property(nonatomic, readonly) NSURL *webSocketURL;
@property(nonatomic, readonly) NSURL *webSocketRestURL;
+ (ARDRegisterResponse *)responseFromJSONData:(NSData *)data;
@end

View File

@ -0,0 +1,111 @@
/*
* 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 "ARDRegisterResponse.h"
#import "ARDSignalingMessage.h"
#import "ARDUtilities.h"
#import "RTCICEServer+JSON.h"
static NSString const *kARDRegisterResultKey = @"result";
static NSString const *kARDRegisterResultParamsKey = @"params";
static NSString const *kARDRegisterInitiatorKey = @"is_initiator";
static NSString const *kARDRegisterRoomIdKey = @"room_id";
static NSString const *kARDRegisterClientIdKey = @"client_id";
static NSString const *kARDRegisterMessagesKey = @"messages";
static NSString const *kARDRegisterWebSocketURLKey = @"wss_url";
static NSString const *kARDRegisterWebSocketRestURLKey = @"wss_post_url";
@interface ARDRegisterResponse ()
@property(nonatomic, assign) ARDRegisterResultType result;
@property(nonatomic, assign) BOOL isInitiator;
@property(nonatomic, strong) NSString *roomId;
@property(nonatomic, strong) NSString *clientId;
@property(nonatomic, strong) NSArray *messages;
@property(nonatomic, strong) NSURL *webSocketURL;
@property(nonatomic, strong) NSURL *webSocketRestURL;
@end
@implementation ARDRegisterResponse
@synthesize result = _result;
@synthesize isInitiator = _isInitiator;
@synthesize roomId = _roomId;
@synthesize clientId = _clientId;
@synthesize messages = _messages;
@synthesize webSocketURL = _webSocketURL;
@synthesize webSocketRestURL = _webSocketRestURL;
+ (ARDRegisterResponse *)responseFromJSONData:(NSData *)data {
NSDictionary *responseJSON = [NSDictionary dictionaryWithJSONData:data];
if (!responseJSON) {
return nil;
}
ARDRegisterResponse *response = [[ARDRegisterResponse alloc] init];
NSString *resultString = responseJSON[kARDRegisterResultKey];
response.result = [[self class] resultTypeFromString:resultString];
NSDictionary *params = responseJSON[kARDRegisterResultParamsKey];
response.isInitiator = [params[kARDRegisterInitiatorKey] boolValue];
response.roomId = params[kARDRegisterRoomIdKey];
response.clientId = params[kARDRegisterClientIdKey];
// Parse messages.
NSArray *messages = params[kARDRegisterMessagesKey];
NSMutableArray *signalingMessages =
[NSMutableArray arrayWithCapacity:messages.count];
for (NSString *message in messages) {
ARDSignalingMessage *signalingMessage =
[ARDSignalingMessage messageFromJSONString:message];
[signalingMessages addObject:signalingMessage];
}
response.messages = signalingMessages;
// Parse websocket urls.
NSString *webSocketURLString = params[kARDRegisterWebSocketURLKey];
response.webSocketURL = [NSURL URLWithString:webSocketURLString];
NSString *webSocketRestURLString = params[kARDRegisterWebSocketRestURLKey];
response.webSocketRestURL = [NSURL URLWithString:webSocketRestURLString];
return response;
}
#pragma mark - Private
+ (ARDRegisterResultType)resultTypeFromString:(NSString *)resultString {
ARDRegisterResultType result = kARDRegisterResultTypeUnknown;
if ([resultString isEqualToString:@"SUCCESS"]) {
result = kARDRegisterResultTypeSuccess;
} else if ([resultString isEqualToString:@"FULL"]) {
result = kARDRegisterResultTypeFull;
}
return result;
}
@end

View File

@ -27,40 +27,40 @@
#import <Foundation/Foundation.h>
// Used to log messages to destination like UI.
@protocol APPRTCLogger<NSObject>
- (void)logMessage:(NSString*)message;
@end
#import "RTCICECandidate.h"
#import "RTCSessionDescription.h"
@class RTCVideoTrack;
@class APPRTCConnectionManager;
typedef enum {
kARDSignalingMessageTypeCandidate,
kARDSignalingMessageTypeOffer,
kARDSignalingMessageTypeAnswer,
kARDSignalingMessageTypeBye,
} ARDSignalingMessageType;
// Used to provide AppRTC connection information.
@protocol APPRTCConnectionManagerDelegate<NSObject>
@interface ARDSignalingMessage : NSObject
- (void)connectionManager:(APPRTCConnectionManager*)manager
didReceiveLocalVideoTrack:(RTCVideoTrack*)localVideoTrack;
@property(nonatomic, readonly) ARDSignalingMessageType type;
- (void)connectionManager:(APPRTCConnectionManager*)manager
didReceiveRemoteVideoTrack:(RTCVideoTrack*)remoteVideoTrack;
- (void)connectionManagerDidReceiveHangup:(APPRTCConnectionManager*)manager;
- (void)connectionManager:(APPRTCConnectionManager*)manager
didErrorWithMessage:(NSString*)errorMessage;
+ (ARDSignalingMessage *)messageFromJSONString:(NSString *)jsonString;
- (NSData *)JSONData;
@end
// Abstracts the network connection aspect of AppRTC. The delegate will receive
// information about connection status as changes occur.
@interface APPRTCConnectionManager : NSObject
@interface ARDICECandidateMessage : ARDSignalingMessage
@property(nonatomic, weak) id<APPRTCConnectionManagerDelegate> delegate;
@property(nonatomic, weak) id<APPRTCLogger> logger;
@property(nonatomic, readonly) RTCICECandidate *candidate;
- (instancetype)initWithDelegate:(id<APPRTCConnectionManagerDelegate>)delegate
logger:(id<APPRTCLogger>)logger;
- (BOOL)connectToRoomWithURL:(NSURL*)url;
- (void)disconnect;
- (instancetype)initWithCandidate:(RTCICECandidate *)candidate;
@end
@interface ARDSessionDescriptionMessage : ARDSignalingMessage
@property(nonatomic, readonly) RTCSessionDescription *sessionDescription;
- (instancetype)initWithDescription:(RTCSessionDescription *)description;
@end
@interface ARDByeMessage : ARDSignalingMessage
@end

View File

@ -0,0 +1,143 @@
/*
* 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 "ARDSignalingMessage.h"
#import "ARDUtilities.h"
#import "RTCICECandidate+JSON.h"
#import "RTCSessionDescription+JSON.h"
static NSString const *kARDSignalingMessageTypeKey = @"type";
@implementation ARDSignalingMessage
@synthesize type = _type;
- (instancetype)initWithType:(ARDSignalingMessageType)type {
if (self = [super init]) {
_type = type;
}
return self;
}
- (NSString *)description {
return [[NSString alloc] initWithData:[self JSONData]
encoding:NSUTF8StringEncoding];
}
+ (ARDSignalingMessage *)messageFromJSONString:(NSString *)jsonString {
NSDictionary *values = [NSDictionary dictionaryWithJSONString:jsonString];
if (!values) {
NSLog(@"Error parsing signaling message JSON.");
return nil;
}
NSString *typeString = values[kARDSignalingMessageTypeKey];
ARDSignalingMessage *message = nil;
if ([typeString isEqualToString:@"candidate"]) {
RTCICECandidate *candidate =
[RTCICECandidate candidateFromJSONDictionary:values];
message = [[ARDICECandidateMessage alloc] initWithCandidate:candidate];
} else if ([typeString isEqualToString:@"offer"] ||
[typeString isEqualToString:@"answer"]) {
RTCSessionDescription *description =
[RTCSessionDescription descriptionFromJSONDictionary:values];
message =
[[ARDSessionDescriptionMessage alloc] initWithDescription:description];
} else if ([typeString isEqualToString:@"bye"]) {
message = [[ARDByeMessage alloc] init];
} else {
NSLog(@"Unexpected type: %@", typeString);
}
return message;
}
- (NSData *)JSONData {
return nil;
}
@end
@implementation ARDICECandidateMessage
@synthesize candidate = _candidate;
- (instancetype)initWithCandidate:(RTCICECandidate *)candidate {
if (self = [super initWithType:kARDSignalingMessageTypeCandidate]) {
_candidate = candidate;
}
return self;
}
- (NSData *)JSONData {
return [_candidate JSONData];
}
@end
@implementation ARDSessionDescriptionMessage
@synthesize sessionDescription = _sessionDescription;
- (instancetype)initWithDescription:(RTCSessionDescription *)description {
ARDSignalingMessageType type = kARDSignalingMessageTypeOffer;
NSString *typeString = description.type;
if ([typeString isEqualToString:@"offer"]) {
type = kARDSignalingMessageTypeOffer;
} else if ([typeString isEqualToString:@"answer"]) {
type = kARDSignalingMessageTypeAnswer;
} else {
NSAssert(NO, @"Unexpected type: %@", typeString);
}
if (self = [super initWithType:type]) {
_sessionDescription = description;
}
return self;
}
- (NSData *)JSONData {
return [_sessionDescription JSONData];
}
@end
@implementation ARDByeMessage
- (instancetype)init {
return [super initWithType:kARDSignalingMessageTypeBye];
}
- (NSData *)JSONData {
NSDictionary *message = @{
@"type": @"bye"
};
return [NSJSONSerialization dataWithJSONObject:message
options:NSJSONWritingPrettyPrinted
error:NULL];
}
@end

View File

@ -1,130 +0,0 @@
/*
* 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 "ARDSignalingParams.h"
#import "ARDUtilities.h"
#import "RTCICEServer+JSON.h"
#import "RTCMediaConstraints+JSON.h"
static NSString const *kARDSignalingParamsErrorKey = @"error";
static NSString const *kARDSignalingParamsErrorMessagesKey = @"error_messages";
static NSString const *kARDSignalingParamsInitiatorKey = @"initiator";
static NSString const *kARDSignalingParamsPeerConnectionConfigKey =
@"pc_config";
static NSString const *kARDSignalingParamsICEServersKey = @"iceServers";
static NSString const *kARDSignalingParamsMediaConstraintsKey =
@"media_constraints";
static NSString const *kARDSignalingParamsMediaConstraintsVideoKey =
@"video";
static NSString const *kARDSignalingParamsTokenKey = @"token";
static NSString const *kARDSignalingParamsTurnRequestUrlKey = @"turn_url";
@interface ARDSignalingParams ()
@property(nonatomic, strong) NSArray *errorMessages;
@property(nonatomic, strong) RTCMediaConstraints *offerConstraints;
@property(nonatomic, strong) RTCMediaConstraints *mediaConstraints;
@property(nonatomic, strong) NSMutableArray *iceServers;
@property(nonatomic, strong) NSURL *signalingServerURL;
@property(nonatomic, strong) NSURL *turnRequestURL;
@property(nonatomic, strong) NSString *channelToken;
@end
@implementation ARDSignalingParams
@synthesize errorMessages = _errorMessages;
@synthesize isInitiator = _isInitiator;
@synthesize offerConstraints = _offerConstraints;
@synthesize mediaConstraints = _mediaConstraints;
@synthesize iceServers = _iceServers;
@synthesize signalingServerURL = _signalingServerURL;
+ (ARDSignalingParams *)paramsFromJSONData:(NSData *)data {
NSDictionary *paramsJSON = [NSDictionary dictionaryWithJSONData:data];
if (!paramsJSON) {
return nil;
}
ARDSignalingParams *params = [[ARDSignalingParams alloc] init];
// Parse errors.
BOOL hasError = NO;
NSArray *errorMessages = paramsJSON[kARDSignalingParamsErrorMessagesKey];
if (errorMessages.count > 0) {
params.errorMessages = errorMessages;
return params;
}
// Parse ICE servers.
NSString *peerConnectionConfigString =
paramsJSON[kARDSignalingParamsPeerConnectionConfigKey];
NSDictionary *peerConnectionConfig =
[NSDictionary dictionaryWithJSONString:peerConnectionConfigString];
NSArray *iceServerJSONArray =
peerConnectionConfig[kARDSignalingParamsICEServersKey];
NSMutableArray *iceServers = [NSMutableArray array];
for (NSDictionary *iceServerJSON in iceServerJSONArray) {
RTCICEServer *iceServer =
[RTCICEServer serverFromJSONDictionary:iceServerJSON];
[iceServers addObject:iceServer];
}
params.iceServers = iceServers;
// Parse initiator.
BOOL isInitiator = [paramsJSON[kARDSignalingParamsInitiatorKey] boolValue];
params.isInitiator = isInitiator;
// Parse video constraints.
RTCMediaConstraints *videoConstraints = nil;
NSString *mediaConstraintsJSONString =
paramsJSON[kARDSignalingParamsMediaConstraintsKey];
NSDictionary *mediaConstraintsJSON =
[NSDictionary dictionaryWithJSONString:mediaConstraintsJSONString];
id videoJSON =
mediaConstraintsJSON[kARDSignalingParamsMediaConstraintsVideoKey];
if ([videoJSON isKindOfClass:[NSDictionary class]]) {
videoConstraints =
[RTCMediaConstraints constraintsFromJSONDictionary:videoJSON];
} else if ([videoJSON isKindOfClass:[NSNumber class]] &&
[videoJSON boolValue]) {
videoConstraints = [[RTCMediaConstraints alloc] init];
}
params.mediaConstraints = videoConstraints;
// Parse channel token.
NSString *token = paramsJSON[kARDSignalingParamsTokenKey];
params.channelToken = token;
// Parse turn request url.
params.turnRequestURL =
[NSURL URLWithString:paramsJSON[kARDSignalingParamsTurnRequestUrlKey]];
return params;
}
@end

View File

@ -38,9 +38,15 @@
@interface NSURLConnection (ARDUtilities)
// Issues an asynchronous request that calls back on main queue.
+ (void)sendAsynchronousRequest:(NSURLRequest *)request
+ (void)sendAsyncRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLResponse *response,
NSData *data,
NSError *error))completionHandler;
// Posts data to the specified URL.
+ (void)sendAsyncPostToURL:(NSURL *)url
withData:(NSData *)data
completionHandler:(void (^)(BOOL succeeded,
NSData *data))completionHandler;
@end

View File

@ -55,7 +55,7 @@
@implementation NSURLConnection (ARDUtilities)
+ (void)sendAsynchronousRequest:(NSURLRequest *)request
+ (void)sendAsyncRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLResponse *response,
NSData *data,
NSError *error))completionHandler {
@ -65,7 +65,45 @@
completionHandler:^(NSURLResponse *response,
NSData *data,
NSError *error) {
if (completionHandler) {
completionHandler(response, data, error);
}
}];
}
// Posts data to the specified URL.
+ (void)sendAsyncPostToURL:(NSURL *)url
withData:(NSData *)data
completionHandler:(void (^)(BOOL succeeded,
NSData *data))completionHandler {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
request.HTTPBody = data;
[[self class] sendAsyncRequest:request
completionHandler:^(NSURLResponse *response,
NSData *data,
NSError *error) {
if (error) {
NSLog(@"Error posting data: %@", error.localizedDescription);
if (completionHandler) {
completionHandler(NO, data);
}
return;
}
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode != 200) {
NSString *serverResponse = data.length > 0 ?
[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] :
nil;
NSLog(@"Received bad response: %@", serverResponse);
if (completionHandler) {
completionHandler(NO, data);
}
return;
}
if (completionHandler) {
completionHandler(YES, data);
}
}];
}

View File

@ -0,0 +1,75 @@
/*
* 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 <Foundation/Foundation.h>
#import "ARDSignalingMessage.h"
typedef NS_ENUM(NSInteger, ARDWebSocketChannelState) {
// State when disconnected.
kARDWebSocketChannelStateClosed,
// State when connection is established but not ready for use.
kARDWebSocketChannelStateOpen,
// State when connection is established and registered.
kARDWebSocketChannelStateRegistered,
// State when connection encounters a fatal error.
kARDWebSocketChannelStateError
};
@class ARDWebSocketChannel;
@protocol ARDWebSocketChannelDelegate <NSObject>
- (void)channel:(ARDWebSocketChannel *)channel
didChangeState:(ARDWebSocketChannelState)state;
- (void)channel:(ARDWebSocketChannel *)channel
didReceiveMessage:(ARDSignalingMessage *)message;
@end
// Wraps a WebSocket connection to the AppRTC WebSocket server.
@interface ARDWebSocketChannel : NSObject
@property(nonatomic, readonly) NSString *roomId;
@property(nonatomic, readonly) NSString *clientId;
@property(nonatomic, readonly) ARDWebSocketChannelState state;
@property(nonatomic, weak) id<ARDWebSocketChannelDelegate> delegate;
- (instancetype)initWithURL:(NSURL *)url
restURL:(NSURL *)restURL
delegate:(id<ARDWebSocketChannelDelegate>)delegate;
// Registers with the WebSocket server for the given room and client id once
// the web socket connection is open.
- (void)registerForRoomId:(NSString *)roomId
clientId:(NSString *)clientId;
// Sends data over the WebSocket connection if registered, otherwise POSTs to
// the web socket server instead.
- (void)sendData:(NSData *)data;
@end

View File

@ -0,0 +1,213 @@
/*
* 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 "ARDWebSocketChannel.h"
#import "ARDUtilities.h"
#import "SRWebSocket.h"
// TODO(tkchin): move these to a configuration object.
static NSString const *kARDWSSMessageErrorKey = @"error";
static NSString const *kARDWSSMessagePayloadKey = @"msg";
@interface ARDWebSocketChannel () <SRWebSocketDelegate>
@end
@implementation ARDWebSocketChannel {
NSURL *_url;
NSURL *_restURL;
SRWebSocket *_socket;
}
@synthesize delegate = _delegate;
@synthesize state = _state;
@synthesize roomId = _roomId;
@synthesize clientId = _clientId;
- (instancetype)initWithURL:(NSURL *)url
restURL:(NSURL *)restURL
delegate:(id<ARDWebSocketChannelDelegate>)delegate {
if (self = [super init]) {
_url = url;
_restURL = restURL;
_delegate = delegate;
_socket = [[SRWebSocket alloc] initWithURL:url];
_socket.delegate = self;
NSLog(@"Opening WebSocket.");
[_socket open];
}
return self;
}
- (void)dealloc {
[self disconnect];
}
- (void)setState:(ARDWebSocketChannelState)state {
if (_state == state) {
return;
}
_state = state;
[_delegate channel:self didChangeState:_state];
}
- (void)registerForRoomId:(NSString *)roomId
clientId:(NSString *)clientId {
NSParameterAssert(roomId.length);
NSParameterAssert(clientId.length);
_roomId = roomId;
_clientId = clientId;
if (_state == kARDWebSocketChannelStateOpen) {
[self registerWithCollider];
}
}
- (void)sendData:(NSData *)data {
NSParameterAssert(_clientId.length);
NSParameterAssert(_roomId.length);
if (_state == kARDWebSocketChannelStateRegistered) {
NSString *payload =
[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSDictionary *message = @{
@"cmd": @"send",
@"msg": payload,
};
NSData *messageJSONObject =
[NSJSONSerialization dataWithJSONObject:message
options:NSJSONWritingPrettyPrinted
error:nil];
NSString *messageString =
[[NSString alloc] initWithData:messageJSONObject
encoding:NSUTF8StringEncoding];
NSLog(@"C->WSS: %@", messageString);
[_socket send:messageString];
} else {
NSString *dataString =
[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"C->WSS POST: %@", dataString);
NSString *urlString =
[NSString stringWithFormat:@"%@/%@/%@",
[_restURL absoluteString], _roomId, _clientId];
NSURL *url = [NSURL URLWithString:urlString];
[NSURLConnection sendAsyncPostToURL:url
withData:data
completionHandler:nil];
}
}
- (void)disconnect {
if (_state == kARDWebSocketChannelStateClosed ||
_state == kARDWebSocketChannelStateError) {
return;
}
[_socket close];
NSLog(@"C->WSS DELETE rid:%@ cid:%@", _roomId, _clientId);
NSString *urlString =
[NSString stringWithFormat:@"%@/%@/%@",
[_restURL absoluteString], _roomId, _clientId];
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"DELETE";
request.HTTPBody = nil;
[NSURLConnection sendAsyncRequest:request completionHandler:nil];
}
#pragma mark - SRWebSocketDelegate
- (void)webSocketDidOpen:(SRWebSocket *)webSocket {
NSLog(@"WebSocket connection opened.");
self.state = kARDWebSocketChannelStateOpen;
if (_roomId.length && _clientId.length) {
[self registerWithCollider];
}
}
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
NSString *messageString = message;
NSData *messageData = [messageString dataUsingEncoding:NSUTF8StringEncoding];
id jsonObject = [NSJSONSerialization JSONObjectWithData:messageData
options:0
error:nil];
if (![jsonObject isKindOfClass:[NSDictionary class]]) {
NSLog(@"Unexpected message: %@", jsonObject);
return;
}
NSDictionary *wssMessage = jsonObject;
NSString *errorString = wssMessage[kARDWSSMessageErrorKey];
if (errorString.length) {
NSLog(@"WSS error: %@", errorString);
return;
}
NSString *payload = wssMessage[kARDWSSMessagePayloadKey];
ARDSignalingMessage *signalingMessage =
[ARDSignalingMessage messageFromJSONString:payload];
NSLog(@"WSS->C: %@", payload);
[_delegate channel:self didReceiveMessage:signalingMessage];
}
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
NSLog(@"WebSocket error: %@", error);
self.state = kARDWebSocketChannelStateError;
}
- (void)webSocket:(SRWebSocket *)webSocket
didCloseWithCode:(NSInteger)code
reason:(NSString *)reason
wasClean:(BOOL)wasClean {
NSLog(@"WebSocket closed with code: %ld reason:%@ wasClean:%d",
(long)code, reason, wasClean);
NSParameterAssert(_state != kARDWebSocketChannelStateError);
self.state = kARDWebSocketChannelStateClosed;
}
#pragma mark - Private
- (void)registerWithCollider {
if (_state == kARDWebSocketChannelStateRegistered) {
return;
}
NSParameterAssert(_roomId.length);
NSParameterAssert(_clientId.length);
NSDictionary *registerMessage = @{
@"cmd": @"register",
@"roomid" : _roomId,
@"clientid" : _clientId,
};
NSData *message =
[NSJSONSerialization dataWithJSONObject:registerMessage
options:NSJSONWritingPrettyPrinted
error:nil];
NSString *messageString =
[[NSString alloc] initWithData:message encoding:NSUTF8StringEncoding];
NSLog(@"Registering on WSS for rid:%@ cid:%@", _roomId, _clientId);
// Registration can fail if server rejects it. For example, if the room is
// full.
[_socket send:messageString];
self.state = kARDWebSocketChannelStateRegistered;
}
@end

View File

@ -1,167 +0,0 @@
/*
* libjingle
* Copyright 2013, 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 "GAEChannelClient.h"
#import "RTCPeerConnectionFactory.h"
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
@interface GAEChannelClient () <UIWebViewDelegate>
@property(nonatomic, strong) UIWebView* webView;
#else
#import <WebKit/WebKit.h>
@interface GAEChannelClient ()
@property(nonatomic, strong) WebView* webView;
#endif
@end
@implementation GAEChannelClient
- (instancetype)initWithToken:(NSString*)token
delegate:(id<GAEMessageHandler>)delegate {
NSParameterAssert([token length] > 0);
NSParameterAssert(delegate);
self = [super init];
if (self) {
#if TARGET_OS_IPHONE
_webView = [[UIWebView alloc] init];
_webView.delegate = self;
#else
_webView = [[WebView alloc] init];
_webView.policyDelegate = self;
#endif
_delegate = delegate;
NSString* htmlPath =
[[NSBundle mainBundle] pathForResource:@"channel" ofType:@"html"];
NSURL* htmlUrl = [NSURL fileURLWithPath:htmlPath];
NSString* path = [NSString
stringWithFormat:@"%@?token=%@", [htmlUrl absoluteString], token];
#if TARGET_OS_IPHONE
[_webView
#else
[[_webView mainFrame]
#endif
loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:path]]];
}
return self;
}
- (void)dealloc {
#if TARGET_OS_IPHONE
_webView.delegate = nil;
[_webView stopLoading];
#else
_webView.policyDelegate = nil;
[[_webView mainFrame] stopLoading];
#endif
}
#if TARGET_OS_IPHONE
#pragma mark - UIWebViewDelegate
- (BOOL)webView:(UIWebView*)webView
shouldStartLoadWithRequest:(NSURLRequest*)request
navigationType:(UIWebViewNavigationType)navigationType {
#else
// WebPolicyDelegate is an informal delegate.
#pragma mark - WebPolicyDelegate
- (void)webView:(WebView*)webView
decidePolicyForNavigationAction:(NSDictionary*)actionInformation
request:(NSURLRequest*)request
frame:(WebFrame*)frame
decisionListener:(id<WebPolicyDecisionListener>)listener {
#endif
NSString* scheme = [request.URL scheme];
NSAssert(scheme, @"scheme is nil: %@", request);
if (![scheme isEqualToString:@"js-frame"]) {
#if TARGET_OS_IPHONE
return YES;
#else
[listener use];
return;
#endif
}
dispatch_async(dispatch_get_main_queue(), ^{
NSString* queuedMessage = [webView
stringByEvaluatingJavaScriptFromString:@"popQueuedMessage();"];
NSAssert([queuedMessage length], @"Empty queued message from JS");
NSDictionary* queuedMessageDict =
[GAEChannelClient jsonStringToDictionary:queuedMessage];
NSString* method = queuedMessageDict[@"type"];
NSAssert(method, @"Missing method: %@", queuedMessageDict);
NSDictionary* payload = queuedMessageDict[@"payload"]; // May be nil.
if ([method isEqualToString:@"onopen"]) {
[self.delegate onOpen];
} else if ([method isEqualToString:@"onmessage"]) {
NSDictionary* payloadData =
[GAEChannelClient jsonStringToDictionary:payload[@"data"]];
[self.delegate onMessage:payloadData];
} else if ([method isEqualToString:@"onclose"]) {
[self.delegate onClose];
} else if ([method isEqualToString:@"onerror"]) {
NSNumber* codeNumber = payload[@"code"];
int code = [codeNumber intValue];
NSAssert([codeNumber isEqualToNumber:[NSNumber numberWithInt:code]],
@"Unexpected non-integral code: %@", payload);
[self.delegate onError:code withDescription:payload[@"description"]];
} else {
NSAssert(NO, @"Invalid message sent from UIWebView: %@", queuedMessage);
}
});
#if TARGET_OS_IPHONE
return NO;
#else
[listener ignore];
return;
#endif
}
#pragma mark - Private
+ (NSDictionary*)jsonStringToDictionary:(NSString*)str {
NSData* data = [str dataUsingEncoding:NSUTF8StringEncoding];
NSError* error;
NSDictionary* dict =
[NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
NSAssert(!error, @"Invalid JSON? %@", str);
return dict;
}
@end

View File

@ -50,7 +50,16 @@ static NSString const *kRTCICECandidateSdpKey = @"candidate";
kRTCICECandidateMidKey : self.sdpMid,
kRTCICECandidateSdpKey : self.sdp
};
return [NSJSONSerialization dataWithJSONObject:json options:0 error:nil];
NSError *error = nil;
NSData *data =
[NSJSONSerialization dataWithJSONObject:json
options:NSJSONWritingPrettyPrinted
error:&error];
if (error) {
NSLog(@"Error serializing JSON: %@", error);
return nil;
}
return data;
}
@end

View File

@ -31,6 +31,6 @@
+ (RTCICEServer *)serverFromJSONDictionary:(NSDictionary *)dictionary;
// CEOD provides different JSON, and this parses that.
+ (RTCICEServer *)serverFromCEODJSONDictionary:(NSDictionary *)dictionary;
+ (NSArray *)serversFromCEODJSONDictionary:(NSDictionary *)dictionary;
@end

View File

@ -46,14 +46,19 @@ static NSString const *kRTCICEServerCredentialKey = @"credential";
password:credential];
}
+ (RTCICEServer *)serverFromCEODJSONDictionary:(NSDictionary *)dictionary {
+ (NSArray *)serversFromCEODJSONDictionary:(NSDictionary *)dictionary {
NSString *username = dictionary[kRTCICEServerUsernameKey];
NSString *password = dictionary[kRTCICEServerPasswordKey];
NSArray *uris = dictionary[kRTCICEServerUrisKey];
NSParameterAssert(uris.count > 0);
return [[RTCICEServer alloc] initWithURI:[NSURL URLWithString:uris[0]]
NSMutableArray *servers = [NSMutableArray arrayWithCapacity:uris.count];
for (NSString *uri in uris) {
RTCICEServer *server =
[[RTCICEServer alloc] initWithURI:[NSURL URLWithString:uri]
username:username
password:password];
[servers addObject:server];
}
return servers;
}
@end

View File

@ -1,94 +0,0 @@
<html>
<head>
<script src="http://apprtc.appspot.com/_ah/channel/jsapi"></script>
</head>
<!--
Helper HTML that redirects Google AppEngine's Channel API to Objective C.
This is done by hosting this page in an iOS application. The hosting
class creates a UIWebView control and implements the UIWebViewDelegate
protocol. Then when there is a channel message it is queued in JS,
and an IFRAME is added to the DOM, triggering a navigation event
|shouldStartLoadWithRequest| in Objective C which can then fetch the
message using |popQueuedMessage|. This queuing is necessary to avoid URL
length limits in UIWebView (which are undocumented).
-->
<body onbeforeunload="closeSocket()" onload="openSocket()">
<script type="text/javascript">
// QueryString is copy/pasta from
// chromium's chrome/test/data/media/html/utils.js.
var QueryString = function () {
// Allows access to query parameters on the URL; e.g., given a URL like:
// http://<url>/my.html?test=123&bob=123
// parameters can now be accessed via QueryString.test or
// QueryString.bob.
var params = {};
// RegEx to split out values by &.
var r = /([^&=]+)=?([^&]*)/g;
// Lambda function for decoding extracted match values. Replaces '+'
// with space so decodeURIComponent functions properly.
function d(s) { return decodeURIComponent(s.replace(/\+/g, ' ')); }
var match;
while (match = r.exec(window.location.search.substring(1)))
params[d(match[1])] = d(match[2]);
return params;
} ();
var channel = null;
var socket = null;
// In-order queue of messages to be delivered to ObjectiveC.
// Each is a JSON.stringify()'d dictionary containing a 'type'
// field and optionally a 'payload'.
var messageQueue = [];
function openSocket() {
if (!QueryString.token || !QueryString.token.match(/^[A-z0-9_-]+$/)) {
// Send error back to ObjC. This will assert in GAEChannelClient.m.
sendMessageToObjC("JSError:Missing/malformed token parameter " +
QueryString.token);
throw "Missing/malformed token parameter: " + QueryString.token;
}
channel = new goog.appengine.Channel(QueryString.token);
socket = channel.open({
'onopen': function() {
sendMessageToObjC("onopen");
},
'onmessage': function(msg) {
sendMessageToObjC("onmessage", msg);
},
'onclose': function() {
sendMessageToObjC("onclose");
},
'onerror': function(err) {
sendMessageToObjC("onerror", err);
}
});
}
function closeSocket() {
socket.close();
}
// Add an IFRAME to the DOM to trigger a navigation event. Then remove
// it as it is no longer needed. Only one event is generated.
function sendMessageToObjC(type, payload) {
messageQueue.push(JSON.stringify({'type': type, 'payload': payload}));
var iframe = document.createElement("IFRAME");
iframe.setAttribute("src", "js-frame:");
// For some reason we need to set a non-empty size for the iOS6
// simulator...
iframe.setAttribute("height", "1px");
iframe.setAttribute("width", "1px");
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
}
function popQueuedMessage() {
return messageQueue.shift();
}
</script>
</body>
</html>

View File

@ -32,38 +32,28 @@
#import "APPRTCViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "APPRTCConnectionManager.h"
#import "ARDAppClient.h"
#import "RTCEAGLVideoView.h"
#import "RTCVideoTrack.h"
// Padding space for local video view with its parent.
static CGFloat const kLocalViewPadding = 20;
@interface APPRTCViewController ()
<APPRTCConnectionManagerDelegate, APPRTCLogger, RTCEAGLVideoViewDelegate>
@interface APPRTCViewController () <ARDAppClientDelegate,
RTCEAGLVideoViewDelegate>
@property(nonatomic, assign) UIInterfaceOrientation statusBarOrientation;
@property(nonatomic, strong) RTCEAGLVideoView* localVideoView;
@property(nonatomic, strong) RTCEAGLVideoView* remoteVideoView;
@end
@implementation APPRTCViewController {
APPRTCConnectionManager* _connectionManager;
ARDAppClient *_client;
RTCVideoTrack* _localVideoTrack;
RTCVideoTrack* _remoteVideoTrack;
CGSize _localVideoSize;
CGSize _remoteVideoSize;
}
- (instancetype)initWithNibName:(NSString*)nibName
bundle:(NSBundle*)bundle {
if (self = [super initWithNibName:nibName bundle:bundle]) {
_connectionManager =
[[APPRTCConnectionManager alloc] initWithDelegate:self
logger:self];
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
@ -96,48 +86,46 @@ static CGFloat const kLocalViewPadding = 20;
}
- (void)applicationWillResignActive:(UIApplication*)application {
[self logMessage:@"Application lost focus, connection broken."];
[self disconnect];
}
#pragma mark - APPRTCConnectionManagerDelegate
#pragma mark - ARDAppClientDelegate
- (void)connectionManager:(APPRTCConnectionManager*)manager
didReceiveLocalVideoTrack:(RTCVideoTrack*)localVideoTrack {
- (void)appClient:(ARDAppClient *)client
didChangeState:(ARDAppClientState)state {
switch (state) {
case kARDAppClientStateConnected:
NSLog(@"Client connected.");
break;
case kARDAppClientStateConnecting:
NSLog(@"Client connecting.");
break;
case kARDAppClientStateDisconnected:
NSLog(@"Client disconnected.");
[self resetUI];
break;
}
}
- (void)appClient:(ARDAppClient *)client
didReceiveLocalVideoTrack:(RTCVideoTrack *)localVideoTrack {
_localVideoTrack = localVideoTrack;
[_localVideoTrack addRenderer:self.localVideoView];
self.localVideoView.hidden = NO;
}
- (void)connectionManager:(APPRTCConnectionManager*)manager
didReceiveRemoteVideoTrack:(RTCVideoTrack*)remoteVideoTrack {
- (void)appClient:(ARDAppClient *)client
didReceiveRemoteVideoTrack:(RTCVideoTrack *)remoteVideoTrack {
_remoteVideoTrack = remoteVideoTrack;
[_remoteVideoTrack addRenderer:self.remoteVideoView];
}
- (void)connectionManagerDidReceiveHangup:(APPRTCConnectionManager*)manager {
[self showAlertWithMessage:@"Remote hung up."];
- (void)appClient:(ARDAppClient *)client
didError:(NSError *)error {
[self showAlertWithMessage:[NSString stringWithFormat:@"%@", error]];
[self disconnect];
}
- (void)connectionManager:(APPRTCConnectionManager*)manager
didErrorWithMessage:(NSString*)message {
[self showAlertWithMessage:message];
[self disconnect];
}
#pragma mark - APPRTCLogger
- (void)logMessage:(NSString*)message {
dispatch_async(dispatch_get_main_queue(), ^{
NSString* output =
[NSString stringWithFormat:@"%@\n%@", self.logView.text, message];
self.logView.text = output;
[self.logView
scrollRangeToVisible:NSMakeRange([self.logView.text length], 0)];
});
}
#pragma mark - RTCEAGLVideoViewDelegate
- (void)videoView:(RTCEAGLVideoView*)videoView
@ -162,9 +150,10 @@ static CGFloat const kLocalViewPadding = 20;
textField.hidden = YES;
self.instructionsView.hidden = YES;
self.logView.hidden = NO;
NSString* url =
[NSString stringWithFormat:@"https://apprtc.appspot.com/?r=%@", room];
[_connectionManager connectToRoomWithURL:[NSURL URLWithString:url]];
[_client disconnect];
// TODO(tkchin): support reusing the same client object.
_client = [[ARDAppClient alloc] initWithDelegate:self];
[_client connectToRoomWithId:room options:nil];
[self setupCaptureSession];
}
@ -179,7 +168,7 @@ static CGFloat const kLocalViewPadding = 20;
- (void)disconnect {
[self resetUI];
[_connectionManager disconnect];
[_client disconnect];
}
- (void)showAlertWithMessage:(NSString*)message {

View File

@ -28,7 +28,7 @@
#import "APPRTCViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "APPRTCConnectionManager.h"
#import "ARDAppClient.h"
#import "RTCNSGLVideoView.h"
#import "RTCVideoTrack.h"
@ -222,26 +222,16 @@ static NSUInteger const kLogViewHeight = 280;
@end
@interface APPRTCViewController ()
<APPRTCConnectionManagerDelegate, APPRTCMainViewDelegate, APPRTCLogger>
<ARDAppClientDelegate, APPRTCMainViewDelegate>
@property(nonatomic, readonly) APPRTCMainView* mainView;
@end
@implementation APPRTCViewController {
APPRTCConnectionManager* _connectionManager;
ARDAppClient* _client;
RTCVideoTrack* _localVideoTrack;
RTCVideoTrack* _remoteVideoTrack;
}
- (instancetype)initWithNibName:(NSString*)nibName
bundle:(NSBundle*)bundle {
if (self = [super initWithNibName:nibName bundle:bundle]) {
_connectionManager =
[[APPRTCConnectionManager alloc] initWithDelegate:self
logger:self];
}
return self;
}
- (void)dealloc {
[self disconnect];
}
@ -257,43 +247,50 @@ static NSUInteger const kLogViewHeight = 280;
[self disconnect];
}
#pragma mark - APPRTCConnectionManagerDelegate
#pragma mark - ARDAppClientDelegate
- (void)connectionManager:(APPRTCConnectionManager*)manager
didReceiveLocalVideoTrack:(RTCVideoTrack*)localVideoTrack {
- (void)appClient:(ARDAppClient *)client
didChangeState:(ARDAppClientState)state {
switch (state) {
case kARDAppClientStateConnected:
NSLog(@"Client connected.");
break;
case kARDAppClientStateConnecting:
NSLog(@"Client connecting.");
break;
case kARDAppClientStateDisconnected:
NSLog(@"Client disconnected.");
[self resetUI];
_client = nil;
break;
}
}
- (void)appClient:(ARDAppClient *)client
didReceiveLocalVideoTrack:(RTCVideoTrack *)localVideoTrack {
_localVideoTrack = localVideoTrack;
}
- (void)connectionManager:(APPRTCConnectionManager*)manager
didReceiveRemoteVideoTrack:(RTCVideoTrack*)remoteVideoTrack {
- (void)appClient:(ARDAppClient *)client
didReceiveRemoteVideoTrack:(RTCVideoTrack *)remoteVideoTrack {
_remoteVideoTrack = remoteVideoTrack;
[_remoteVideoTrack addRenderer:self.mainView.remoteVideoView];
}
- (void)connectionManagerDidReceiveHangup:(APPRTCConnectionManager*)manager {
[self showAlertWithMessage:@"Remote closed connection"];
- (void)appClient:(ARDAppClient *)client
didError:(NSError *)error {
[self showAlertWithMessage:[NSString stringWithFormat:@"%@", error]];
[self disconnect];
}
- (void)connectionManager:(APPRTCConnectionManager*)manager
didErrorWithMessage:(NSString*)message {
[self showAlertWithMessage:message];
[self disconnect];
}
#pragma mark - APPRTCLogger
- (void)logMessage:(NSString*)message {
[self.mainView displayLogMessage:message];
}
#pragma mark - APPRTCMainViewDelegate
- (void)appRTCMainView:(APPRTCMainView*)mainView
didEnterRoomId:(NSString*)roomId {
NSString* urlString =
[NSString stringWithFormat:@"https://apprtc.appspot.com/?r=%@", roomId];
[_connectionManager connectToRoomWithURL:[NSURL URLWithString:urlString]];
[_client disconnect];
ARDAppClient *client = [[ARDAppClient alloc] initWithDelegate:self];
[client connectToRoomWithId:roomId options:nil];
_client = client;
}
#pragma mark - Private
@ -308,11 +305,15 @@ static NSUInteger const kLogViewHeight = 280;
[alert runModal];
}
- (void)disconnect {
- (void)resetUI {
[_remoteVideoTrack removeRenderer:self.mainView.remoteVideoView];
_remoteVideoTrack = nil;
[self.mainView.remoteVideoView renderFrame:nil];
[_connectionManager disconnect];
}
- (void)disconnect {
[self resetUI];
[_client disconnect];
}
@end

View File

@ -0,0 +1,15 @@
Copyright 2012 Square Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,132 @@
//
// Copyright 2012 Square Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import <Foundation/Foundation.h>
#import <Security/SecCertificate.h>
typedef enum {
SR_CONNECTING = 0,
SR_OPEN = 1,
SR_CLOSING = 2,
SR_CLOSED = 3,
} SRReadyState;
typedef enum SRStatusCode : NSInteger {
SRStatusCodeNormal = 1000,
SRStatusCodeGoingAway = 1001,
SRStatusCodeProtocolError = 1002,
SRStatusCodeUnhandledType = 1003,
// 1004 reserved.
SRStatusNoStatusReceived = 1005,
// 1004-1006 reserved.
SRStatusCodeInvalidUTF8 = 1007,
SRStatusCodePolicyViolated = 1008,
SRStatusCodeMessageTooBig = 1009,
} SRStatusCode;
@class SRWebSocket;
extern NSString *const SRWebSocketErrorDomain;
extern NSString *const SRHTTPResponseErrorKey;
#pragma mark - SRWebSocketDelegate
@protocol SRWebSocketDelegate;
#pragma mark - SRWebSocket
@interface SRWebSocket : NSObject <NSStreamDelegate>
@property (nonatomic, weak) id <SRWebSocketDelegate> delegate;
@property (nonatomic, readonly) SRReadyState readyState;
@property (nonatomic, readonly, retain) NSURL *url;
// This returns the negotiated protocol.
// It will be nil until after the handshake completes.
@property (nonatomic, readonly, copy) NSString *protocol;
// Protocols should be an array of strings that turn into Sec-WebSocket-Protocol.
- (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols;
- (id)initWithURLRequest:(NSURLRequest *)request;
// Some helper constructors.
- (id)initWithURL:(NSURL *)url protocols:(NSArray *)protocols;
- (id)initWithURL:(NSURL *)url;
// Delegate queue will be dispatch_main_queue by default.
// You cannot set both OperationQueue and dispatch_queue.
- (void)setDelegateOperationQueue:(NSOperationQueue*) queue;
- (void)setDelegateDispatchQueue:(dispatch_queue_t) queue;
// By default, it will schedule itself on +[NSRunLoop SR_networkRunLoop] using defaultModes.
- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode;
- (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode;
// SRWebSockets are intended for one-time-use only. Open should be called once and only once.
- (void)open;
- (void)close;
- (void)closeWithCode:(NSInteger)code reason:(NSString *)reason;
// Send a UTF8 String or Data.
- (void)send:(id)data;
// Send Data (can be nil) in a ping message.
- (void)sendPing:(NSData *)data;
@end
#pragma mark - SRWebSocketDelegate
@protocol SRWebSocketDelegate <NSObject>
// message will either be an NSString if the server is using text
// or NSData if the server is using binary.
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message;
@optional
- (void)webSocketDidOpen:(SRWebSocket *)webSocket;
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error;
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean;
- (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload;
@end
#pragma mark - NSURLRequest (CertificateAdditions)
@interface NSURLRequest (CertificateAdditions)
@property (nonatomic, retain, readonly) NSArray *SR_SSLPinnedCertificates;
@end
#pragma mark - NSMutableURLRequest (CertificateAdditions)
@interface NSMutableURLRequest (CertificateAdditions)
@property (nonatomic, retain) NSArray *SR_SSLPinnedCertificates;
@end
#pragma mark - NSRunLoop (SRWebSocket)
@interface NSRunLoop (SRWebSocket)
+ (NSRunLoop *)SR_networkRunLoop;
@end

File diff suppressed because it is too large Load Diff

View File

@ -226,11 +226,9 @@
'type': 'executable',
'product_name': 'AppRTCDemo',
'mac_bundle': 1,
'mac_bundle_resources': [
'examples/objc/AppRTCDemo/channel.html',
],
'dependencies': [
'libjingle.gyp:libjingle_peerconnection_objc',
'socketrocket',
],
'conditions': [
['OS=="ios"', {
@ -265,7 +263,6 @@
'MACOSX_DEPLOYMENT_TARGET' : '10.8',
'OTHER_LDFLAGS': [
'-framework AVFoundation',
'-framework WebKit',
],
},
}],
@ -279,16 +276,18 @@
'examples/objc/APPRTCDemo',
],
'sources': [
'examples/objc/AppRTCDemo/APPRTCAppClient.h',
'examples/objc/AppRTCDemo/APPRTCAppClient.m',
'examples/objc/AppRTCDemo/APPRTCConnectionManager.h',
'examples/objc/AppRTCDemo/APPRTCConnectionManager.m',
'examples/objc/AppRTCDemo/ARDSignalingParams.h',
'examples/objc/AppRTCDemo/ARDSignalingParams.m',
'examples/objc/AppRTCDemo/ARDAppClient.h',
'examples/objc/AppRTCDemo/ARDAppClient.m',
'examples/objc/AppRTCDemo/ARDMessageResponse.h',
'examples/objc/AppRTCDemo/ARDMessageResponse.m',
'examples/objc/AppRTCDemo/ARDRegisterResponse.h',
'examples/objc/AppRTCDemo/ARDRegisterResponse.m',
'examples/objc/AppRTCDemo/ARDSignalingMessage.h',
'examples/objc/AppRTCDemo/ARDSignalingMessage.m',
'examples/objc/AppRTCDemo/ARDUtilities.h',
'examples/objc/AppRTCDemo/ARDUtilities.m',
'examples/objc/AppRTCDemo/GAEChannelClient.h',
'examples/objc/AppRTCDemo/GAEChannelClient.m',
'examples/objc/AppRTCDemo/ARDWebSocketChannel.h',
'examples/objc/AppRTCDemo/ARDWebSocketChannel.m',
'examples/objc/AppRTCDemo/RTCICECandidate+JSON.h',
'examples/objc/AppRTCDemo/RTCICECandidate+JSON.m',
'examples/objc/AppRTCDemo/RTCICEServer+JSON.h',
@ -302,6 +301,47 @@
'CLANG_ENABLE_OBJC_ARC': 'YES',
},
}, # target AppRTCDemo
{
# TODO(tkchin): move this into the real third party location and
# have it mirrored on chrome infra.
'target_name': 'socketrocket',
'type': 'static_library',
'sources': [
'examples/objc/AppRTCDemo/third_party/SocketRocket/SRWebSocket.h',
'examples/objc/AppRTCDemo/third_party/SocketRocket/SRWebSocket.m',
],
'conditions': [
['OS=="mac"', {
'xcode_settings': {
# SocketRocket autosynthesizes some properties. Disable the
# warning so we can compile successfully.
'CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS': 'NO',
'MACOSX_DEPLOYMENT_TARGET' : '10.8',
},
}],
],
'direct_dependent_settings': {
'include_dirs': [
'examples/objc/AppRTCDemo/third_party/SocketRocket',
],
},
'xcode_settings': {
'CLANG_ENABLE_OBJC_ARC': 'YES',
'WARNING_CFLAGS': [
'-Wno-deprecated-declarations',
],
},
'link_settings': {
'xcode_settings': {
'OTHER_LDFLAGS': [
'-framework CFNetwork',
],
},
'libraries': [
'$(SDKROOT)/usr/lib/libicucore.dylib',
],
}
}, # target socketrocket
], # targets
}], # OS=="ios" or (OS=="mac" and target_arch!="ia32" and mac_sdk>="10.8")