 7c82adae61
			
		
	
	7c82adae61
	
	
	
		
			
			R=fischman@webrtc.org Review URL: https://webrtc-codereview.appspot.com/16379004 Patch from Bridger Maxwell <bridgeyman@gmail.com>. git-svn-id: http://webrtc.googlecode.com/svn/trunk@6028 4adac7df-926f-26a2-2b94-8c16560cd09d
		
			
				
	
	
		
			349 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
			
		
		
	
	
			349 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
| /*
 | |
|  * 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 "APPRTCAppClient.h"
 | |
| 
 | |
| #import <dispatch/dispatch.h>
 | |
| 
 | |
| #import "GAEChannelClient.h"
 | |
| #import "RTCICEServer.h"
 | |
| #import "APPRTCAppDelegate.h"
 | |
| #import "RTCMediaConstraints.h"
 | |
| 
 | |
| @interface APPRTCAppClient ()
 | |
| 
 | |
| @property(nonatomic, strong) dispatch_queue_t backgroundQueue;
 | |
| @property(nonatomic, copy) NSString* baseURL;
 | |
| @property(nonatomic, strong) GAEChannelClient* gaeChannel;
 | |
| @property(nonatomic, copy) NSString* postMessageUrl;
 | |
| @property(nonatomic, copy) NSString* pcConfig;
 | |
| @property(nonatomic, strong) NSMutableString* roomHtml;
 | |
| @property(atomic, strong) NSMutableArray* sendQueue;
 | |
| @property(nonatomic, copy) NSString* token;
 | |
| 
 | |
| @property(nonatomic, assign) BOOL verboseLogging;
 | |
| 
 | |
| @end
 | |
| 
 | |
| @implementation APPRTCAppClient
 | |
| 
 | |
| - (id)initWithICEServerDelegate:(id<ICEServerDelegate>)delegate
 | |
|                  messageHandler:(id<GAEMessageHandler>)handler {
 | |
|   if (self = [super init]) {
 | |
|     _ICEServerDelegate = delegate;
 | |
|     _messageHandler = handler;
 | |
|     _backgroundQueue = dispatch_queue_create("RTCBackgroundQueue",
 | |
|                                              DISPATCH_QUEUE_SERIAL);
 | |
|     _sendQueue = [NSMutableArray array];
 | |
|     // Uncomment to see Request/Response logging.
 | |
|     // _verboseLogging = YES;
 | |
|   }
 | |
|   return self;
 | |
| }
 | |
| 
 | |
| #pragma mark - Public methods
 | |
| 
 | |
| - (void)connectToRoom:(NSURL*)url {
 | |
|   NSURLRequest* request = [self getRequestFromUrl:url];
 | |
|   [NSURLConnection connectionWithRequest:request delegate:self];
 | |
| }
 | |
| 
 | |
| - (void)sendData:(NSData*)data {
 | |
|   [self maybeLogMessage:@"Send message"];
 | |
| 
 | |
|   dispatch_async(self.backgroundQueue, ^{
 | |
|     [self.sendQueue addObject:[data copy]];
 | |
| 
 | |
|     if ([self.postMessageUrl length] < 1) {
 | |
|       return;
 | |
|     }
 | |
|     for (NSData* data in self.sendQueue) {
 | |
|       NSString* url =
 | |
|           [NSString stringWithFormat:@"%@/%@",
 | |
|                     self.baseURL, self.postMessageUrl];
 | |
|       [self sendData:data withUrl:url];
 | |
|     }
 | |
|     [self.sendQueue removeAllObjects];
 | |
|   });
 | |
| }
 | |
| 
 | |
| #pragma mark - Internal methods
 | |
| 
 | |
| - (NSString*)findVar:(NSString*)name strippingQuotes:(BOOL)strippingQuotes {
 | |
|   NSError* error;
 | |
|   NSString* pattern =
 | |
|       [NSString stringWithFormat:@".*\n *var %@ = ([^\n]*);\n.*", name];
 | |
|   NSRegularExpression* regexp =
 | |
|       [NSRegularExpression regularExpressionWithPattern:pattern
 | |
|                                                 options:0
 | |
|                                                   error:&error];
 | |
|   NSAssert(!error,
 | |
|            @"Unexpected error compiling regex: ",
 | |
|            error.localizedDescription);
 | |
| 
 | |
|   NSRange fullRange = NSMakeRange(0, [self.roomHtml length]);
 | |
|   NSArray* matches =
 | |
|       [regexp matchesInString:self.roomHtml options:0 range:fullRange];
 | |
|   if ([matches count] != 1) {
 | |
|     [self showMessage:[NSString stringWithFormat:@"%d matches for %@ in %@",
 | |
|                                                  [matches count],
 | |
|                                                  name,
 | |
|                                                  self.roomHtml]];
 | |
|     return nil;
 | |
|   }
 | |
|   NSRange matchRange = [matches[0] rangeAtIndex:1];
 | |
|   NSString* value = [self.roomHtml substringWithRange:matchRange];
 | |
|   if (strippingQuotes) {
 | |
|     NSAssert([value length] > 2,
 | |
|              @"Can't strip quotes from short string: [%@]",
 | |
|              value);
 | |
|     NSAssert(([value characterAtIndex:0] == '\'' &&
 | |
|               [value characterAtIndex:[value length] - 1] == '\''),
 | |
|              @"Can't strip quotes from unquoted string: [%@]",
 | |
|              value);
 | |
|     value = [value substringWithRange:NSMakeRange(1, [value length] - 2)];
 | |
|   }
 | |
|   return value;
 | |
| }
 | |
| 
 | |
| - (NSURLRequest*)getRequestFromUrl:(NSURL*)url {
 | |
|   self.roomHtml = [NSMutableString stringWithCapacity:20000];
 | |
|   NSString* path =
 | |
|       [NSString stringWithFormat:@"https:%@", [url resourceSpecifier]];
 | |
|   NSURLRequest* request =
 | |
|       [NSURLRequest requestWithURL:[NSURL URLWithString:path]];
 | |
|   return request;
 | |
| }
 | |
| 
 | |
| - (void)maybeLogMessage:(NSString*)message {
 | |
|   if (self.verboseLogging) {
 | |
|     NSLog(@"%@", message);
 | |
|   }
 | |
| }
 | |
| 
 | |
| - (void)sendData:(NSData*)data withUrl:(NSString*)url {
 | |
|   NSMutableURLRequest* request =
 | |
|       [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
 | |
|   request.HTTPMethod = @"POST";
 | |
|   [request setHTTPBody:data];
 | |
|   NSURLResponse* response;
 | |
|   NSError* error;
 | |
|   NSData* responseData = [NSURLConnection sendSynchronousRequest:request
 | |
|                                                returningResponse:&response
 | |
|                                                            error:&error];
 | |
|   NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
 | |
|   int status = [httpResponse statusCode];
 | |
|   NSAssert(status == 200,
 | |
|            @"Bad response [%d] to message: %@\n\n%@",
 | |
|            status,
 | |
|            [NSString stringWithUTF8String:[data bytes]],
 | |
|            [NSString stringWithUTF8String:[responseData bytes]]);
 | |
| }
 | |
| 
 | |
| - (void)showMessage:(NSString*)message {
 | |
|   NSLog(@"%@", message);
 | |
|   UIAlertView* alertView = [[UIAlertView alloc] initWithTitle:@"Unable to join"
 | |
|                                                       message:message
 | |
|                                                      delegate:nil
 | |
|                                             cancelButtonTitle:@"OK"
 | |
|                                             otherButtonTitles:nil];
 | |
|   [alertView show];
 | |
| }
 | |
| 
 | |
| - (void)updateICEServers:(NSMutableArray*)ICEServers
 | |
|           withTurnServer:(NSString*)turnServerUrl {
 | |
|   if ([turnServerUrl length] < 1) {
 | |
|     [self.ICEServerDelegate onICEServers:ICEServers];
 | |
|     return;
 | |
|   }
 | |
|   dispatch_async(self.backgroundQueue, ^(void) {
 | |
|       NSMutableURLRequest* request = [NSMutableURLRequest
 | |
|           requestWithURL:[NSURL URLWithString:turnServerUrl]];
 | |
|       [request addValue:@"Mozilla/5.0" forHTTPHeaderField:@"user-agent"];
 | |
|       [request addValue:@"https://apprtc.appspot.com"
 | |
|           forHTTPHeaderField:@"origin"];
 | |
|       NSURLResponse* response;
 | |
|       NSError* error;
 | |
|       NSData* responseData = [NSURLConnection sendSynchronousRequest:request
 | |
|                                                    returningResponse:&response
 | |
|                                                                error:&error];
 | |
|       if (!error) {
 | |
|         NSDictionary* json =
 | |
|             [NSJSONSerialization JSONObjectWithData:responseData
 | |
|                                             options:0
 | |
|                                               error:&error];
 | |
|         NSAssert(!error, @"Unable to parse.  %@", error.localizedDescription);
 | |
|         NSString* username = json[@"username"];
 | |
|         NSString* password = json[@"password"];
 | |
|         NSArray* uris = json[@"uris"];
 | |
|         for (int i = 0; i < [uris count]; ++i) {
 | |
|           NSString* turnServer = [uris objectAtIndex:i];
 | |
|           RTCICEServer* ICEServer =
 | |
|               [[RTCICEServer alloc] initWithURI:[NSURL URLWithString:turnServer]
 | |
|                                        username:username
 | |
|                                        password:password];
 | |
|           NSLog(@"Added ICE Server: %@", ICEServer);
 | |
|           [ICEServers addObject:ICEServer];
 | |
|         }
 | |
|       } else {
 | |
|         NSLog(@"Unable to get TURN server.  Error: %@", error.description);
 | |
|       }
 | |
| 
 | |
|       dispatch_async(dispatch_get_main_queue(), ^(void) {
 | |
|           [self.ICEServerDelegate onICEServers:ICEServers];
 | |
|       });
 | |
|   });
 | |
| }
 | |
| 
 | |
| #pragma mark - NSURLConnectionDataDelegate methods
 | |
| 
 | |
| - (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data {
 | |
|   NSString* roomHtml = [NSString stringWithUTF8String:[data bytes]];
 | |
|   [self maybeLogMessage:[NSString stringWithFormat:@"Received %d chars",
 | |
|                                                    [roomHtml length]]];
 | |
|   [self.roomHtml appendString:roomHtml];
 | |
| }
 | |
| 
 | |
| - (void)connection:(NSURLConnection*)connection
 | |
|     didReceiveResponse:(NSURLResponse*)response {
 | |
|   NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
 | |
|   int statusCode = [httpResponse statusCode];
 | |
|   [self
 | |
|       maybeLogMessage:
 | |
|           [NSString stringWithFormat:
 | |
|                         @"Response received\nURL\n%@\nStatus [%d]\nHeaders\n%@",
 | |
|                         [httpResponse URL],
 | |
|                         statusCode,
 | |
|                         [httpResponse allHeaderFields]]];
 | |
|   NSAssert(statusCode == 200, @"Invalid response  of %d received.", statusCode);
 | |
| }
 | |
| 
 | |
| - (void)connectionDidFinishLoading:(NSURLConnection*)connection {
 | |
|   [self maybeLogMessage:[NSString stringWithFormat:@"finished loading %d chars",
 | |
|                                                    [self.roomHtml length]]];
 | |
|   NSRegularExpression* fullRegex =
 | |
|       [NSRegularExpression regularExpressionWithPattern:@"room is full"
 | |
|                                                 options:0
 | |
|                                                   error:nil];
 | |
|   if ([fullRegex
 | |
|           numberOfMatchesInString:self.roomHtml
 | |
|                           options:0
 | |
|                             range:NSMakeRange(0, [self.roomHtml length])]) {
 | |
|     [self showMessage:@"Room full"];
 | |
|     APPRTCAppDelegate* ad =
 | |
|         (APPRTCAppDelegate*)[[UIApplication sharedApplication] delegate];
 | |
|     [ad closeVideoUI];
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   NSString* fullUrl = [[[connection originalRequest] URL] absoluteString];
 | |
|   NSRange queryRange = [fullUrl rangeOfString:@"?"];
 | |
|   self.baseURL = [fullUrl substringToIndex:queryRange.location];
 | |
|   [self maybeLogMessage:[NSString
 | |
|                             stringWithFormat:@"Base URL: %@", self.baseURL]];
 | |
| 
 | |
|   self.initiator = [[self findVar:@"initiator" strippingQuotes:NO] boolValue];
 | |
|   self.token = [self findVar:@"channelToken" strippingQuotes:YES];
 | |
|   if (!self.token)
 | |
|     return;
 | |
|   [self maybeLogMessage:[NSString stringWithFormat:@"Token: %@", self.token]];
 | |
| 
 | |
|   NSString* roomKey = [self findVar:@"roomKey" strippingQuotes:YES];
 | |
|   NSString* me = [self findVar:@"me" strippingQuotes:YES];
 | |
|   if (!roomKey || !me)
 | |
|     return;
 | |
|   self.postMessageUrl =
 | |
|       [NSString stringWithFormat:@"/message?r=%@&u=%@", roomKey, me];
 | |
|   [self maybeLogMessage:[NSString stringWithFormat:@"POST message URL: %@",
 | |
|                                                    self.postMessageUrl]];
 | |
| 
 | |
|   NSString* pcConfig = [self findVar:@"pcConfig" strippingQuotes:NO];
 | |
|   if (!pcConfig)
 | |
|     return;
 | |
|   [self maybeLogMessage:[NSString
 | |
|                             stringWithFormat:@"PC Config JSON: %@", pcConfig]];
 | |
| 
 | |
|   NSString* turnServerUrl = [self findVar:@"turnUrl" strippingQuotes:YES];
 | |
|   if (turnServerUrl) {
 | |
|     [self maybeLogMessage:[NSString
 | |
|                               stringWithFormat:@"TURN server request URL: %@",
 | |
|                                                turnServerUrl]];
 | |
|   }
 | |
| 
 | |
|   NSError* error;
 | |
|   NSData* pcData = [pcConfig dataUsingEncoding:NSUTF8StringEncoding];
 | |
|   NSDictionary* json =
 | |
|       [NSJSONSerialization JSONObjectWithData:pcData options:0 error:&error];
 | |
|   NSAssert(!error, @"Unable to parse.  %@", error.localizedDescription);
 | |
|   NSArray* servers = [json objectForKey:@"iceServers"];
 | |
|   NSMutableArray* ICEServers = [NSMutableArray array];
 | |
|   for (NSDictionary* server in servers) {
 | |
|     NSString* url = [server objectForKey:@"urls"];
 | |
|     NSString* username = json[@"username"];
 | |
|     NSString* credential = [server objectForKey:@"credential"];
 | |
|     if (!username) {
 | |
|       username = @"";
 | |
|     }
 | |
|     if (!credential) {
 | |
|       credential = @"";
 | |
|     }
 | |
|     [self maybeLogMessage:[NSString
 | |
|                               stringWithFormat:@"url [%@] - credential [%@]",
 | |
|                                                url,
 | |
|                                                credential]];
 | |
|     RTCICEServer* ICEServer =
 | |
|         [[RTCICEServer alloc] initWithURI:[NSURL URLWithString:url]
 | |
|                                  username:username
 | |
|                                  password:credential];
 | |
|     NSLog(@"Added ICE Server: %@", ICEServer);
 | |
|     [ICEServers addObject:ICEServer];
 | |
|   }
 | |
|   [self updateICEServers:ICEServers withTurnServer:turnServerUrl];
 | |
| 
 | |
|   NSString* mc = [self findVar:@"mediaConstraints" strippingQuotes:NO];
 | |
|   if (mc) {
 | |
|     error = nil;
 | |
|     NSData* mcData = [mc dataUsingEncoding:NSUTF8StringEncoding];
 | |
|     json =
 | |
|         [NSJSONSerialization JSONObjectWithData:mcData options:0 error:&error];
 | |
|     NSAssert(!error, @"Unable to parse.  %@", error.localizedDescription);
 | |
|     if ([[json objectForKey:@"video"] boolValue]) {
 | |
|       _videoConstraints = [[RTCMediaConstraints alloc] init];
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   [self
 | |
|       maybeLogMessage:[NSString
 | |
|                           stringWithFormat:@"About to open GAE with token:  %@",
 | |
|                                            self.token]];
 | |
|   self.gaeChannel =
 | |
|       [[GAEChannelClient alloc] initWithToken:self.token
 | |
|                                      delegate:self.messageHandler];
 | |
| }
 | |
| 
 | |
| @end
 |