blob: 6d0a5a21543594b5f408efcd6c8bb1b982c9b2bc [file] [log] [blame]
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +00001/*
2 * libjingle
3 * Copyright 2014, 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 "APPRTCConnectionManager.h"
29
30#import <AVFoundation/AVFoundation.h>
31#import "APPRTCAppClient.h"
32#import "GAEChannelClient.h"
33#import "RTCICECandidate.h"
34#import "RTCMediaConstraints.h"
35#import "RTCMediaStream.h"
36#import "RTCPair.h"
37#import "RTCPeerConnection.h"
38#import "RTCPeerConnectionDelegate.h"
39#import "RTCPeerConnectionFactory.h"
40#import "RTCSessionDescription.h"
41#import "RTCSessionDescriptionDelegate.h"
42#import "RTCStatsDelegate.h"
43#import "RTCVideoCapturer.h"
44#import "RTCVideoSource.h"
45
46@interface APPRTCConnectionManager ()
47 <APPRTCAppClientDelegate, GAEMessageHandler, RTCPeerConnectionDelegate,
48 RTCSessionDescriptionDelegate, RTCStatsDelegate>
49
50@property(nonatomic, strong) APPRTCAppClient* client;
51@property(nonatomic, strong) RTCPeerConnection* peerConnection;
52@property(nonatomic, strong) RTCPeerConnectionFactory* peerConnectionFactory;
53@property(nonatomic, strong) RTCVideoSource* videoSource;
54@property(nonatomic, strong) NSMutableArray* queuedRemoteCandidates;
55
56@end
57
58@implementation APPRTCConnectionManager {
59 NSTimer* _statsTimer;
60}
61
62- (instancetype)initWithDelegate:(id<APPRTCConnectionManagerDelegate>)delegate
63 logger:(id<APPRTCLogger>)logger {
64 if (self = [super init]) {
65 self.delegate = delegate;
66 self.logger = logger;
67 self.peerConnectionFactory = [[RTCPeerConnectionFactory alloc] init];
68 // TODO(tkchin): turn this into a button.
69 // Uncomment for stat logs.
70 // _statsTimer =
71 // [NSTimer scheduledTimerWithTimeInterval:10
72 // target:self
73 // selector:@selector(didFireStatsTimer:)
74 // userInfo:nil
75 // repeats:YES];
76 }
77 return self;
78}
79
80- (void)dealloc {
81 [self disconnect];
82}
83
84- (BOOL)connectToRoomWithURL:(NSURL*)url {
85 if (self.client) {
86 // Already have a connection.
87 return NO;
88 }
89 self.client = [[APPRTCAppClient alloc] initWithDelegate:self
90 messageHandler:self];
91 [self.client connectToRoom:url];
92 return YES;
93}
94
95- (void)disconnect {
96 if (!self.client) {
97 return;
98 }
99 [self.client
100 sendData:[@"{\"type\": \"bye\"}" dataUsingEncoding:NSUTF8StringEncoding]];
101 [self.peerConnection close];
102 self.peerConnection = nil;
103 self.client = nil;
104 self.queuedRemoteCandidates = nil;
105}
106
107#pragma mark - APPRTCAppClientDelegate
108
109- (void)appClient:(APPRTCAppClient*)appClient
110 didErrorWithMessage:(NSString*)message {
111 [self.delegate connectionManager:self
112 didErrorWithMessage:message];
113}
114
115- (void)appClient:(APPRTCAppClient*)appClient
116 didReceiveICEServers:(NSArray*)servers {
117 self.queuedRemoteCandidates = [NSMutableArray array];
118 RTCMediaConstraints* constraints = [[RTCMediaConstraints alloc]
119 initWithMandatoryConstraints:
120 @[
121 [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" value:@"true"],
122 [[RTCPair alloc] initWithKey:@"OfferToReceiveVideo" value:@"true"]
123 ]
124 optionalConstraints:
125 @[
126 [[RTCPair alloc] initWithKey:@"internalSctpDataChannels"
127 value:@"true"],
128 [[RTCPair alloc] initWithKey:@"DtlsSrtpKeyAgreement"
129 value:@"true"]
130 ]];
131 self.peerConnection =
132 [self.peerConnectionFactory peerConnectionWithICEServers:servers
133 constraints:constraints
134 delegate:self];
135 RTCMediaStream* lms =
136 [self.peerConnectionFactory mediaStreamWithLabel:@"ARDAMS"];
137
138 // The iOS simulator doesn't provide any sort of camera capture
139 // support or emulation (http://goo.gl/rHAnC1) so don't bother
140 // trying to open a local stream.
141 RTCVideoTrack* localVideoTrack;
142
143 // TODO(tkchin): local video capture for OSX. See
144 // https://code.google.com/p/webrtc/issues/detail?id=3417.
145#if !TARGET_IPHONE_SIMULATOR && TARGET_OS_IPHONE
146 NSString* cameraID = nil;
147 for (AVCaptureDevice* captureDevice in
148 [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) {
149 if (captureDevice.position == AVCaptureDevicePositionFront) {
150 cameraID = [captureDevice localizedName];
151 break;
152 }
153 }
154 NSAssert(cameraID, @"Unable to get the front camera id");
155
156 RTCVideoCapturer* capturer =
157 [RTCVideoCapturer capturerWithDeviceName:cameraID];
158 self.videoSource = [self.peerConnectionFactory
159 videoSourceWithCapturer:capturer
160 constraints:self.client.videoConstraints];
161 localVideoTrack =
162 [self.peerConnectionFactory videoTrackWithID:@"ARDAMSv0"
163 source:self.videoSource];
164 if (localVideoTrack) {
165 [lms addVideoTrack:localVideoTrack];
166 }
167 [self.delegate connectionManager:self
168 didReceiveLocalVideoTrack:localVideoTrack];
169#endif
170
171 [lms addAudioTrack:[self.peerConnectionFactory audioTrackWithID:@"ARDAMSa0"]];
172 [self.peerConnection addStream:lms constraints:constraints];
173 [self.logger logMessage:@"onICEServers - added local stream."];
174}
175
176#pragma mark - GAEMessageHandler methods
177
178- (void)onOpen {
179 if (!self.client.initiator) {
180 [self.logger logMessage:@"Callee; waiting for remote offer"];
181 return;
182 }
183 [self.logger logMessage:@"GAE onOpen - create offer."];
184 RTCPair* audio =
185 [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" value:@"true"];
186 RTCPair* video =
187 [[RTCPair alloc] initWithKey:@"OfferToReceiveVideo" value:@"true"];
188 NSArray* mandatory = @[ audio, video ];
189 RTCMediaConstraints* constraints =
190 [[RTCMediaConstraints alloc] initWithMandatoryConstraints:mandatory
191 optionalConstraints:nil];
192 [self.peerConnection createOfferWithDelegate:self constraints:constraints];
193 [self.logger logMessage:@"PC - createOffer."];
194}
195
196- (void)onMessage:(NSDictionary*)messageData {
197 NSString* type = messageData[@"type"];
198 NSAssert(type, @"Missing type: %@", messageData);
199 [self.logger logMessage:[NSString stringWithFormat:@"GAE onMessage type - %@",
200 type]];
201 if ([type isEqualToString:@"candidate"]) {
202 NSString* mid = messageData[@"id"];
203 NSNumber* sdpLineIndex = messageData[@"label"];
204 NSString* sdp = messageData[@"candidate"];
205 RTCICECandidate* candidate =
206 [[RTCICECandidate alloc] initWithMid:mid
207 index:sdpLineIndex.intValue
208 sdp:sdp];
209 if (self.queuedRemoteCandidates) {
210 [self.queuedRemoteCandidates addObject:candidate];
211 } else {
212 [self.peerConnection addICECandidate:candidate];
213 }
214 } else if ([type isEqualToString:@"offer"] ||
215 [type isEqualToString:@"answer"]) {
216 NSString* sdpString = messageData[@"sdp"];
217 RTCSessionDescription* sdp = [[RTCSessionDescription alloc]
218 initWithType:type
219 sdp:[[self class] preferISAC:sdpString]];
220 [self.peerConnection setRemoteDescriptionWithDelegate:self
221 sessionDescription:sdp];
222 [self.logger logMessage:@"PC - setRemoteDescription."];
223 } else if ([type isEqualToString:@"bye"]) {
224 [self.delegate connectionManagerDidReceiveHangup:self];
225 } else {
226 NSAssert(NO, @"Invalid message: %@", messageData);
227 }
228}
229
230- (void)onClose {
231 [self.logger logMessage:@"GAE onClose."];
232 [self.delegate connectionManagerDidReceiveHangup:self];
233}
234
235- (void)onError:(int)code withDescription:(NSString*)description {
236 NSString* message = [NSString stringWithFormat:@"GAE onError: %d, %@",
237 code, description];
238 [self.logger logMessage:message];
239 [self.delegate connectionManager:self
240 didErrorWithMessage:message];
241}
242
243#pragma mark - RTCPeerConnectionDelegate
244
245- (void)peerConnectionOnError:(RTCPeerConnection*)peerConnection {
246 dispatch_async(dispatch_get_main_queue(), ^{
247 NSString* message = @"PeerConnection error";
248 NSLog(@"%@", message);
249 NSAssert(NO, @"PeerConnection failed.");
250 [self.delegate connectionManager:self
251 didErrorWithMessage:message];
252 });
253}
254
255- (void)peerConnection:(RTCPeerConnection*)peerConnection
256 signalingStateChanged:(RTCSignalingState)stateChanged {
257 dispatch_async(dispatch_get_main_queue(), ^{
258 NSLog(@"PCO onSignalingStateChange: %d", stateChanged);
259 });
260}
261
262- (void)peerConnection:(RTCPeerConnection*)peerConnection
263 addedStream:(RTCMediaStream*)stream {
264 dispatch_async(dispatch_get_main_queue(), ^{
265 NSLog(@"PCO onAddStream.");
266 NSAssert([stream.audioTracks count] == 1 || [stream.videoTracks count] == 1,
267 @"Expected audio or video track");
268 NSAssert([stream.audioTracks count] <= 1,
269 @"Expected at most 1 audio stream");
270 NSAssert([stream.videoTracks count] <= 1,
271 @"Expected at most 1 video stream");
272 if ([stream.videoTracks count] != 0) {
273 [self.delegate connectionManager:self
274 didReceiveRemoteVideoTrack:stream.videoTracks[0]];
275 }
276 });
277}
278
279- (void)peerConnection:(RTCPeerConnection*)peerConnection
280 removedStream:(RTCMediaStream*)stream {
281 dispatch_async(dispatch_get_main_queue(),
282 ^{ NSLog(@"PCO onRemoveStream."); });
283}
284
285- (void)peerConnectionOnRenegotiationNeeded:(RTCPeerConnection*)peerConnection {
286 dispatch_async(dispatch_get_main_queue(), ^{
287 NSLog(@"PCO onRenegotiationNeeded - ignoring because AppRTC has a "
288 "predefined negotiation strategy");
289 });
290}
291
292- (void)peerConnection:(RTCPeerConnection*)peerConnection
293 gotICECandidate:(RTCICECandidate*)candidate {
294 dispatch_async(dispatch_get_main_queue(), ^{
295 NSLog(@"PCO onICECandidate.\n Mid[%@] Index[%li] Sdp[%@]",
296 candidate.sdpMid,
297 (long)candidate.sdpMLineIndex,
298 candidate.sdp);
299 NSDictionary* json = @{
300 @"type" : @"candidate",
301 @"label" : @(candidate.sdpMLineIndex),
302 @"id" : candidate.sdpMid,
303 @"candidate" : candidate.sdp
304 };
305 NSError* error;
306 NSData* data =
307 [NSJSONSerialization dataWithJSONObject:json options:0 error:&error];
308 if (!error) {
309 [self.client sendData:data];
310 } else {
311 NSAssert(NO,
312 @"Unable to serialize JSON object with error: %@",
313 error.localizedDescription);
314 }
315 });
316}
317
318- (void)peerConnection:(RTCPeerConnection*)peerConnection
319 iceGatheringChanged:(RTCICEGatheringState)newState {
320 dispatch_async(dispatch_get_main_queue(),
321 ^{ NSLog(@"PCO onIceGatheringChange. %d", newState); });
322}
323
324- (void)peerConnection:(RTCPeerConnection*)peerConnection
325 iceConnectionChanged:(RTCICEConnectionState)newState {
326 dispatch_async(dispatch_get_main_queue(), ^{
327 NSLog(@"PCO onIceConnectionChange. %d", newState);
328 if (newState == RTCICEConnectionConnected)
329 [self.logger logMessage:@"ICE Connection Connected."];
330 NSAssert(newState != RTCICEConnectionFailed, @"ICE Connection failed!");
331 });
332}
333
334- (void)peerConnection:(RTCPeerConnection*)peerConnection
335 didOpenDataChannel:(RTCDataChannel*)dataChannel {
336 NSAssert(NO, @"AppRTC doesn't use DataChannels");
337}
338
339#pragma mark - RTCSessionDescriptionDelegate
340
341- (void)peerConnection:(RTCPeerConnection*)peerConnection
342 didCreateSessionDescription:(RTCSessionDescription*)origSdp
343 error:(NSError*)error {
344 dispatch_async(dispatch_get_main_queue(), ^{
345 if (error) {
346 [self.logger logMessage:@"SDP onFailure."];
347 NSAssert(NO, error.description);
348 return;
349 }
350 [self.logger logMessage:@"SDP onSuccess(SDP) - set local description."];
351 RTCSessionDescription* sdp = [[RTCSessionDescription alloc]
352 initWithType:origSdp.type
353 sdp:[[self class] preferISAC:origSdp.description]];
354 [self.peerConnection setLocalDescriptionWithDelegate:self
355 sessionDescription:sdp];
356 [self.logger logMessage:@"PC setLocalDescription."];
357 NSDictionary* json = @{@"type" : sdp.type, @"sdp" : sdp.description};
358 NSError* jsonError;
359 NSData* data = [NSJSONSerialization dataWithJSONObject:json
360 options:0
361 error:&jsonError];
362 NSAssert(!jsonError, @"Error: %@", jsonError.description);
363 [self.client sendData:data];
364 });
365}
366
367- (void)peerConnection:(RTCPeerConnection*)peerConnection
368 didSetSessionDescriptionWithError:(NSError*)error {
369 dispatch_async(dispatch_get_main_queue(), ^{
370 if (error) {
371 [self.logger logMessage:@"SDP onFailure."];
372 NSAssert(NO, error.description);
373 return;
374 }
375 [self.logger logMessage:@"SDP onSuccess() - possibly drain candidates"];
376 if (!self.client.initiator) {
377 if (self.peerConnection.remoteDescription &&
378 !self.peerConnection.localDescription) {
379 [self.logger logMessage:@"Callee, setRemoteDescription succeeded"];
380 RTCPair* audio = [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio"
381 value:@"true"];
382 RTCPair* video = [[RTCPair alloc] initWithKey:@"OfferToReceiveVideo"
383 value:@"true"];
384 NSArray* mandatory = @[ audio, video ];
385 RTCMediaConstraints* constraints = [[RTCMediaConstraints alloc]
386 initWithMandatoryConstraints:mandatory
387 optionalConstraints:nil];
388 [self.peerConnection createAnswerWithDelegate:self
389 constraints:constraints];
390 [self.logger logMessage:@"PC - createAnswer."];
391 } else {
392 [self.logger logMessage:@"SDP onSuccess - drain candidates"];
393 [self drainRemoteCandidates];
394 }
395 } else {
396 if (self.peerConnection.remoteDescription) {
397 [self.logger logMessage:@"SDP onSuccess - drain candidates"];
398 [self drainRemoteCandidates];
399 }
400 }
401 });
402}
403
404#pragma mark - RTCStatsDelegate methods
405
406- (void)peerConnection:(RTCPeerConnection*)peerConnection
407 didGetStats:(NSArray*)stats {
408 dispatch_async(dispatch_get_main_queue(), ^{
409 NSString* message = [NSString stringWithFormat:@"Stats:\n %@", stats];
410 [self.logger logMessage:message];
411 });
412}
413
414#pragma mark - Private
415
416// Match |pattern| to |string| and return the first group of the first
417// match, or nil if no match was found.
418+ (NSString*)firstMatch:(NSRegularExpression*)pattern
419 withString:(NSString*)string {
420 NSTextCheckingResult* result =
421 [pattern firstMatchInString:string
422 options:0
423 range:NSMakeRange(0, [string length])];
424 if (!result)
425 return nil;
426 return [string substringWithRange:[result rangeAtIndex:1]];
427}
428
429// Mangle |origSDP| to prefer the ISAC/16k audio codec.
430+ (NSString*)preferISAC:(NSString*)origSDP {
431 int mLineIndex = -1;
432 NSString* isac16kRtpMap = nil;
433 NSArray* lines = [origSDP componentsSeparatedByString:@"\n"];
434 NSRegularExpression* isac16kRegex = [NSRegularExpression
435 regularExpressionWithPattern:@"^a=rtpmap:(\\d+) ISAC/16000[\r]?$"
436 options:0
437 error:nil];
438 for (int i = 0;
439 (i < [lines count]) && (mLineIndex == -1 || isac16kRtpMap == nil);
440 ++i) {
441 NSString* line = [lines objectAtIndex:i];
442 if ([line hasPrefix:@"m=audio "]) {
443 mLineIndex = i;
444 continue;
445 }
446 isac16kRtpMap = [self firstMatch:isac16kRegex withString:line];
447 }
448 if (mLineIndex == -1) {
449 NSLog(@"No m=audio line, so can't prefer iSAC");
450 return origSDP;
451 }
452 if (isac16kRtpMap == nil) {
453 NSLog(@"No ISAC/16000 line, so can't prefer iSAC");
454 return origSDP;
455 }
456 NSArray* origMLineParts =
457 [[lines objectAtIndex:mLineIndex] componentsSeparatedByString:@" "];
458 NSMutableArray* newMLine =
459 [NSMutableArray arrayWithCapacity:[origMLineParts count]];
460 int origPartIndex = 0;
461 // Format is: m=<media> <port> <proto> <fmt> ...
462 [newMLine addObject:[origMLineParts objectAtIndex:origPartIndex++]];
463 [newMLine addObject:[origMLineParts objectAtIndex:origPartIndex++]];
464 [newMLine addObject:[origMLineParts objectAtIndex:origPartIndex++]];
465 [newMLine addObject:isac16kRtpMap];
466 for (; origPartIndex < [origMLineParts count]; ++origPartIndex) {
467 if (![isac16kRtpMap
468 isEqualToString:[origMLineParts objectAtIndex:origPartIndex]]) {
469 [newMLine addObject:[origMLineParts objectAtIndex:origPartIndex]];
470 }
471 }
472 NSMutableArray* newLines = [NSMutableArray arrayWithCapacity:[lines count]];
473 [newLines addObjectsFromArray:lines];
474 [newLines replaceObjectAtIndex:mLineIndex
475 withObject:[newMLine componentsJoinedByString:@" "]];
476 return [newLines componentsJoinedByString:@"\n"];
477}
478
479- (void)drainRemoteCandidates {
480 for (RTCICECandidate* candidate in self.queuedRemoteCandidates) {
481 [self.peerConnection addICECandidate:candidate];
482 }
483 self.queuedRemoteCandidates = nil;
484}
485
486- (void)didFireStatsTimer:(NSTimer*)timer {
487 if (self.peerConnection) {
488 [self.peerConnection getStatsWithDelegate:self
489 mediaStreamTrack:nil
490 statsOutputLevel:RTCStatsOutputLevelDebug];
491 }
492}
493
494@end