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