APPRTCViewController.m (13529B)
1 /* 2 * Copyright 2014 The WebRTC Project Authors. All rights reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 #import "APPRTCViewController.h" 12 13 #import <AVFoundation/AVFoundation.h> 14 15 #import "sdk/objc/api/peerconnection/RTCVideoTrack.h" 16 #import "sdk/objc/components/renderer/metal/RTCMTLNSVideoView.h" 17 18 #import "ARDAppClient.h" 19 #import "ARDCaptureController.h" 20 #import "ARDSettingsModel.h" 21 22 static NSUInteger const kContentWidth = 900; 23 static NSUInteger const kRoomFieldWidth = 200; 24 static NSUInteger const kActionItemHeight = 30; 25 static NSUInteger const kBottomViewHeight = 200; 26 27 @class APPRTCMainView; 28 @protocol APPRTCMainViewDelegate 29 30 - (void)appRTCMainView:(APPRTCMainView*)mainView 31 didEnterRoomId:(NSString*)roomId 32 loopback:(BOOL)isLoopback; 33 34 @end 35 36 @interface APPRTCMainView : NSView 37 38 @property(nonatomic, weak) id<APPRTCMainViewDelegate> delegate; 39 @property(nonatomic, readonly) 40 NSView<RTC_OBJC_TYPE(RTCVideoRenderer)>* localVideoView; 41 @property(nonatomic, readonly) 42 NSView<RTC_OBJC_TYPE(RTCVideoRenderer)>* remoteVideoView; 43 @property(nonatomic, readonly) NSTextView* logView; 44 45 - (void)displayLogMessage:(NSString*)message; 46 47 @end 48 49 @interface APPRTCMainView () <NSTextFieldDelegate, 50 RTC_OBJC_TYPE (RTCVideoViewDelegate)> 51 @end 52 @implementation APPRTCMainView { 53 NSScrollView* _scrollView; 54 NSView* _actionItemsView; 55 NSButton* _connectButton; 56 NSButton* _loopbackButton; 57 NSTextField* _roomField; 58 CGSize _localVideoSize; 59 CGSize _remoteVideoSize; 60 } 61 62 @synthesize delegate = _delegate; 63 @synthesize localVideoView = _localVideoView; 64 @synthesize remoteVideoView = _remoteVideoView; 65 @synthesize logView = _logView; 66 67 - (void)displayLogMessage:(NSString*)message { 68 dispatch_async(dispatch_get_main_queue(), ^{ 69 self.logView.string = 70 [NSString stringWithFormat:@"%@%@\n", self.logView.string, message]; 71 NSRange range = NSMakeRange(self.logView.string.length, 0); 72 [self.logView scrollRangeToVisible:range]; 73 }); 74 } 75 76 #pragma mark - Private 77 78 - (instancetype)initWithFrame:(NSRect)frame { 79 self = [super initWithFrame:frame]; 80 if (self) { 81 [self setupViews]; 82 } 83 return self; 84 } 85 86 + (BOOL)requiresConstraintBasedLayout { 87 return YES; 88 } 89 90 - (void)updateConstraints { 91 NSParameterAssert(_roomField != nil && _scrollView != nil && 92 _remoteVideoView != nil && _localVideoView != nil && 93 _actionItemsView != nil && _connectButton != nil && 94 _loopbackButton != nil); 95 96 [self removeConstraints:[self constraints]]; 97 NSDictionary* viewsDictionary = 98 NSDictionaryOfVariableBindings(_roomField, 99 _scrollView, 100 _remoteVideoView, 101 _localVideoView, 102 _actionItemsView, 103 _connectButton, 104 _loopbackButton); 105 106 NSSize remoteViewSize = [self remoteVideoViewSize]; 107 NSDictionary* metrics = @{ 108 @"remoteViewWidth" : @(remoteViewSize.width), 109 @"remoteViewHeight" : @(remoteViewSize.height), 110 @"kBottomViewHeight" : @(kBottomViewHeight), 111 @"localViewHeight" : @(remoteViewSize.height / 3), 112 @"localViewWidth" : @(remoteViewSize.width / 3), 113 @"kRoomFieldWidth" : @(kRoomFieldWidth), 114 @"kActionItemHeight" : @(kActionItemHeight) 115 }; 116 // Declare this separately to avoid compiler warning about splitting string 117 // within an NSArray expression. 118 NSString* verticalConstraintLeft = @"V:|-[_remoteVideoView(remoteViewHeight)]" 119 @"-[_scrollView(kBottomViewHeight)]-|"; 120 NSString* verticalConstraintRight = 121 @"V:|-[_remoteVideoView(remoteViewHeight)]-[_actionItemsView(" 122 @"kBottomViewHeight)]-|"; 123 NSArray* constraintFormats = @[ 124 verticalConstraintLeft, 125 verticalConstraintRight, 126 @"H:|-[_remoteVideoView(remoteViewWidth)]-|", 127 @"V:|-[_localVideoView(localViewHeight)]", 128 @"H:|-[_localVideoView(localViewWidth)]", 129 @"H:|-[_scrollView(==_actionItemsView)]-[_actionItemsView]-|" 130 ]; 131 132 NSArray* actionItemsConstraints = @[ 133 @"H:|-[_roomField(kRoomFieldWidth)]-[_loopbackButton(kRoomFieldWidth)]", 134 @"H:|-[_connectButton(kRoomFieldWidth)]", 135 @"V:|-[_roomField(kActionItemHeight)]-[_connectButton(kActionItemHeight)]", 136 @"V:|-[_loopbackButton(kActionItemHeight)]", 137 ]; 138 139 [APPRTCMainView addConstraints:constraintFormats 140 toView:self 141 viewsDictionary:viewsDictionary 142 metrics:metrics]; 143 [APPRTCMainView addConstraints:actionItemsConstraints 144 toView:_actionItemsView 145 viewsDictionary:viewsDictionary 146 metrics:metrics]; 147 [super updateConstraints]; 148 } 149 150 #pragma mark - Constraints helper 151 152 + (void)addConstraints:(NSArray*)constraintFormats 153 toView:(NSView*)view 154 viewsDictionary:(NSDictionary*)viewsDictionary 155 metrics:(NSDictionary*)metrics { 156 for (NSString* constraintFormat in constraintFormats) { 157 NSArray* constraints = 158 [NSLayoutConstraint constraintsWithVisualFormat:constraintFormat 159 options:0 160 metrics:metrics 161 views:viewsDictionary]; 162 for (NSLayoutConstraint* constraint in constraints) { 163 [view addConstraint:constraint]; 164 } 165 } 166 } 167 168 #pragma mark - Control actions 169 170 - (void)startCall:(id)sender { 171 NSString* roomString = _roomField.stringValue; 172 // Generate room id for loopback options. 173 if (_loopbackButton.intValue && [roomString isEqualToString:@""]) { 174 roomString = [NSUUID UUID].UUIDString; 175 roomString = [roomString stringByReplacingOccurrencesOfString:@"-" 176 withString:@""]; 177 } 178 [self.delegate appRTCMainView:self 179 didEnterRoomId:roomString 180 loopback:_loopbackButton.intValue]; 181 [self setNeedsUpdateConstraints:YES]; 182 } 183 184 #pragma mark - RTCVideoViewDelegate 185 186 - (void)videoView:(id<RTC_OBJC_TYPE(RTCVideoRenderer)>)videoView 187 didChangeVideoSize:(CGSize)size { 188 if (videoView == _remoteVideoView) { 189 _remoteVideoSize = size; 190 } else if (videoView == _localVideoView) { 191 _localVideoSize = size; 192 } else { 193 return; 194 } 195 196 [self setNeedsUpdateConstraints:YES]; 197 } 198 199 #pragma mark - Private 200 201 - (void)setupViews { 202 NSParameterAssert([[self subviews] count] == 0); 203 204 _logView = [[NSTextView alloc] initWithFrame:NSZeroRect]; 205 [_logView setMinSize:NSMakeSize(0, kBottomViewHeight)]; 206 [_logView setMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; 207 [_logView setVerticallyResizable:YES]; 208 [_logView setAutoresizingMask:NSViewWidthSizable]; 209 NSTextContainer* textContainer = [_logView textContainer]; 210 NSSize containerSize = NSMakeSize(kContentWidth, FLT_MAX); 211 [textContainer setContainerSize:containerSize]; 212 [textContainer setWidthTracksTextView:YES]; 213 [_logView setEditable:NO]; 214 215 [self setupActionItemsView]; 216 217 _scrollView = [[NSScrollView alloc] initWithFrame:NSZeroRect]; 218 [_scrollView setTranslatesAutoresizingMaskIntoConstraints:NO]; 219 [_scrollView setHasVerticalScroller:YES]; 220 [_scrollView setDocumentView:_logView]; 221 [self addSubview:_scrollView]; 222 223 _remoteVideoView = 224 [[RTC_OBJC_TYPE(RTCMTLNSVideoView) alloc] initWithFrame:NSZeroRect]; 225 _localVideoView = 226 [[RTC_OBJC_TYPE(RTCMTLNSVideoView) alloc] initWithFrame:NSZeroRect]; 227 228 [_remoteVideoView setTranslatesAutoresizingMaskIntoConstraints:NO]; 229 [self addSubview:_remoteVideoView]; 230 [_localVideoView setTranslatesAutoresizingMaskIntoConstraints:NO]; 231 [self addSubview:_localVideoView]; 232 } 233 234 - (void)setupActionItemsView { 235 _actionItemsView = [[NSView alloc] initWithFrame:NSZeroRect]; 236 [_actionItemsView setTranslatesAutoresizingMaskIntoConstraints:NO]; 237 [self addSubview:_actionItemsView]; 238 239 _roomField = [[NSTextField alloc] initWithFrame:NSZeroRect]; 240 [_roomField setTranslatesAutoresizingMaskIntoConstraints:NO]; 241 [[_roomField cell] setPlaceholderString:@"Enter AppRTC room id"]; 242 [_actionItemsView addSubview:_roomField]; 243 [_roomField setEditable:YES]; 244 245 _connectButton = [[NSButton alloc] initWithFrame:NSZeroRect]; 246 [_connectButton setTranslatesAutoresizingMaskIntoConstraints:NO]; 247 _connectButton.title = @"Start call"; 248 _connectButton.bezelStyle = NSBezelStyleSmallSquare; 249 _connectButton.target = self; 250 _connectButton.action = @selector(startCall:); 251 [_actionItemsView addSubview:_connectButton]; 252 253 _loopbackButton = [[NSButton alloc] initWithFrame:NSZeroRect]; 254 [_loopbackButton setTranslatesAutoresizingMaskIntoConstraints:NO]; 255 _loopbackButton.title = @"Loopback"; 256 [_loopbackButton setButtonType:NSButtonTypeSwitch]; 257 [_actionItemsView addSubview:_loopbackButton]; 258 } 259 260 - (NSSize)remoteVideoViewSize { 261 if (!_remoteVideoView.bounds.size.width) { 262 return NSMakeSize(kContentWidth, 0); 263 } 264 NSInteger width = MAX(_remoteVideoView.bounds.size.width, kContentWidth); 265 NSInteger height = (width / 16) * 9; 266 return NSMakeSize(width, height); 267 } 268 269 @end 270 271 @interface APPRTCViewController () <ARDAppClientDelegate, 272 APPRTCMainViewDelegate> 273 @property(nonatomic, readonly) APPRTCMainView* mainView; 274 @end 275 276 @implementation APPRTCViewController { 277 ARDAppClient* _client; 278 RTC_OBJC_TYPE(RTCVideoTrack) * _localVideoTrack; 279 RTC_OBJC_TYPE(RTCVideoTrack) * _remoteVideoTrack; 280 ARDCaptureController* _captureController; 281 } 282 283 - (void)dealloc { 284 [self disconnect]; 285 } 286 287 - (void)viewDidAppear { 288 [super viewDidAppear]; 289 [self displayUsageInstructions]; 290 } 291 292 - (void)loadView { 293 APPRTCMainView* view = [[APPRTCMainView alloc] initWithFrame:NSZeroRect]; 294 [view setTranslatesAutoresizingMaskIntoConstraints:NO]; 295 view.delegate = self; 296 self.view = view; 297 } 298 299 - (void)windowWillClose:(NSNotification*)notification { 300 [self disconnect]; 301 } 302 303 #pragma mark - Usage 304 305 - (void)displayUsageInstructions { 306 [self.mainView displayLogMessage: 307 @"To start call:\n" 308 @"• Enter AppRTC room id (not neccessary for loopback)\n" 309 @"• Start call"]; 310 } 311 312 #pragma mark - ARDAppClientDelegate 313 314 - (void)appClient:(ARDAppClient*)client 315 didChangeState:(ARDAppClientState)state { 316 switch (state) { 317 case kARDAppClientStateConnected: 318 [self.mainView displayLogMessage:@"Client connected."]; 319 break; 320 case kARDAppClientStateConnecting: 321 [self.mainView displayLogMessage:@"Client connecting."]; 322 break; 323 case kARDAppClientStateDisconnected: 324 [self.mainView displayLogMessage:@"Client disconnected."]; 325 [self resetUI]; 326 _client = nil; 327 break; 328 } 329 } 330 331 - (void)appClient:(ARDAppClient*)client 332 didChangeConnectionState:(RTCIceConnectionState)state { 333 } 334 335 - (void)appClient:(ARDAppClient*)client 336 didCreateLocalCapturer: 337 (RTC_OBJC_TYPE(RTCCameraVideoCapturer) *)localCapturer { 338 _captureController = [[ARDCaptureController alloc] 339 initWithCapturer:localCapturer 340 settings:[[ARDSettingsModel alloc] init]]; 341 [_captureController startCapture]; 342 } 343 344 - (void)appClient:(ARDAppClient*)client 345 didReceiveLocalVideoTrack:(RTC_OBJC_TYPE(RTCVideoTrack) *)localVideoTrack { 346 _localVideoTrack = localVideoTrack; 347 [_localVideoTrack addRenderer:self.mainView.localVideoView]; 348 } 349 350 - (void)appClient:(ARDAppClient*)client 351 didReceiveRemoteVideoTrack: 352 (RTC_OBJC_TYPE(RTCVideoTrack) *)remoteVideoTrack { 353 _remoteVideoTrack = remoteVideoTrack; 354 [_remoteVideoTrack addRenderer:self.mainView.remoteVideoView]; 355 } 356 357 - (void)appClient:(ARDAppClient*)client didError:(NSError*)error { 358 [self showAlertWithMessage:[NSString stringWithFormat:@"%@", error]]; 359 [self disconnect]; 360 } 361 362 - (void)appClient:(ARDAppClient*)client didGetStats:(NSArray*)stats { 363 } 364 365 #pragma mark - APPRTCMainViewDelegate 366 367 - (void)appRTCMainView:(APPRTCMainView*)mainView 368 didEnterRoomId:(NSString*)roomId 369 loopback:(BOOL)isLoopback { 370 if ([roomId isEqualToString:@""]) { 371 [self.mainView displayLogMessage:@"Missing room id"]; 372 return; 373 } 374 375 [self disconnect]; 376 ARDAppClient* client = [[ARDAppClient alloc] initWithDelegate:self]; 377 [client connectToRoomWithId:roomId 378 settings:[[ARDSettingsModel alloc] 379 init] // Use default settings. 380 isLoopback:isLoopback]; 381 _client = client; 382 } 383 384 #pragma mark - Private 385 386 - (APPRTCMainView*)mainView { 387 return (APPRTCMainView*)self.view; 388 } 389 390 - (void)showAlertWithMessage:(NSString*)message { 391 dispatch_async(dispatch_get_main_queue(), ^{ 392 NSAlert* alert = [[NSAlert alloc] init]; 393 [alert setMessageText:message]; 394 [alert runModal]; 395 }); 396 } 397 398 - (void)resetUI { 399 [_remoteVideoTrack removeRenderer:self.mainView.remoteVideoView]; 400 [_localVideoTrack removeRenderer:self.mainView.localVideoView]; 401 _remoteVideoTrack = nil; 402 _localVideoTrack = nil; 403 [self.mainView.remoteVideoView renderFrame:nil]; 404 [self.mainView.localVideoView renderFrame:nil]; 405 } 406 407 - (void)disconnect { 408 [self resetUI]; 409 [_captureController stopCapture]; 410 _captureController = nil; 411 [_client disconnect]; 412 } 413 414 @end