You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
439 lines
14 KiB
439 lines
14 KiB
/*
|
|
* 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 <AVFoundation/AVFoundation.h>
|
|
|
|
#import <WebRTC/RTCMTLNSVideoView.h>
|
|
#import <WebRTC/RTCNSGLVideoView.h>
|
|
#import <WebRTC/RTCVideoTrack.h>
|
|
|
|
#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<APPRTCMainViewDelegate> delegate;
|
|
@property(nonatomic, readonly) NSView<RTC_OBJC_TYPE(RTCVideoRenderer)>* localVideoView;
|
|
@property(nonatomic, readonly) NSView<RTC_OBJC_TYPE(RTCVideoRenderer)>* remoteVideoView;
|
|
@property(nonatomic, readonly) NSTextView* logView;
|
|
|
|
- (void)displayLogMessage:(NSString*)message;
|
|
|
|
@end
|
|
|
|
@interface APPRTCMainView () <NSTextFieldDelegate, RTC_OBJC_TYPE (RTCNSGLVideoViewDelegate)>
|
|
@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 ()
|
|
<ARDAppClientDelegate, APPRTCMainViewDelegate>
|
|
@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
|