Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / third_party / libjingle / source / talk / examples / ios / AppRTCDemo / APPRTCAppDelegate.m
1 /*
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"
31 #import "RTCICECandidate.h"
32 #import "RTCICEServer.h"
33 #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
64     signalingStateChanged:(RTCSignalingState)stateChanged {
65   NSLog(@"PCO onSignalingStateChange: %d", stateChanged);
66 }
67
68 - (void)peerConnection:(RTCPeerConnection *)peerConnection
69              addedStream:(RTCMediaStream *)stream {
70   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
81          removedStream:(RTCMediaStream *)stream {
82   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
93        gotICECandidate:(RTCICECandidate *)candidate {
94   NSLog(@"PCO onICECandidate.\n  Mid[%@] Index[%d] Sdp[%@]",
95         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
115     iceGatheringChanged:(RTCICEGatheringState)newState {
116   NSLog(@"PCO onIceGatheringChange. %d", newState);
117 }
118
119 - (void)peerConnection:(RTCPeerConnection *)peerConnection
120     iceConnectionChanged:(RTCICEConnectionState)newState {
121   NSLog(@"PCO onIceConnectionChange. %d", newState);
122   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];
129 }
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
145 @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
153 #pragma mark - UIApplicationDelegate methods
154
155 - (BOOL)application:(UIApplication *)application
156     didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
157   [RTCPeerConnectionFactory initializeSSL];
158   self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
159   self.viewController =
160       [[APPRTCViewController alloc] initWithNibName:@"APPRTCViewController"
161                                              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];
193   self.client.ICEServerDelegate = self;
194   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
210 #pragma mark - ICEServerDelegate method
211
212 - (void)onICEServers:(NSArray *)servers {
213   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 =
218       [self.peerConnectionFactory peerConnectionWithICEServers:servers
219                                                    constraints:constraints
220                                                       delegate:self.pcObserver];
221   RTCMediaStream *lms =
222       [self.peerConnectionFactory mediaStreamWithLabel:@"ARDAMS"];
223   // TODO(hughv): Add video.
224   [lms addAudioTrack:[self.peerConnectionFactory audioTrackWithID:@"ARDAMSa0"]];
225   [self.peerConnection addStream:lms constraints:constraints];
226   [self displayLogMessage:@"onICEServers - add local stream."];
227 }
228
229 #pragma mark - GAEMessageHandler methods
230
231 - (void)onOpen {
232   if (!self.client.initiator) {
233       [self displayLogMessage:@"Callee; waiting for remote offer"];
234       return;
235   }
236   [self displayLogMessage:@"GAE onOpen - create offer."];
237   RTCPair *audio =
238       [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" value:@"true"];
239   // TODO(hughv): Add video.
240   //  RTCPair *video = [[RTCPair alloc] initWithKey:@"OfferToReceiveVideo"
241   //                                          value:@"true"];
242   NSArray *mandatory = @[ audio /*, video*/ ];
243   RTCMediaConstraints *constraints =
244       [[RTCMediaConstraints alloc] initWithMandatoryConstraints:mandatory
245                                             optionalConstraints:nil];
246   [self.peerConnection createOfferWithDelegate:self constraints:constraints];
247   [self displayLogMessage:@"PC - createOffer."];
248 }
249
250 - (void)onMessage:(NSString *)data {
251   NSString *message = [self unHTMLifyString:data];
252   NSError *error;
253   NSDictionary *objects = [NSJSONSerialization
254       JSONObjectWithData:[message dataUsingEncoding:NSUTF8StringEncoding]
255                  options:0
256                    error:&error];
257   NSAssert(!error,
258            @"%@",
259            [NSString stringWithFormat:@"Error: %@", error.description]);
260   NSAssert([objects count] > 0, @"Invalid JSON object");
261   NSString *value = [objects objectForKey:@"type"];
262   [self displayLogMessage:
263           [NSString stringWithFormat:@"GAE onMessage type - %@", value]];
264   if ([value compare:@"candidate"] == NSOrderedSame) {
265     NSString *mid = [objects objectForKey:@"id"];
266     NSNumber *sdpLineIndex = [objects objectForKey:@"label"];
267     NSString *sdp = [objects objectForKey:@"candidate"];
268     RTCICECandidate *candidate =
269         [[RTCICECandidate alloc] initWithMid:mid
270                                        index:sdpLineIndex.intValue
271                                          sdp:sdp];
272     if (self.queuedRemoteCandidates) {
273       [self.queuedRemoteCandidates addObject:candidate];
274     } else {
275       [self.peerConnection addICECandidate:candidate];
276     }
277   } else if (([value compare:@"offer"] == NSOrderedSame) ||
278              ([value compare:@"answer"] == NSOrderedSame)) {
279     NSString *sdpString = [objects objectForKey:@"sdp"];
280     RTCSessionDescription *sdp = [[RTCSessionDescription alloc]
281         initWithType:value sdp:[APPRTCAppDelegate preferISAC:sdpString]];
282     [self.peerConnection setRemoteDescriptionWithDelegate:self
283                                        sessionDescription:sdp];
284     [self displayLogMessage:@"PC - setRemoteDescription."];
285   } else if ([value compare:@"bye"] == NSOrderedSame) {
286     [self disconnect];
287   } else {
288     NSAssert(NO, @"Invalid message: %@", data);
289   }
290 }
291
292 - (void)onClose {
293   [self displayLogMessage:@"GAE onClose."];
294   [self disconnect];
295 }
296
297 - (void)onError:(int)code withDescription:(NSString *)description {
298   [self displayLogMessage:
299           [NSString stringWithFormat:@"GAE onError:  %@", description]];
300   [self disconnect];
301 }
302
303 #pragma mark - RTCSessionDescriptonDelegate methods
304
305 // Match |pattern| to |string| and return the first group of the first
306 // match, or nil if no match was found.
307 + (NSString *)firstMatch:(NSRegularExpression *)pattern
308               withString:(NSString *)string {
309   NSTextCheckingResult* result =
310     [pattern firstMatchInString:string
311                         options:0
312                           range:NSMakeRange(0, [string length])];
313   if (!result)
314     return nil;
315   return [string substringWithRange:[result rangeAtIndex:1]];
316 }
317
318 // Mangle |origSDP| to prefer the ISAC/16k audio codec.
319 + (NSString *)preferISAC:(NSString *)origSDP {
320   int mLineIndex = -1;
321   NSString* isac16kRtpMap = nil;
322   NSArray* lines = [origSDP componentsSeparatedByString:@"\n"];
323   NSRegularExpression* isac16kRegex = [NSRegularExpression
324       regularExpressionWithPattern:@"^a=rtpmap:(\\d+) ISAC/16000[\r]?$"
325                            options:0
326                              error:nil];
327   for (int i = 0;
328        (i < [lines count]) && (mLineIndex == -1 || isac16kRtpMap == nil);
329        ++i) {
330     NSString* line = [lines objectAtIndex:i];
331     if ([line hasPrefix:@"m=audio "]) {
332       mLineIndex = i;
333       continue;
334     }
335     isac16kRtpMap = [self firstMatch:isac16kRegex withString:line];
336   }
337   if (mLineIndex == -1) {
338     NSLog(@"No m=audio line, so can't prefer iSAC");
339     return origSDP;
340   }
341   if (isac16kRtpMap == nil) {
342     NSLog(@"No ISAC/16000 line, so can't prefer iSAC");
343     return origSDP;
344   }
345   NSArray* origMLineParts =
346       [[lines objectAtIndex:mLineIndex] componentsSeparatedByString:@" "];
347   NSMutableArray* newMLine =
348       [NSMutableArray arrayWithCapacity:[origMLineParts count]];
349   int origPartIndex = 0;
350   // Format is: m=<media> <port> <proto> <fmt> ...
351   [newMLine addObject:[origMLineParts objectAtIndex:origPartIndex++]];
352   [newMLine addObject:[origMLineParts objectAtIndex:origPartIndex++]];
353   [newMLine addObject:[origMLineParts objectAtIndex:origPartIndex++]];
354   [newMLine addObject:isac16kRtpMap];
355   for (; origPartIndex < [origMLineParts count]; ++origPartIndex) {
356     if ([isac16kRtpMap compare:[origMLineParts objectAtIndex:origPartIndex]]
357         != NSOrderedSame) {
358       [newMLine addObject:[origMLineParts objectAtIndex:origPartIndex]];
359     }
360   }
361   NSMutableArray* newLines = [NSMutableArray arrayWithCapacity:[lines count]];
362   [newLines addObjectsFromArray:lines];
363   [newLines replaceObjectAtIndex:mLineIndex
364                       withObject:[newMLine componentsJoinedByString:@" "]];
365   return [newLines componentsJoinedByString:@"\n"];
366 }
367
368 - (void)peerConnection:(RTCPeerConnection *)peerConnection
369     didCreateSessionDescription:(RTCSessionDescription *)origSdp
370                           error:(NSError *)error {
371   if (error) {
372     [self displayLogMessage:@"SDP onFailure."];
373     NSAssert(NO, error.description);
374     return;
375   }
376
377   [self displayLogMessage:@"SDP onSuccess(SDP) - set local description."];
378   RTCSessionDescription* sdp =
379       [[RTCSessionDescription alloc]
380           initWithType:origSdp.type
381                    sdp:[APPRTCAppDelegate preferISAC:origSdp.description]];
382   [self.peerConnection setLocalDescriptionWithDelegate:self
383                                     sessionDescription:sdp];
384   [self displayLogMessage:@"PC setLocalDescription."];
385   dispatch_async(dispatch_get_main_queue(), ^(void) {
386     NSDictionary *json = @{ @"type" : sdp.type, @"sdp" : sdp.description };
387     NSError *error;
388     NSData *data =
389         [NSJSONSerialization dataWithJSONObject:json options:0 error:&error];
390     NSAssert(!error,
391              @"%@",
392              [NSString stringWithFormat:@"Error: %@", error.description]);
393     [self sendData:data];
394   });
395 }
396
397 - (void)peerConnection:(RTCPeerConnection *)peerConnection
398     didSetSessionDescriptionWithError:(NSError *)error {
399   if (error) {
400     [self displayLogMessage:@"SDP onFailure."];
401     NSAssert(NO, error.description);
402     return;
403   }
404
405   [self displayLogMessage:@"SDP onSuccess() - possibly drain candidates"];
406   dispatch_async(dispatch_get_main_queue(), ^(void) {
407     if (!self.client.initiator) {
408         if (self.peerConnection.remoteDescription
409                 && !self.peerConnection.localDescription) {
410             [self displayLogMessage:@"Callee, setRemoteDescription succeeded"];
411             RTCPair *audio =
412                 [[RTCPair alloc]
413                     initWithKey:@"OfferToReceiveAudio" value:@"true"];
414             // TODO(hughv): Add video.
415             // RTCPair *video =
416             //    [[RTCPair alloc]
417             //        initWithKey:@"OfferToReceiveVideo" value:@"true"];
418             NSArray *mandatory = @[ audio /*, video*/ ];
419             RTCMediaConstraints *constraints =
420                 [[RTCMediaConstraints alloc]
421                     initWithMandatoryConstraints:mandatory
422                     optionalConstraints:nil];
423             [self.peerConnection
424                 createAnswerWithDelegate:self constraints:constraints];
425             [self displayLogMessage:@"PC - createAnswer."];
426         } else {
427             [self displayLogMessage:@"SDP onSuccess - drain candidates"];
428             [self drainRemoteCandidates];
429         }
430     } else {
431         if (self.peerConnection.remoteDescription) {
432             [self displayLogMessage:@"SDP onSuccess - drain candidates"];
433             [self drainRemoteCandidates];
434         }
435     }
436   });
437 }
438
439 #pragma mark - internal methods
440
441 - (void)disconnect {
442   [self.client
443       sendData:[@"{\"type\": \"bye\"}" dataUsingEncoding:NSUTF8StringEncoding]];
444   self.peerConnection = nil;
445   self.peerConnectionFactory = nil;
446   self.pcObserver = nil;
447   self.client.ICEServerDelegate = nil;
448   self.client.messageHandler = nil;
449   self.client = nil;
450   [RTCPeerConnectionFactory deinitializeSSL];
451 }
452
453 - (void)drainRemoteCandidates {
454   for (RTCICECandidate *candidate in self.queuedRemoteCandidates) {
455     [self.peerConnection addICECandidate:candidate];
456   }
457   self.queuedRemoteCandidates = nil;
458 }
459
460 - (NSString *)unHTMLifyString:(NSString *)base {
461   // TODO(hughv): Investigate why percent escapes are being added.  Removing
462   // them isn't necessary on Android.
463   // convert HTML escaped characters to UTF8.
464   NSString *removePercent =
465       [base stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
466   // remove leading and trailing ".
467   NSRange range;
468   range.length = [removePercent length] - 2;
469   range.location = 1;
470   NSString *removeQuotes = [removePercent substringWithRange:range];
471   // convert \" to ".
472   NSString *removeEscapedQuotes =
473       [removeQuotes stringByReplacingOccurrencesOfString:@"\\\""
474                                               withString:@"\""];
475   // convert \\ to \.
476   NSString *removeBackslash =
477       [removeEscapedQuotes stringByReplacingOccurrencesOfString:@"\\\\"
478                                                      withString:@"\\"];
479   return removeBackslash;
480 }
481
482 @end