blob: 0c429a078007982db48d9753f575fdbe9f7c4c19 [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"
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 onSignalingStateChange:(RTCSignalingState)stateChanged {
65 NSLog(@"PCO onSignalingStateChange.");
66}
67
68- (void)peerConnection:(RTCPeerConnection *)peerConnection
69 onAddStream:(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 onRemoveStream:(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 onIceCandidate:(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 onIceGatheringChange:(RTCIceGatheringState)newState {
116 NSLog(@"PCO onIceGatheringChange. %d", newState);
117}
118
119- (void)peerConnection:(RTCPeerConnection *)peerConnection
120 onIceConnectionChange:(RTCIceConnectionState)newState {
121 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 {
142 self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
143 self.viewController =
144 [[APPRTCViewController alloc] initWithNibName:@"RTCViewController"
145 bundle:nil];
146 self.window.rootViewController = self.viewController;
147 [self.window makeKeyAndVisible];
148 return YES;
149}
150
151- (void)applicationWillResignActive:(UIApplication *)application {
152 [self displayLogMessage:@"Application lost focus, connection broken."];
153 [self disconnect];
154 [self.viewController resetUI];
155}
156
157- (void)applicationDidEnterBackground:(UIApplication *)application {
158}
159
160- (void)applicationWillEnterForeground:(UIApplication *)application {
161}
162
163- (void)applicationDidBecomeActive:(UIApplication *)application {
164}
165
166- (void)applicationWillTerminate:(UIApplication *)application {
167}
168
169- (BOOL)application:(UIApplication *)application
170 openURL:(NSURL *)url
171 sourceApplication:(NSString *)sourceApplication
172 annotation:(id)annotation {
173 if (self.client) {
174 return NO;
175 }
176 self.client = [[APPRTCAppClient alloc] init];
177 self.client.iceServerDelegate = self;
178 self.client.messageHandler = self;
179 [self.client connectToRoom:url];
180 return YES;
181}
182
183- (void)displayLogMessage:(NSString *)message {
184 NSLog(@"%@", message);
185 [self.viewController displayText:message];
186}
187
188#pragma mark - RTCSendMessage method
189
190- (void)sendData:(NSData *)data {
191 [self.client sendData:data];
192}
193
194#pragma mark - IceServerDelegate method
195
196- (void)onIceServers:(NSArray *)servers {
197 self.queuedRemoteCandidates = [NSMutableArray array];
198 self.peerConnectionFactory = [[RTCPeerConnectionFactory alloc] init];
199 RTCMediaConstraints *constraints = [[RTCMediaConstraints alloc] init];
200 self.pcObserver = [[PCObserver alloc] initWithDelegate:self];
201 self.peerConnection =
202 [self.peerConnectionFactory peerConnectionWithIceServers:servers
203 constraints:constraints
204 delegate:self.pcObserver];
205 RTCMediaStream *lms =
206 [self.peerConnectionFactory mediaStreamWithLabel:@"ARDAMS"];
207 // TODO(hughv): Add video.
208 [lms addAudioTrack:[self.peerConnectionFactory audioTrackWithId:@"ARDAMSa0"]];
209 [self.peerConnection addStream:lms withConstraints:constraints];
210 [self displayLogMessage:@"onIceServers - add local stream."];
211}
212
213#pragma mark - GAEMessageHandler methods
214
215- (void)onOpen {
216 [self displayLogMessage:@"GAE onOpen - create offer."];
217 RTCPair *audio =
218 [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" value:@"true"];
219 // TODO(hughv): Add video.
220 // RTCPair *video = [[RTCPair alloc] initWithKey:@"OfferToReceiveVideo"
221 // value:@"true"];
222 NSArray *mandatory = @[ audio /*, video*/ ];
223 RTCMediaConstraints *constraints =
224 [[RTCMediaConstraints alloc] initWithMandatoryConstraints:mandatory
225 optionalConstraints:nil];
226 [self.peerConnection createOfferWithDelegate:self constraints:constraints];
227 [self displayLogMessage:@"PC - createOffer."];
228}
229
230- (void)onMessage:(NSString *)data {
231 NSString *message = [self unHTMLifyString:data];
232 NSError *error;
233 NSDictionary *objects = [NSJSONSerialization
234 JSONObjectWithData:[message dataUsingEncoding:NSUTF8StringEncoding]
235 options:0
236 error:&error];
237 NSAssert(!error,
238 @"%@",
239 [NSString stringWithFormat:@"Error: %@", error.description]);
240 NSAssert([objects count] > 0, @"Invalid JSON object");
241 NSString *value = [objects objectForKey:@"type"];
242 [self displayLogMessage:
243 [NSString stringWithFormat:@"GAE onMessage type - %@", value]];
244 if ([value compare:@"candidate"] == NSOrderedSame) {
245 NSString *mid = [objects objectForKey:@"id"];
246 NSNumber *sdpLineIndex = [objects objectForKey:@"label"];
247 NSString *sdp = [objects objectForKey:@"candidate"];
248 RTCIceCandidate *candidate =
249 [[RTCIceCandidate alloc] initWithMid:mid
250 index:sdpLineIndex.intValue
251 sdp:sdp];
252 if (self.queuedRemoteCandidates) {
253 [self.queuedRemoteCandidates addObject:candidate];
254 } else {
255 [self.peerConnection addIceCandidate:candidate];
256 }
257 } else if (([value compare:@"offer"] == NSOrderedSame) ||
258 ([value compare:@"answer"] == NSOrderedSame)) {
259 NSString *sdpString = [objects objectForKey:@"sdp"];
260 RTCSessionDescription *sdp =
261 [[RTCSessionDescription alloc] initWithType:value sdp:sdpString];
262 [self.peerConnection setRemoteDescriptionWithDelegate:self
263 sessionDescription:sdp];
264 [self displayLogMessage:@"PC - setRemoteDescription."];
265 } else if ([value compare:@"bye"] == NSOrderedSame) {
266 [self disconnect];
267 } else {
268 NSAssert(NO, @"Invalid message: %@", data);
269 }
270}
271
272- (void)onClose {
273 [self displayLogMessage:@"GAE onClose."];
274 [self disconnect];
275}
276
277- (void)onError:(int)code withDescription:(NSString *)description {
278 [self displayLogMessage:
279 [NSString stringWithFormat:@"GAE onError: %@", description]];
280 [self disconnect];
281}
282
283#pragma mark - RTCSessionDescriptonDelegate methods
284
285- (void)peerConnection:(RTCPeerConnection *)peerConnection
286 createSessionDescriptionCompleted:(RTCSessionDescription *)sdp
287 withError:(NSError *)error {
288 if (error) {
289 [self displayLogMessage:@"SDP onFailure."];
290 NSAssert(NO, error.description);
291 return;
292 }
293
294 [self displayLogMessage:@"SDP onSuccess(SDP) - set local description."];
295 [self.peerConnection setLocalDescriptionWithDelegate:self
296 sessionDescription:sdp];
297 [self displayLogMessage:@"PC setLocalDescription."];
298 dispatch_async(dispatch_get_main_queue(), ^(void) {
299 NSDictionary *json = @{ @"type" : sdp.type, @"sdp" : sdp.description };
300 NSError *error;
301 NSData *data =
302 [NSJSONSerialization dataWithJSONObject:json options:0 error:&error];
303 NSAssert(!error,
304 @"%@",
305 [NSString stringWithFormat:@"Error: %@", error.description]);
306 [self sendData:data];
307 });
308}
309
310- (void)peerConnection:(RTCPeerConnection *)peerConnection
311 setSessionDescriptionCompletedWithError:(NSError *)error {
312 if (error) {
313 [self displayLogMessage:@"SDP onFailure."];
314 NSAssert(NO, error.description);
315 return;
316 }
317
318 [self displayLogMessage:@"SDP onSuccess() - possibly drain candidates"];
319 dispatch_async(dispatch_get_main_queue(), ^(void) {
320 // TODO(hughv): Handle non-initiator case. http://s10/46622051
321 if (self.peerConnection.remoteDescription) {
322 [self displayLogMessage:@"SDP onSuccess - drain candidates"];
323 [self drainRemoteCandidates];
324 }
325 });
326}
327
328#pragma mark - internal methods
329
330- (void)disconnect {
331 [self.client
332 sendData:[@"{\"type\": \"bye\"}" dataUsingEncoding:NSUTF8StringEncoding]];
333 self.peerConnection = nil;
334 self.peerConnectionFactory = nil;
335 self.pcObserver = nil;
336 self.client.iceServerDelegate = nil;
337 self.client.messageHandler = nil;
338 self.client = nil;
339}
340
341- (void)drainRemoteCandidates {
342 for (RTCIceCandidate *candidate in self.queuedRemoteCandidates) {
343 [self.peerConnection addIceCandidate:candidate];
344 }
345 self.queuedRemoteCandidates = nil;
346}
347
348- (NSString *)unHTMLifyString:(NSString *)base {
349 // TODO(hughv): Investigate why percent escapes are being added. Removing
350 // them isn't necessary on Android.
351 // convert HTML escaped characters to UTF8.
352 NSString *removePercent =
353 [base stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
354 // remove leading and trailing ".
355 NSRange range;
356 range.length = [removePercent length] - 2;
357 range.location = 1;
358 NSString *removeQuotes = [removePercent substringWithRange:range];
359 // convert \" to ".
360 NSString *removeEscapedQuotes =
361 [removeQuotes stringByReplacingOccurrencesOfString:@"\\\""
362 withString:@"\""];
363 // convert \\ to \.
364 NSString *removeBackslash =
365 [removeEscapedQuotes stringByReplacingOccurrencesOfString:@"\\\\"
366 withString:@"\\"];
367 return removeBackslash;
368}
369
370@end