blob: 65cdd097944d7d67b39439ced3029fdaf5c094aa [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 {
fischman@webrtc.orgc31d4d02013-09-05 21:49:58 +000065 NSLog(@"PCO onSignalingStateChange: %d", stateChanged);
henrike@webrtc.org28e20752013-07-10 00:45:36 +000066}
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);
fischman@webrtc.orgc31d4d02013-09-05 21:49:58 +0000122 if (newState == RTCICEConnectionConnected)
123 [self displayLogMessage:@"ICE Connection Connected."];
124 NSAssert(newState != RTCICEConnectionFailed, @"ICE Connection failed!");
125}
126
127- (void)displayLogMessage:(NSString *)message {
128 [_delegate displayLogMessage:message];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000129}
130
131@end
132
133@interface APPRTCAppDelegate ()
134
135@property(nonatomic, strong) APPRTCAppClient *client;
136@property(nonatomic, strong) PCObserver *pcObserver;
137@property(nonatomic, strong) RTCPeerConnection *peerConnection;
138@property(nonatomic, strong) RTCPeerConnectionFactory *peerConnectionFactory;
139@property(nonatomic, strong) NSMutableArray *queuedRemoteCandidates;
140
141@end
142
143@implementation APPRTCAppDelegate
144
fischman@webrtc.org9ca93a82013-10-29 00:14:15 +0000145@synthesize window = _window;
146@synthesize viewController = _viewController;
147@synthesize client = _client;
148@synthesize pcObserver = _pcObserver;
149@synthesize peerConnection = _peerConnection;
150@synthesize peerConnectionFactory = _peerConnectionFactory;
151@synthesize queuedRemoteCandidates = _queuedRemoteCandidates;
152
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000153#pragma mark - UIApplicationDelegate methods
154
155- (BOOL)application:(UIApplication *)application
156 didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000157 [RTCPeerConnectionFactory initializeSSL];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000158 self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
159 self.viewController =
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000160 [[APPRTCViewController alloc] initWithNibName:@"APPRTCViewController"
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000161 bundle:nil];
162 self.window.rootViewController = self.viewController;
163 [self.window makeKeyAndVisible];
164 return YES;
165}
166
167- (void)applicationWillResignActive:(UIApplication *)application {
168 [self displayLogMessage:@"Application lost focus, connection broken."];
169 [self disconnect];
170 [self.viewController resetUI];
171}
172
173- (void)applicationDidEnterBackground:(UIApplication *)application {
174}
175
176- (void)applicationWillEnterForeground:(UIApplication *)application {
177}
178
179- (void)applicationDidBecomeActive:(UIApplication *)application {
180}
181
182- (void)applicationWillTerminate:(UIApplication *)application {
183}
184
185- (BOOL)application:(UIApplication *)application
186 openURL:(NSURL *)url
187 sourceApplication:(NSString *)sourceApplication
188 annotation:(id)annotation {
189 if (self.client) {
190 return NO;
191 }
192 self.client = [[APPRTCAppClient alloc] init];
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000193 self.client.ICEServerDelegate = self;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000194 self.client.messageHandler = self;
195 [self.client connectToRoom:url];
196 return YES;
197}
198
199- (void)displayLogMessage:(NSString *)message {
200 NSLog(@"%@", message);
201 [self.viewController displayText:message];
202}
203
204#pragma mark - RTCSendMessage method
205
206- (void)sendData:(NSData *)data {
207 [self.client sendData:data];
208}
209
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000210#pragma mark - ICEServerDelegate method
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000211
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000212- (void)onICEServers:(NSArray *)servers {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000213 self.queuedRemoteCandidates = [NSMutableArray array];
214 self.peerConnectionFactory = [[RTCPeerConnectionFactory alloc] init];
215 RTCMediaConstraints *constraints = [[RTCMediaConstraints alloc] init];
216 self.pcObserver = [[PCObserver alloc] initWithDelegate:self];
217 self.peerConnection =
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000218 [self.peerConnectionFactory peerConnectionWithICEServers:servers
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000219 constraints:constraints
220 delegate:self.pcObserver];
221 RTCMediaStream *lms =
222 [self.peerConnectionFactory mediaStreamWithLabel:@"ARDAMS"];
223 // TODO(hughv): Add video.
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000224 [lms addAudioTrack:[self.peerConnectionFactory audioTrackWithID:@"ARDAMSa0"]];
225 [self.peerConnection addStream:lms constraints:constraints];
226 [self displayLogMessage:@"onICEServers - add local stream."];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000227}
228
229#pragma mark - GAEMessageHandler methods
230
231- (void)onOpen {
232 [self displayLogMessage:@"GAE onOpen - create offer."];
233 RTCPair *audio =
234 [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" value:@"true"];
235 // TODO(hughv): Add video.
236 // RTCPair *video = [[RTCPair alloc] initWithKey:@"OfferToReceiveVideo"
237 // value:@"true"];
238 NSArray *mandatory = @[ audio /*, video*/ ];
239 RTCMediaConstraints *constraints =
240 [[RTCMediaConstraints alloc] initWithMandatoryConstraints:mandatory
241 optionalConstraints:nil];
242 [self.peerConnection createOfferWithDelegate:self constraints:constraints];
243 [self displayLogMessage:@"PC - createOffer."];
244}
245
246- (void)onMessage:(NSString *)data {
247 NSString *message = [self unHTMLifyString:data];
248 NSError *error;
249 NSDictionary *objects = [NSJSONSerialization
250 JSONObjectWithData:[message dataUsingEncoding:NSUTF8StringEncoding]
251 options:0
252 error:&error];
253 NSAssert(!error,
254 @"%@",
255 [NSString stringWithFormat:@"Error: %@", error.description]);
256 NSAssert([objects count] > 0, @"Invalid JSON object");
257 NSString *value = [objects objectForKey:@"type"];
258 [self displayLogMessage:
259 [NSString stringWithFormat:@"GAE onMessage type - %@", value]];
260 if ([value compare:@"candidate"] == NSOrderedSame) {
261 NSString *mid = [objects objectForKey:@"id"];
262 NSNumber *sdpLineIndex = [objects objectForKey:@"label"];
263 NSString *sdp = [objects objectForKey:@"candidate"];
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000264 RTCICECandidate *candidate =
265 [[RTCICECandidate alloc] initWithMid:mid
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000266 index:sdpLineIndex.intValue
267 sdp:sdp];
268 if (self.queuedRemoteCandidates) {
269 [self.queuedRemoteCandidates addObject:candidate];
270 } else {
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000271 [self.peerConnection addICECandidate:candidate];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000272 }
273 } else if (([value compare:@"offer"] == NSOrderedSame) ||
274 ([value compare:@"answer"] == NSOrderedSame)) {
275 NSString *sdpString = [objects objectForKey:@"sdp"];
fischman@webrtc.orgc31d4d02013-09-05 21:49:58 +0000276 RTCSessionDescription *sdp = [[RTCSessionDescription alloc]
277 initWithType:value sdp:[APPRTCAppDelegate preferISAC:sdpString]];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000278 [self.peerConnection setRemoteDescriptionWithDelegate:self
279 sessionDescription:sdp];
280 [self displayLogMessage:@"PC - setRemoteDescription."];
281 } else if ([value compare:@"bye"] == NSOrderedSame) {
282 [self disconnect];
283 } else {
284 NSAssert(NO, @"Invalid message: %@", data);
285 }
286}
287
288- (void)onClose {
289 [self displayLogMessage:@"GAE onClose."];
290 [self disconnect];
291}
292
293- (void)onError:(int)code withDescription:(NSString *)description {
294 [self displayLogMessage:
295 [NSString stringWithFormat:@"GAE onError: %@", description]];
296 [self disconnect];
297}
298
299#pragma mark - RTCSessionDescriptonDelegate methods
300
fischman@webrtc.orgc31d4d02013-09-05 21:49:58 +0000301// Match |pattern| to |string| and return the first group of the first
302// match, or nil if no match was found.
303+ (NSString *)firstMatch:(NSRegularExpression *)pattern
304 withString:(NSString *)string {
305 NSTextCheckingResult* result =
306 [pattern firstMatchInString:string
307 options:0
308 range:NSMakeRange(0, [string length])];
309 if (!result)
310 return nil;
311 return [string substringWithRange:[result rangeAtIndex:1]];
312}
313
314// Mangle |origSDP| to prefer the ISAC/16k audio codec.
315+ (NSString *)preferISAC:(NSString *)origSDP {
316 int mLineIndex = -1;
317 NSString* isac16kRtpMap = nil;
318 NSArray* lines = [origSDP componentsSeparatedByString:@"\n"];
319 NSRegularExpression* isac16kRegex = [NSRegularExpression
320 regularExpressionWithPattern:@"^a=rtpmap:(\\d+) ISAC/16000[\r]?$"
321 options:0
322 error:nil];
323 for (int i = 0;
324 (i < [lines count]) && (mLineIndex == -1 || isac16kRtpMap == nil);
325 ++i) {
326 NSString* line = [lines objectAtIndex:i];
327 if ([line hasPrefix:@"m=audio "]) {
328 mLineIndex = i;
329 continue;
330 }
331 isac16kRtpMap = [self firstMatch:isac16kRegex withString:line];
332 }
333 if (mLineIndex == -1) {
334 NSLog(@"No m=audio line, so can't prefer iSAC");
335 return origSDP;
336 }
337 if (isac16kRtpMap == nil) {
338 NSLog(@"No ISAC/16000 line, so can't prefer iSAC");
339 return origSDP;
340 }
341 NSArray* origMLineParts =
342 [[lines objectAtIndex:mLineIndex] componentsSeparatedByString:@" "];
343 NSMutableArray* newMLine =
344 [NSMutableArray arrayWithCapacity:[origMLineParts count]];
345 int origPartIndex = 0;
346 // Format is: m=<media> <port> <proto> <fmt> ...
347 [newMLine addObject:[origMLineParts objectAtIndex:origPartIndex++]];
348 [newMLine addObject:[origMLineParts objectAtIndex:origPartIndex++]];
349 [newMLine addObject:[origMLineParts objectAtIndex:origPartIndex++]];
350 [newMLine addObject:isac16kRtpMap];
351 for (; origPartIndex < [origMLineParts count]; ++origPartIndex) {
352 if ([isac16kRtpMap compare:[origMLineParts objectAtIndex:origPartIndex]]
353 != NSOrderedSame) {
354 [newMLine addObject:[origMLineParts objectAtIndex:origPartIndex]];
355 }
356 }
357 NSMutableArray* newLines = [NSMutableArray arrayWithCapacity:[lines count]];
358 [newLines addObjectsFromArray:lines];
359 [newLines replaceObjectAtIndex:mLineIndex
360 withObject:[newMLine componentsJoinedByString:@" "]];
361 return [newLines componentsJoinedByString:@"\n"];
362}
363
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000364- (void)peerConnection:(RTCPeerConnection *)peerConnection
fischman@webrtc.orgc31d4d02013-09-05 21:49:58 +0000365 didCreateSessionDescription:(RTCSessionDescription *)origSdp
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000366 error:(NSError *)error {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000367 if (error) {
368 [self displayLogMessage:@"SDP onFailure."];
369 NSAssert(NO, error.description);
370 return;
371 }
372
373 [self displayLogMessage:@"SDP onSuccess(SDP) - set local description."];
fischman@webrtc.orgc31d4d02013-09-05 21:49:58 +0000374 RTCSessionDescription* sdp =
375 [[RTCSessionDescription alloc]
376 initWithType:origSdp.type
377 sdp:[APPRTCAppDelegate preferISAC:origSdp.description]];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000378 [self.peerConnection setLocalDescriptionWithDelegate:self
379 sessionDescription:sdp];
380 [self displayLogMessage:@"PC setLocalDescription."];
381 dispatch_async(dispatch_get_main_queue(), ^(void) {
382 NSDictionary *json = @{ @"type" : sdp.type, @"sdp" : sdp.description };
383 NSError *error;
384 NSData *data =
385 [NSJSONSerialization dataWithJSONObject:json options:0 error:&error];
386 NSAssert(!error,
387 @"%@",
388 [NSString stringWithFormat:@"Error: %@", error.description]);
389 [self sendData:data];
390 });
391}
392
393- (void)peerConnection:(RTCPeerConnection *)peerConnection
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000394 didSetSessionDescriptionWithError:(NSError *)error {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000395 if (error) {
396 [self displayLogMessage:@"SDP onFailure."];
397 NSAssert(NO, error.description);
398 return;
399 }
400
401 [self displayLogMessage:@"SDP onSuccess() - possibly drain candidates"];
402 dispatch_async(dispatch_get_main_queue(), ^(void) {
403 // TODO(hughv): Handle non-initiator case. http://s10/46622051
404 if (self.peerConnection.remoteDescription) {
405 [self displayLogMessage:@"SDP onSuccess - drain candidates"];
406 [self drainRemoteCandidates];
407 }
408 });
409}
410
411#pragma mark - internal methods
412
413- (void)disconnect {
414 [self.client
415 sendData:[@"{\"type\": \"bye\"}" dataUsingEncoding:NSUTF8StringEncoding]];
416 self.peerConnection = nil;
417 self.peerConnectionFactory = nil;
418 self.pcObserver = nil;
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000419 self.client.ICEServerDelegate = nil;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000420 self.client.messageHandler = nil;
421 self.client = nil;
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000422 [RTCPeerConnectionFactory deinitializeSSL];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000423}
424
425- (void)drainRemoteCandidates {
fischman@webrtc.org1bc19542013-08-01 18:29:45 +0000426 for (RTCICECandidate *candidate in self.queuedRemoteCandidates) {
427 [self.peerConnection addICECandidate:candidate];
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000428 }
429 self.queuedRemoteCandidates = nil;
430}
431
432- (NSString *)unHTMLifyString:(NSString *)base {
433 // TODO(hughv): Investigate why percent escapes are being added. Removing
434 // them isn't necessary on Android.
435 // convert HTML escaped characters to UTF8.
436 NSString *removePercent =
437 [base stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
438 // remove leading and trailing ".
439 NSRange range;
440 range.length = [removePercent length] - 2;
441 range.location = 1;
442 NSString *removeQuotes = [removePercent substringWithRange:range];
443 // convert \" to ".
444 NSString *removeEscapedQuotes =
445 [removeQuotes stringByReplacingOccurrencesOfString:@"\\\""
446 withString:@"\""];
447 // convert \\ to \.
448 NSString *removeBackslash =
449 [removeEscapedQuotes stringByReplacingOccurrencesOfString:@"\\\\"
450 withString:@"\\"];
451 return removeBackslash;
452}
453
454@end