blob: 710f4ad5ecadd102c6de70356219eafc57471273 [file] [log] [blame]
henrike@webrtc.org28e20752013-07-10 00:45:36 +00001/*
2 * libjingle
3 * Copyright 2013, Google Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#import "APPRTCAppDelegate.h"
29
30#import "APPRTCViewController.h"
fischman@webrtc.org1bc19542013-08-01 18:29:45 +000031#import "RTCICECandidate.h"
32#import "RTCICEServer.h"
henrike@webrtc.org28e20752013-07-10 00:45:36 +000033#import "RTCMediaConstraints.h"
34#import "RTCMediaStream.h"
35#import "RTCPair.h"
36#import "RTCPeerConnection.h"
37#import "RTCPeerConnectionDelegate.h"
38#import "RTCPeerConnectionFactory.h"
39#import "RTCSessionDescription.h"
40
41@interface PCObserver : NSObject<RTCPeerConnectionDelegate>
42
43- (id)initWithDelegate:(id<APPRTCSendMessage>)delegate;
44
45@end
46
47@implementation PCObserver {
48 id<APPRTCSendMessage> _delegate;
49}
50
51- (id)initWithDelegate:(id<APPRTCSendMessage>)delegate {
52 if (self = [super init]) {
53 _delegate = delegate;
54 }
55 return self;
56}
57
58- (void)peerConnectionOnError:(RTCPeerConnection *)peerConnection {
59 NSLog(@"PCO onError.");
60 NSAssert(NO, @"PeerConnection failed.");
61}
62
63- (void)peerConnection:(RTCPeerConnection *)peerConnection
fischman@webrtc.org1bc19542013-08-01 18:29:45 +000064 signalingStateChanged:(RTCSignalingState)stateChanged {
henrike@webrtc.org28e20752013-07-10 00:45:36 +000065 NSLog(@"PCO onSignalingStateChange.");
66}
67
68- (void)peerConnection:(RTCPeerConnection *)peerConnection
fischman@webrtc.org1bc19542013-08-01 18:29:45 +000069 addedStream:(RTCMediaStream *)stream {
henrike@webrtc.org28e20752013-07-10 00:45:36 +000070 NSLog(@"PCO onAddStream.");
71 dispatch_async(dispatch_get_main_queue(), ^(void) {
72 NSAssert([stream.audioTracks count] >= 1,
73 @"Expected at least 1 audio stream");
74 //NSAssert([stream.videoTracks count] >= 1,
75 // @"Expected at least 1 video stream");
76 // TODO(hughv): Add video support
77 });
78}
79
80- (void)peerConnection:(RTCPeerConnection *)peerConnection
fischman@webrtc.org1bc19542013-08-01 18:29:45 +000081 removedStream:(RTCMediaStream *)stream {
henrike@webrtc.org28e20752013-07-10 00:45:36 +000082 NSLog(@"PCO onRemoveStream.");
83 // TODO(hughv): Remove video track.
84}
85
86- (void)
87 peerConnectionOnRenegotiationNeeded:(RTCPeerConnection *)peerConnection {
88 NSLog(@"PCO onRenegotiationNeeded.");
89 // TODO(hughv): Handle this.
90}
91
92- (void)peerConnection:(RTCPeerConnection *)peerConnection
fischman@webrtc.org1bc19542013-08-01 18:29:45 +000093 gotICECandidate:(RTCICECandidate *)candidate {
94 NSLog(@"PCO onICECandidate.\n Mid[%@] Index[%d] Sdp[%@]",
henrike@webrtc.org28e20752013-07-10 00:45:36 +000095 candidate.sdpMid,
96 candidate.sdpMLineIndex,
97 candidate.sdp);
98 NSDictionary *json =
99 @{ @"type" : @"candidate",
100 @"label" : [NSNumber numberWithInt:candidate.sdpMLineIndex],
101 @"id" : candidate.sdpMid,
102 @"candidate" : candidate.sdp };
103 NSError *error;
104 NSData *data =
105 [NSJSONSerialization dataWithJSONObject:json options:0 error:&error];
106 if (!error) {
107 [_delegate sendData:data];
108 } else {
109 NSAssert(NO, @"Unable to serialize JSON object with error: %@",
110 error.localizedDescription);
111 }
112}
113
114- (void)peerConnection:(RTCPeerConnection *)peerConnection
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000115 iceGatheringChanged:(RTCICEGatheringState)newState {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000116 NSLog(@"PCO onIceGatheringChange. %d", newState);
117}
118
119- (void)peerConnection:(RTCPeerConnection *)peerConnection
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000120 iceConnectionChanged:(RTCICEConnectionState)newState {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000121 NSLog(@"PCO onIceConnectionChange. %d", newState);
122}
123
124@end
125
126@interface APPRTCAppDelegate ()
127
128@property(nonatomic, strong) APPRTCAppClient *client;
129@property(nonatomic, strong) PCObserver *pcObserver;
130@property(nonatomic, strong) RTCPeerConnection *peerConnection;
131@property(nonatomic, strong) RTCPeerConnectionFactory *peerConnectionFactory;
132@property(nonatomic, strong) NSMutableArray *queuedRemoteCandidates;
133
134@end
135
136@implementation APPRTCAppDelegate
137
138#pragma mark - UIApplicationDelegate methods
139
140- (BOOL)application:(UIApplication *)application
141 didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000142 [RTCPeerConnectionFactory initializeSSL];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000143 self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
144 self.viewController =
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000145 [[APPRTCViewController alloc] initWithNibName:@"APPRTCViewController"
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000146 bundle:nil];
147 self.window.rootViewController = self.viewController;
148 [self.window makeKeyAndVisible];
149 return YES;
150}
151
152- (void)applicationWillResignActive:(UIApplication *)application {
153 [self displayLogMessage:@"Application lost focus, connection broken."];
154 [self disconnect];
155 [self.viewController resetUI];
156}
157
158- (void)applicationDidEnterBackground:(UIApplication *)application {
159}
160
161- (void)applicationWillEnterForeground:(UIApplication *)application {
162}
163
164- (void)applicationDidBecomeActive:(UIApplication *)application {
165}
166
167- (void)applicationWillTerminate:(UIApplication *)application {
168}
169
170- (BOOL)application:(UIApplication *)application
171 openURL:(NSURL *)url
172 sourceApplication:(NSString *)sourceApplication
173 annotation:(id)annotation {
174 if (self.client) {
175 return NO;
176 }
177 self.client = [[APPRTCAppClient alloc] init];
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000178 self.client.ICEServerDelegate = self;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000179 self.client.messageHandler = self;
180 [self.client connectToRoom:url];
181 return YES;
182}
183
184- (void)displayLogMessage:(NSString *)message {
185 NSLog(@"%@", message);
186 [self.viewController displayText:message];
187}
188
189#pragma mark - RTCSendMessage method
190
191- (void)sendData:(NSData *)data {
192 [self.client sendData:data];
193}
194
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000195#pragma mark - ICEServerDelegate method
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000196
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000197- (void)onICEServers:(NSArray *)servers {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000198 self.queuedRemoteCandidates = [NSMutableArray array];
199 self.peerConnectionFactory = [[RTCPeerConnectionFactory alloc] init];
200 RTCMediaConstraints *constraints = [[RTCMediaConstraints alloc] init];
201 self.pcObserver = [[PCObserver alloc] initWithDelegate:self];
202 self.peerConnection =
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000203 [self.peerConnectionFactory peerConnectionWithICEServers:servers
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000204 constraints:constraints
205 delegate:self.pcObserver];
206 RTCMediaStream *lms =
207 [self.peerConnectionFactory mediaStreamWithLabel:@"ARDAMS"];
208 // TODO(hughv): Add video.
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000209 [lms addAudioTrack:[self.peerConnectionFactory audioTrackWithID:@"ARDAMSa0"]];
210 [self.peerConnection addStream:lms constraints:constraints];
211 [self displayLogMessage:@"onICEServers - add local stream."];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000212}
213
214#pragma mark - GAEMessageHandler methods
215
216- (void)onOpen {
217 [self displayLogMessage:@"GAE onOpen - create offer."];
218 RTCPair *audio =
219 [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" value:@"true"];
220 // TODO(hughv): Add video.
221 // RTCPair *video = [[RTCPair alloc] initWithKey:@"OfferToReceiveVideo"
222 // value:@"true"];
223 NSArray *mandatory = @[ audio /*, video*/ ];
224 RTCMediaConstraints *constraints =
225 [[RTCMediaConstraints alloc] initWithMandatoryConstraints:mandatory
226 optionalConstraints:nil];
227 [self.peerConnection createOfferWithDelegate:self constraints:constraints];
228 [self displayLogMessage:@"PC - createOffer."];
229}
230
231- (void)onMessage:(NSString *)data {
232 NSString *message = [self unHTMLifyString:data];
233 NSError *error;
234 NSDictionary *objects = [NSJSONSerialization
235 JSONObjectWithData:[message dataUsingEncoding:NSUTF8StringEncoding]
236 options:0
237 error:&error];
238 NSAssert(!error,
239 @"%@",
240 [NSString stringWithFormat:@"Error: %@", error.description]);
241 NSAssert([objects count] > 0, @"Invalid JSON object");
242 NSString *value = [objects objectForKey:@"type"];
243 [self displayLogMessage:
244 [NSString stringWithFormat:@"GAE onMessage type - %@", value]];
245 if ([value compare:@"candidate"] == NSOrderedSame) {
246 NSString *mid = [objects objectForKey:@"id"];
247 NSNumber *sdpLineIndex = [objects objectForKey:@"label"];
248 NSString *sdp = [objects objectForKey:@"candidate"];
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000249 RTCICECandidate *candidate =
250 [[RTCICECandidate alloc] initWithMid:mid
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000251 index:sdpLineIndex.intValue
252 sdp:sdp];
253 if (self.queuedRemoteCandidates) {
254 [self.queuedRemoteCandidates addObject:candidate];
255 } else {
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000256 [self.peerConnection addICECandidate:candidate];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000257 }
258 } else if (([value compare:@"offer"] == NSOrderedSame) ||
259 ([value compare:@"answer"] == NSOrderedSame)) {
260 NSString *sdpString = [objects objectForKey:@"sdp"];
261 RTCSessionDescription *sdp =
262 [[RTCSessionDescription alloc] initWithType:value sdp:sdpString];
263 [self.peerConnection setRemoteDescriptionWithDelegate:self
264 sessionDescription:sdp];
265 [self displayLogMessage:@"PC - setRemoteDescription."];
266 } else if ([value compare:@"bye"] == NSOrderedSame) {
267 [self disconnect];
268 } else {
269 NSAssert(NO, @"Invalid message: %@", data);
270 }
271}
272
273- (void)onClose {
274 [self displayLogMessage:@"GAE onClose."];
275 [self disconnect];
276}
277
278- (void)onError:(int)code withDescription:(NSString *)description {
279 [self displayLogMessage:
280 [NSString stringWithFormat:@"GAE onError: %@", description]];
281 [self disconnect];
282}
283
284#pragma mark - RTCSessionDescriptonDelegate methods
285
286- (void)peerConnection:(RTCPeerConnection *)peerConnection
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000287 didCreateSessionDescription:(RTCSessionDescription *)sdp
288 error:(NSError *)error {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000289 if (error) {
290 [self displayLogMessage:@"SDP onFailure."];
291 NSAssert(NO, error.description);
292 return;
293 }
294
295 [self displayLogMessage:@"SDP onSuccess(SDP) - set local description."];
296 [self.peerConnection setLocalDescriptionWithDelegate:self
297 sessionDescription:sdp];
298 [self displayLogMessage:@"PC setLocalDescription."];
299 dispatch_async(dispatch_get_main_queue(), ^(void) {
300 NSDictionary *json = @{ @"type" : sdp.type, @"sdp" : sdp.description };
301 NSError *error;
302 NSData *data =
303 [NSJSONSerialization dataWithJSONObject:json options:0 error:&error];
304 NSAssert(!error,
305 @"%@",
306 [NSString stringWithFormat:@"Error: %@", error.description]);
307 [self sendData:data];
308 });
309}
310
311- (void)peerConnection:(RTCPeerConnection *)peerConnection
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000312 didSetSessionDescriptionWithError:(NSError *)error {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000313 if (error) {
314 [self displayLogMessage:@"SDP onFailure."];
315 NSAssert(NO, error.description);
316 return;
317 }
318
319 [self displayLogMessage:@"SDP onSuccess() - possibly drain candidates"];
320 dispatch_async(dispatch_get_main_queue(), ^(void) {
321 // TODO(hughv): Handle non-initiator case. http://s10/46622051
322 if (self.peerConnection.remoteDescription) {
323 [self displayLogMessage:@"SDP onSuccess - drain candidates"];
324 [self drainRemoteCandidates];
325 }
326 });
327}
328
329#pragma mark - internal methods
330
331- (void)disconnect {
332 [self.client
333 sendData:[@"{\"type\": \"bye\"}" dataUsingEncoding:NSUTF8StringEncoding]];
334 self.peerConnection = nil;
335 self.peerConnectionFactory = nil;
336 self.pcObserver = nil;
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000337 self.client.ICEServerDelegate = nil;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000338 self.client.messageHandler = nil;
339 self.client = nil;
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000340 [RTCPeerConnectionFactory deinitializeSSL];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000341}
342
343- (void)drainRemoteCandidates {
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000344 for (RTCICECandidate *candidate in self.queuedRemoteCandidates) {
345 [self.peerConnection addICECandidate:candidate];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000346 }
347 self.queuedRemoteCandidates = nil;
348}
349
350- (NSString *)unHTMLifyString:(NSString *)base {
351 // TODO(hughv): Investigate why percent escapes are being added. Removing
352 // them isn't necessary on Android.
353 // convert HTML escaped characters to UTF8.
354 NSString *removePercent =
355 [base stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
356 // remove leading and trailing ".
357 NSRange range;
358 range.length = [removePercent length] - 2;
359 range.location = 1;
360 NSString *removeQuotes = [removePercent substringWithRange:range];
361 // convert \" to ".
362 NSString *removeEscapedQuotes =
363 [removeQuotes stringByReplacingOccurrencesOfString:@"\\\""
364 withString:@"\""];
365 // convert \\ to \.
366 NSString *removeBackslash =
367 [removeEscapedQuotes stringByReplacingOccurrencesOfString:@"\\\\"
368 withString:@"\\"];
369 return removeBackslash;
370}
371
372@end