blob: 9134e4dcb9fd09355b9fb0894f00b9308cdebe90 [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"
tkchin@webrtc.org3e9ad262014-11-27 00:52:38 +000034#import "RTCICECandidate+JSON.h"
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000035#import "RTCMediaConstraints.h"
36#import "RTCMediaStream.h"
37#import "RTCPair.h"
38#import "RTCPeerConnection.h"
39#import "RTCPeerConnectionDelegate.h"
40#import "RTCPeerConnectionFactory.h"
41#import "RTCSessionDescription.h"
tkchin@webrtc.org3e9ad262014-11-27 00:52:38 +000042#import "RTCSessionDescription+JSON.h"
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000043#import "RTCSessionDescriptionDelegate.h"
44#import "RTCStatsDelegate.h"
45#import "RTCVideoCapturer.h"
46#import "RTCVideoSource.h"
47
48@interface APPRTCConnectionManager ()
49 <APPRTCAppClientDelegate, GAEMessageHandler, RTCPeerConnectionDelegate,
50 RTCSessionDescriptionDelegate, RTCStatsDelegate>
51
52@property(nonatomic, strong) APPRTCAppClient* client;
53@property(nonatomic, strong) RTCPeerConnection* peerConnection;
54@property(nonatomic, strong) RTCPeerConnectionFactory* peerConnectionFactory;
55@property(nonatomic, strong) RTCVideoSource* videoSource;
56@property(nonatomic, strong) NSMutableArray* queuedRemoteCandidates;
57
58@end
59
60@implementation APPRTCConnectionManager {
61 NSTimer* _statsTimer;
62}
63
64- (instancetype)initWithDelegate:(id<APPRTCConnectionManagerDelegate>)delegate
65 logger:(id<APPRTCLogger>)logger {
66 if (self = [super init]) {
67 self.delegate = delegate;
68 self.logger = logger;
69 self.peerConnectionFactory = [[RTCPeerConnectionFactory alloc] init];
70 // TODO(tkchin): turn this into a button.
71 // Uncomment for stat logs.
72 // _statsTimer =
73 // [NSTimer scheduledTimerWithTimeInterval:10
74 // target:self
75 // selector:@selector(didFireStatsTimer:)
76 // userInfo:nil
77 // repeats:YES];
78 }
79 return self;
80}
81
82- (void)dealloc {
83 [self disconnect];
84}
85
86- (BOOL)connectToRoomWithURL:(NSURL*)url {
87 if (self.client) {
88 // Already have a connection.
89 return NO;
90 }
91 self.client = [[APPRTCAppClient alloc] initWithDelegate:self
92 messageHandler:self];
93 [self.client connectToRoom:url];
94 return YES;
95}
96
97- (void)disconnect {
98 if (!self.client) {
99 return;
100 }
101 [self.client
102 sendData:[@"{\"type\": \"bye\"}" dataUsingEncoding:NSUTF8StringEncoding]];
103 [self.peerConnection close];
104 self.peerConnection = nil;
105 self.client = nil;
tkchin@webrtc.org738df892014-06-04 20:19:39 +0000106 self.videoSource = nil;
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000107 self.queuedRemoteCandidates = nil;
108}
109
110#pragma mark - APPRTCAppClientDelegate
111
112- (void)appClient:(APPRTCAppClient*)appClient
113 didErrorWithMessage:(NSString*)message {
114 [self.delegate connectionManager:self
115 didErrorWithMessage:message];
116}
117
118- (void)appClient:(APPRTCAppClient*)appClient
119 didReceiveICEServers:(NSArray*)servers {
120 self.queuedRemoteCandidates = [NSMutableArray array];
121 RTCMediaConstraints* constraints = [[RTCMediaConstraints alloc]
122 initWithMandatoryConstraints:
123 @[
124 [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" value:@"true"],
125 [[RTCPair alloc] initWithKey:@"OfferToReceiveVideo" value:@"true"]
126 ]
127 optionalConstraints:
128 @[
129 [[RTCPair alloc] initWithKey:@"internalSctpDataChannels"
130 value:@"true"],
131 [[RTCPair alloc] initWithKey:@"DtlsSrtpKeyAgreement"
132 value:@"true"]
133 ]];
134 self.peerConnection =
135 [self.peerConnectionFactory peerConnectionWithICEServers:servers
136 constraints:constraints
137 delegate:self];
138 RTCMediaStream* lms =
139 [self.peerConnectionFactory mediaStreamWithLabel:@"ARDAMS"];
140
141 // The iOS simulator doesn't provide any sort of camera capture
142 // support or emulation (http://goo.gl/rHAnC1) so don't bother
143 // trying to open a local stream.
144 RTCVideoTrack* localVideoTrack;
145
146 // TODO(tkchin): local video capture for OSX. See
147 // https://code.google.com/p/webrtc/issues/detail?id=3417.
148#if !TARGET_IPHONE_SIMULATOR && TARGET_OS_IPHONE
149 NSString* cameraID = nil;
150 for (AVCaptureDevice* captureDevice in
151 [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) {
152 if (captureDevice.position == AVCaptureDevicePositionFront) {
153 cameraID = [captureDevice localizedName];
154 break;
155 }
156 }
157 NSAssert(cameraID, @"Unable to get the front camera id");
158
159 RTCVideoCapturer* capturer =
160 [RTCVideoCapturer capturerWithDeviceName:cameraID];
161 self.videoSource = [self.peerConnectionFactory
162 videoSourceWithCapturer:capturer
tkchin@webrtc.org3e9ad262014-11-27 00:52:38 +0000163 constraints:self.client.params.mediaConstraints];
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000164 localVideoTrack =
165 [self.peerConnectionFactory videoTrackWithID:@"ARDAMSv0"
166 source:self.videoSource];
167 if (localVideoTrack) {
168 [lms addVideoTrack:localVideoTrack];
169 }
170 [self.delegate connectionManager:self
171 didReceiveLocalVideoTrack:localVideoTrack];
172#endif
173
174 [lms addAudioTrack:[self.peerConnectionFactory audioTrackWithID:@"ARDAMSa0"]];
perkj@webrtc.orgc2dd5ee2014-11-04 11:31:29 +0000175 [self.peerConnection addStream:lms];
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000176 [self.logger logMessage:@"onICEServers - added local stream."];
177}
178
179#pragma mark - GAEMessageHandler methods
180
181- (void)onOpen {
tkchin@webrtc.org3e9ad262014-11-27 00:52:38 +0000182 if (!self.client.params.isInitiator) {
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000183 [self.logger logMessage:@"Callee; waiting for remote offer"];
184 return;
185 }
186 [self.logger logMessage:@"GAE onOpen - create offer."];
187 RTCPair* audio =
188 [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" value:@"true"];
189 RTCPair* video =
190 [[RTCPair alloc] initWithKey:@"OfferToReceiveVideo" value:@"true"];
191 NSArray* mandatory = @[ audio, video ];
192 RTCMediaConstraints* constraints =
193 [[RTCMediaConstraints alloc] initWithMandatoryConstraints:mandatory
194 optionalConstraints:nil];
195 [self.peerConnection createOfferWithDelegate:self constraints:constraints];
196 [self.logger logMessage:@"PC - createOffer."];
197}
198
199- (void)onMessage:(NSDictionary*)messageData {
200 NSString* type = messageData[@"type"];
201 NSAssert(type, @"Missing type: %@", messageData);
202 [self.logger logMessage:[NSString stringWithFormat:@"GAE onMessage type - %@",
203 type]];
204 if ([type isEqualToString:@"candidate"]) {
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000205 RTCICECandidate* candidate =
tkchin@webrtc.org3e9ad262014-11-27 00:52:38 +0000206 [RTCICECandidate candidateFromJSONDictionary:messageData];
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000207 if (self.queuedRemoteCandidates) {
208 [self.queuedRemoteCandidates addObject:candidate];
209 } else {
210 [self.peerConnection addICECandidate:candidate];
211 }
212 } else if ([type isEqualToString:@"offer"] ||
213 [type isEqualToString:@"answer"]) {
tkchin@webrtc.org3e9ad262014-11-27 00:52:38 +0000214 RTCSessionDescription* sdp =
215 [RTCSessionDescription descriptionFromJSONDictionary:messageData];
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000216 [self.peerConnection setRemoteDescriptionWithDelegate:self
217 sessionDescription:sdp];
218 [self.logger logMessage:@"PC - setRemoteDescription."];
219 } else if ([type isEqualToString:@"bye"]) {
220 [self.delegate connectionManagerDidReceiveHangup:self];
221 } else {
222 NSAssert(NO, @"Invalid message: %@", messageData);
223 }
224}
225
226- (void)onClose {
227 [self.logger logMessage:@"GAE onClose."];
228 [self.delegate connectionManagerDidReceiveHangup:self];
229}
230
231- (void)onError:(int)code withDescription:(NSString*)description {
232 NSString* message = [NSString stringWithFormat:@"GAE onError: %d, %@",
233 code, description];
234 [self.logger logMessage:message];
235 [self.delegate connectionManager:self
236 didErrorWithMessage:message];
237}
238
239#pragma mark - RTCPeerConnectionDelegate
240
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000241- (void)peerConnection:(RTCPeerConnection*)peerConnection
242 signalingStateChanged:(RTCSignalingState)stateChanged {
243 dispatch_async(dispatch_get_main_queue(), ^{
244 NSLog(@"PCO onSignalingStateChange: %d", stateChanged);
245 });
246}
247
248- (void)peerConnection:(RTCPeerConnection*)peerConnection
249 addedStream:(RTCMediaStream*)stream {
250 dispatch_async(dispatch_get_main_queue(), ^{
251 NSLog(@"PCO onAddStream.");
252 NSAssert([stream.audioTracks count] == 1 || [stream.videoTracks count] == 1,
253 @"Expected audio or video track");
254 NSAssert([stream.audioTracks count] <= 1,
255 @"Expected at most 1 audio stream");
256 NSAssert([stream.videoTracks count] <= 1,
257 @"Expected at most 1 video stream");
258 if ([stream.videoTracks count] != 0) {
259 [self.delegate connectionManager:self
260 didReceiveRemoteVideoTrack:stream.videoTracks[0]];
261 }
262 });
263}
264
265- (void)peerConnection:(RTCPeerConnection*)peerConnection
266 removedStream:(RTCMediaStream*)stream {
267 dispatch_async(dispatch_get_main_queue(),
268 ^{ NSLog(@"PCO onRemoveStream."); });
269}
270
271- (void)peerConnectionOnRenegotiationNeeded:(RTCPeerConnection*)peerConnection {
272 dispatch_async(dispatch_get_main_queue(), ^{
273 NSLog(@"PCO onRenegotiationNeeded - ignoring because AppRTC has a "
274 "predefined negotiation strategy");
275 });
276}
277
278- (void)peerConnection:(RTCPeerConnection*)peerConnection
279 gotICECandidate:(RTCICECandidate*)candidate {
280 dispatch_async(dispatch_get_main_queue(), ^{
tkchin@webrtc.org3e9ad262014-11-27 00:52:38 +0000281 NSLog(@"PCO onICECandidate.\n%@", candidate);
282 [self.client sendData:[candidate JSONData]];
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000283 });
284}
285
286- (void)peerConnection:(RTCPeerConnection*)peerConnection
287 iceGatheringChanged:(RTCICEGatheringState)newState {
288 dispatch_async(dispatch_get_main_queue(),
289 ^{ NSLog(@"PCO onIceGatheringChange. %d", newState); });
290}
291
292- (void)peerConnection:(RTCPeerConnection*)peerConnection
293 iceConnectionChanged:(RTCICEConnectionState)newState {
294 dispatch_async(dispatch_get_main_queue(), ^{
295 NSLog(@"PCO onIceConnectionChange. %d", newState);
296 if (newState == RTCICEConnectionConnected)
297 [self.logger logMessage:@"ICE Connection Connected."];
298 NSAssert(newState != RTCICEConnectionFailed, @"ICE Connection failed!");
299 });
300}
301
302- (void)peerConnection:(RTCPeerConnection*)peerConnection
303 didOpenDataChannel:(RTCDataChannel*)dataChannel {
304 NSAssert(NO, @"AppRTC doesn't use DataChannels");
305}
306
307#pragma mark - RTCSessionDescriptionDelegate
308
309- (void)peerConnection:(RTCPeerConnection*)peerConnection
tkchin@webrtc.org3e9ad262014-11-27 00:52:38 +0000310 didCreateSessionDescription:(RTCSessionDescription*)sdp
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000311 error:(NSError*)error {
312 dispatch_async(dispatch_get_main_queue(), ^{
313 if (error) {
314 [self.logger logMessage:@"SDP onFailure."];
315 NSAssert(NO, error.description);
316 return;
317 }
318 [self.logger logMessage:@"SDP onSuccess(SDP) - set local description."];
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000319 [self.peerConnection setLocalDescriptionWithDelegate:self
320 sessionDescription:sdp];
321 [self.logger logMessage:@"PC setLocalDescription."];
tkchin@webrtc.org3e9ad262014-11-27 00:52:38 +0000322 [self.client sendData:[sdp JSONData]];
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000323 });
324}
325
326- (void)peerConnection:(RTCPeerConnection*)peerConnection
327 didSetSessionDescriptionWithError:(NSError*)error {
328 dispatch_async(dispatch_get_main_queue(), ^{
329 if (error) {
330 [self.logger logMessage:@"SDP onFailure."];
331 NSAssert(NO, error.description);
332 return;
333 }
334 [self.logger logMessage:@"SDP onSuccess() - possibly drain candidates"];
tkchin@webrtc.org3e9ad262014-11-27 00:52:38 +0000335 if (!self.client.params.isInitiator) {
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000336 if (self.peerConnection.remoteDescription &&
337 !self.peerConnection.localDescription) {
338 [self.logger logMessage:@"Callee, setRemoteDescription succeeded"];
339 RTCPair* audio = [[RTCPair alloc] initWithKey:@"OfferToReceiveAudio"
340 value:@"true"];
341 RTCPair* video = [[RTCPair alloc] initWithKey:@"OfferToReceiveVideo"
342 value:@"true"];
343 NSArray* mandatory = @[ audio, video ];
344 RTCMediaConstraints* constraints = [[RTCMediaConstraints alloc]
345 initWithMandatoryConstraints:mandatory
346 optionalConstraints:nil];
347 [self.peerConnection createAnswerWithDelegate:self
348 constraints:constraints];
349 [self.logger logMessage:@"PC - createAnswer."];
350 } else {
351 [self.logger logMessage:@"SDP onSuccess - drain candidates"];
352 [self drainRemoteCandidates];
353 }
354 } else {
355 if (self.peerConnection.remoteDescription) {
356 [self.logger logMessage:@"SDP onSuccess - drain candidates"];
357 [self drainRemoteCandidates];
358 }
359 }
360 });
361}
362
363#pragma mark - RTCStatsDelegate methods
364
365- (void)peerConnection:(RTCPeerConnection*)peerConnection
366 didGetStats:(NSArray*)stats {
367 dispatch_async(dispatch_get_main_queue(), ^{
368 NSString* message = [NSString stringWithFormat:@"Stats:\n %@", stats];
369 [self.logger logMessage:message];
370 });
371}
372
373#pragma mark - Private
374
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000375- (void)drainRemoteCandidates {
376 for (RTCICECandidate* candidate in self.queuedRemoteCandidates) {
377 [self.peerConnection addICECandidate:candidate];
378 }
379 self.queuedRemoteCandidates = nil;
380}
381
382- (void)didFireStatsTimer:(NSTimer*)timer {
383 if (self.peerConnection) {
384 [self.peerConnection getStatsWithDelegate:self
385 mediaStreamTrack:nil
386 statsOutputLevel:RTCStatsOutputLevelDebug];
387 }
388}
389
390@end