/* * Copyright 2014 The WebRTC Project Authors. All rights reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #import "APPRTCViewController.h" #import #import #import #import #import "ARDAppClient.h" #import "ARDCaptureController.h" #import "ARDSettingsModel.h" static NSUInteger const kContentWidth = 900; static NSUInteger const kRoomFieldWidth = 200; static NSUInteger const kActionItemHeight = 30; static NSUInteger const kBottomViewHeight = 200; @class APPRTCMainView; @protocol APPRTCMainViewDelegate - (void)appRTCMainView:(APPRTCMainView*)mainView didEnterRoomId:(NSString*)roomId loopback:(BOOL)isLoopback; @end @interface APPRTCMainView : NSView @property(nonatomic, weak) id delegate; @property(nonatomic, readonly) NSView* localVideoView; @property(nonatomic, readonly) NSView* remoteVideoView; @property(nonatomic, readonly) NSTextView* logView; - (void)displayLogMessage:(NSString*)message; @end @interface APPRTCMainView () @end @implementation APPRTCMainView { NSScrollView* _scrollView; NSView* _actionItemsView; NSButton* _connectButton; NSButton* _loopbackButton; NSTextField* _roomField; CGSize _localVideoSize; CGSize _remoteVideoSize; } @synthesize delegate = _delegate; @synthesize localVideoView = _localVideoView; @synthesize remoteVideoView = _remoteVideoView; @synthesize logView = _logView; - (void)displayLogMessage:(NSString *)message { dispatch_async(dispatch_get_main_queue(), ^{ self.logView.string = [NSString stringWithFormat:@"%@%@\n", self.logView.string, message]; NSRange range = NSMakeRange(self.logView.string.length, 0); [self.logView scrollRangeToVisible:range]; }); } #pragma mark - Private - (instancetype)initWithFrame:(NSRect)frame { if (self = [super initWithFrame:frame]) { [self setupViews]; } return self; } + (BOOL)requiresConstraintBasedLayout { return YES; } - (void)updateConstraints { NSParameterAssert( _roomField != nil && _scrollView != nil && _remoteVideoView != nil && _localVideoView != nil && _actionItemsView!= nil && _connectButton != nil && _loopbackButton != nil); [self removeConstraints:[self constraints]]; NSDictionary* viewsDictionary = NSDictionaryOfVariableBindings(_roomField, _scrollView, _remoteVideoView, _localVideoView, _actionItemsView, _connectButton, _loopbackButton); NSSize remoteViewSize = [self remoteVideoViewSize]; NSDictionary* metrics = @{ @"remoteViewWidth" : @(remoteViewSize.width), @"remoteViewHeight" : @(remoteViewSize.height), @"kBottomViewHeight" : @(kBottomViewHeight), @"localViewHeight" : @(remoteViewSize.height / 3), @"localViewWidth" : @(remoteViewSize.width / 3), @"kRoomFieldWidth" : @(kRoomFieldWidth), @"kActionItemHeight" : @(kActionItemHeight) }; // Declare this separately to avoid compiler warning about splitting string // within an NSArray expression. NSString* verticalConstraintLeft = @"V:|-[_remoteVideoView(remoteViewHeight)]-[_scrollView(kBottomViewHeight)]-|"; NSString* verticalConstraintRight = @"V:|-[_remoteVideoView(remoteViewHeight)]-[_actionItemsView(kBottomViewHeight)]-|"; NSArray* constraintFormats = @[ verticalConstraintLeft, verticalConstraintRight, @"H:|-[_remoteVideoView(remoteViewWidth)]-|", @"V:|-[_localVideoView(localViewHeight)]", @"H:|-[_localVideoView(localViewWidth)]", @"H:|-[_scrollView(==_actionItemsView)]-[_actionItemsView]-|" ]; NSArray* actionItemsConstraints = @[ @"H:|-[_roomField(kRoomFieldWidth)]-[_loopbackButton(kRoomFieldWidth)]", @"H:|-[_connectButton(kRoomFieldWidth)]", @"V:|-[_roomField(kActionItemHeight)]-[_connectButton(kActionItemHeight)]", @"V:|-[_loopbackButton(kActionItemHeight)]", ]; [APPRTCMainView addConstraints:constraintFormats toView:self viewsDictionary:viewsDictionary metrics:metrics]; [APPRTCMainView addConstraints:actionItemsConstraints toView:_actionItemsView viewsDictionary:viewsDictionary metrics:metrics]; [super updateConstraints]; } #pragma mark - Constraints helper + (void)addConstraints:(NSArray*)constraints toView:(NSView*)view viewsDictionary:(NSDictionary*)viewsDictionary metrics:(NSDictionary*)metrics { for (NSString* constraintFormat in constraints) { NSArray* constraints = [NSLayoutConstraint constraintsWithVisualFormat:constraintFormat options:0 metrics:metrics views:viewsDictionary]; for (NSLayoutConstraint* constraint in constraints) { [view addConstraint:constraint]; } } } #pragma mark - Control actions - (void)startCall:(id)sender { NSString* roomString = _roomField.stringValue; // Generate room id for loopback options. if (_loopbackButton.intValue && [roomString isEqualToString:@""]) { roomString = [NSUUID UUID].UUIDString; roomString = [roomString stringByReplacingOccurrencesOfString:@"-" withString:@""]; } [self.delegate appRTCMainView:self didEnterRoomId:roomString loopback:_loopbackButton.intValue]; [self setNeedsUpdateConstraints:YES]; } #pragma mark - RTC_OBJC_TYPE(RTCNSGLVideoViewDelegate) - (void)videoView:(RTC_OBJC_TYPE(RTCNSGLVideoView) *)videoView didChangeVideoSize:(NSSize)size { if (videoView == _remoteVideoView) { _remoteVideoSize = size; } else if (videoView == _localVideoView) { _localVideoSize = size; } else { return; } [self setNeedsUpdateConstraints:YES]; } #pragma mark - Private - (void)setupViews { NSParameterAssert([[self subviews] count] == 0); _logView = [[NSTextView alloc] initWithFrame:NSZeroRect]; [_logView setMinSize:NSMakeSize(0, kBottomViewHeight)]; [_logView setMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; [_logView setVerticallyResizable:YES]; [_logView setAutoresizingMask:NSViewWidthSizable]; NSTextContainer* textContainer = [_logView textContainer]; NSSize containerSize = NSMakeSize(kContentWidth, FLT_MAX); [textContainer setContainerSize:containerSize]; [textContainer setWidthTracksTextView:YES]; [_logView setEditable:NO]; [self setupActionItemsView]; _scrollView = [[NSScrollView alloc] initWithFrame:NSZeroRect]; [_scrollView setTranslatesAutoresizingMaskIntoConstraints:NO]; [_scrollView setHasVerticalScroller:YES]; [_scrollView setDocumentView:_logView]; [self addSubview:_scrollView]; // NOTE (daniela): Ignoring Clang diagonstic here. // We're performing run time check to make sure class is available on runtime. // If not we're providing sensible default. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpartial-availability" if ([RTC_OBJC_TYPE(RTCMTLNSVideoView) class] && [RTC_OBJC_TYPE(RTCMTLNSVideoView) isMetalAvailable]) { _remoteVideoView = [[RTC_OBJC_TYPE(RTCMTLNSVideoView) alloc] initWithFrame:NSZeroRect]; _localVideoView = [[RTC_OBJC_TYPE(RTCMTLNSVideoView) alloc] initWithFrame:NSZeroRect]; } #pragma clang diagnostic pop if (_remoteVideoView == nil) { NSOpenGLPixelFormatAttribute attributes[] = { NSOpenGLPFADoubleBuffer, NSOpenGLPFADepthSize, 24, NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, 0 }; NSOpenGLPixelFormat* pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; RTC_OBJC_TYPE(RTCNSGLVideoView)* remote = [[RTC_OBJC_TYPE(RTCNSGLVideoView) alloc] initWithFrame:NSZeroRect pixelFormat:pixelFormat]; remote.delegate = self; _remoteVideoView = remote; RTC_OBJC_TYPE(RTCNSGLVideoView)* local = [[RTC_OBJC_TYPE(RTCNSGLVideoView) alloc] initWithFrame:NSZeroRect pixelFormat:pixelFormat]; local.delegate = self; _localVideoView = local; } [_remoteVideoView setTranslatesAutoresizingMaskIntoConstraints:NO]; [self addSubview:_remoteVideoView]; [_localVideoView setTranslatesAutoresizingMaskIntoConstraints:NO]; [self addSubview:_localVideoView]; } - (void)setupActionItemsView { _actionItemsView = [[NSView alloc] initWithFrame:NSZeroRect]; [_actionItemsView setTranslatesAutoresizingMaskIntoConstraints:NO]; [self addSubview:_actionItemsView]; _roomField = [[NSTextField alloc] initWithFrame:NSZeroRect]; [_roomField setTranslatesAutoresizingMaskIntoConstraints:NO]; [[_roomField cell] setPlaceholderString: @"Enter AppRTC room id"]; [_actionItemsView addSubview:_roomField]; [_roomField setEditable:YES]; _connectButton = [[NSButton alloc] initWithFrame:NSZeroRect]; [_connectButton setTranslatesAutoresizingMaskIntoConstraints:NO]; _connectButton.title = @"Start call"; _connectButton.bezelStyle = NSRoundedBezelStyle; _connectButton.target = self; _connectButton.action = @selector(startCall:); [_actionItemsView addSubview:_connectButton]; _loopbackButton = [[NSButton alloc] initWithFrame:NSZeroRect]; [_loopbackButton setTranslatesAutoresizingMaskIntoConstraints:NO]; _loopbackButton.title = @"Loopback"; [_loopbackButton setButtonType:NSSwitchButton]; [_actionItemsView addSubview:_loopbackButton]; } - (NSSize)remoteVideoViewSize { if (!_remoteVideoView.bounds.size.width) { return NSMakeSize(kContentWidth, 0); } NSInteger width = MAX(_remoteVideoView.bounds.size.width, kContentWidth); NSInteger height = (width/16) * 9; return NSMakeSize(width, height); } @end @interface APPRTCViewController () @property(nonatomic, readonly) APPRTCMainView* mainView; @end @implementation APPRTCViewController { ARDAppClient* _client; RTC_OBJC_TYPE(RTCVideoTrack) * _localVideoTrack; RTC_OBJC_TYPE(RTCVideoTrack) * _remoteVideoTrack; ARDCaptureController* _captureController; } - (void)dealloc { [self disconnect]; } - (void)viewDidAppear { [super viewDidAppear]; [self displayUsageInstructions]; } - (void)loadView { APPRTCMainView* view = [[APPRTCMainView alloc] initWithFrame:NSZeroRect]; [view setTranslatesAutoresizingMaskIntoConstraints:NO]; view.delegate = self; self.view = view; } - (void)windowWillClose:(NSNotification*)notification { [self disconnect]; } #pragma mark - Usage - (void)displayUsageInstructions { [self.mainView displayLogMessage: @"To start call:\n" @"• Enter AppRTC room id (not neccessary for loopback)\n" @"• Start call"]; } #pragma mark - ARDAppClientDelegate - (void)appClient:(ARDAppClient *)client didChangeState:(ARDAppClientState)state { switch (state) { case kARDAppClientStateConnected: [self.mainView displayLogMessage:@"Client connected."]; break; case kARDAppClientStateConnecting: [self.mainView displayLogMessage:@"Client connecting."]; break; case kARDAppClientStateDisconnected: [self.mainView displayLogMessage:@"Client disconnected."]; [self resetUI]; _client = nil; break; } } - (void)appClient:(ARDAppClient *)client didChangeConnectionState:(RTCIceConnectionState)state { } - (void)appClient:(ARDAppClient*)client didCreateLocalCapturer:(RTC_OBJC_TYPE(RTCCameraVideoCapturer) *)localCapturer { _captureController = [[ARDCaptureController alloc] initWithCapturer:localCapturer settings:[[ARDSettingsModel alloc] init]]; [_captureController startCapture]; } - (void)appClient:(ARDAppClient*)client didReceiveLocalVideoTrack:(RTC_OBJC_TYPE(RTCVideoTrack) *)localVideoTrack { _localVideoTrack = localVideoTrack; [_localVideoTrack addRenderer:self.mainView.localVideoView]; } - (void)appClient:(ARDAppClient*)client didReceiveRemoteVideoTrack:(RTC_OBJC_TYPE(RTCVideoTrack) *)remoteVideoTrack { _remoteVideoTrack = remoteVideoTrack; [_remoteVideoTrack addRenderer:self.mainView.remoteVideoView]; } - (void)appClient:(ARDAppClient *)client didError:(NSError *)error { [self showAlertWithMessage:[NSString stringWithFormat:@"%@", error]]; [self disconnect]; } - (void)appClient:(ARDAppClient *)client didGetStats:(NSArray *)stats { } #pragma mark - APPRTCMainViewDelegate - (void)appRTCMainView:(APPRTCMainView*)mainView didEnterRoomId:(NSString*)roomId loopback:(BOOL)isLoopback { if ([roomId isEqualToString:@""]) { [self.mainView displayLogMessage:@"Missing room id"]; return; } [self disconnect]; ARDAppClient* client = [[ARDAppClient alloc] initWithDelegate:self]; [client connectToRoomWithId:roomId settings:[[ARDSettingsModel alloc] init] // Use default settings. isLoopback:isLoopback]; _client = client; } #pragma mark - Private - (APPRTCMainView*)mainView { return (APPRTCMainView*)self.view; } - (void)showAlertWithMessage:(NSString*)message { dispatch_async(dispatch_get_main_queue(), ^{ NSAlert* alert = [[NSAlert alloc] init]; [alert setMessageText:message]; [alert runModal]; }); } - (void)resetUI { [_remoteVideoTrack removeRenderer:self.mainView.remoteVideoView]; [_localVideoTrack removeRenderer:self.mainView.localVideoView]; _remoteVideoTrack = nil; _localVideoTrack = nil; [self.mainView.remoteVideoView renderFrame:nil]; [self.mainView.localVideoView renderFrame:nil]; } - (void)disconnect { [self resetUI]; [_captureController stopCapture]; _captureController = nil; [_client disconnect]; } @end