1 /* 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 "APPRTCViewController.h" 29 30 #import <AVFoundation/AVFoundation.h> 31 #import "APPRTCConnectionManager.h" 32 #import "RTCNSGLVideoView.h" 33 34 static NSUInteger const kContentWidth = 1280; 35 static NSUInteger const kContentHeight = 720; 36 static NSUInteger const kRoomFieldWidth = 80; 37 static NSUInteger const kLogViewHeight = 280; 38 39 @class APPRTCMainView; 40 @protocol APPRTCMainViewDelegate 41 42 - (void)appRTCMainView:(APPRTCMainView*)mainView 43 didEnterRoomId:(NSString*)roomId; 44 45 @end 46 47 @interface APPRTCMainView : NSView 48 49 @property(nonatomic, weak) id<APPRTCMainViewDelegate> delegate; 50 @property(nonatomic, readonly) RTCNSGLVideoView* localVideoView; 51 @property(nonatomic, readonly) RTCNSGLVideoView* remoteVideoView; 52 53 - (void)displayLogMessage:(NSString*)message; 54 55 @end 56 57 @interface APPRTCMainView () <NSTextFieldDelegate, RTCNSGLVideoViewDelegate> 58 @end 59 @implementation APPRTCMainView { 60 NSScrollView* _scrollView; 61 NSTextField* _roomLabel; 62 NSTextField* _roomField; 63 NSTextView* _logView; 64 RTCNSGLVideoView* _localVideoView; 65 RTCNSGLVideoView* _remoteVideoView; 66 CGSize _localVideoSize; 67 CGSize _remoteVideoSize; 68 } 69 70 + (BOOL)requiresConstraintBasedLayout { 71 return YES; 72 } 73 74 - (instancetype)initWithFrame:(NSRect)frame { 75 if (self = [super initWithFrame:frame]) { 76 [self setupViews]; 77 } 78 return self; 79 } 80 81 - (void)updateConstraints { 82 NSParameterAssert( 83 _roomField != nil && _scrollView != nil && _remoteVideoView != nil); 84 [self removeConstraints:[self constraints]]; 85 NSDictionary* viewsDictionary = 86 NSDictionaryOfVariableBindings(_roomLabel, 87 _roomField, 88 _scrollView, 89 _remoteVideoView); 90 91 NSSize remoteViewSize = [self remoteVideoViewSize]; 92 NSDictionary* metrics = @{ 93 @"kLogViewHeight" : @(kLogViewHeight), 94 @"kRoomFieldWidth" : @(kRoomFieldWidth), 95 @"remoteViewWidth" : @(remoteViewSize.width), 96 @"remoteViewHeight" : @(remoteViewSize.height), 97 }; 98 // Declare this separately to avoid compiler warning about splitting string 99 // within an NSArray expression. 100 NSString* verticalConstraint = 101 @"V:|-[_roomLabel]-[_roomField]-[_scrollView(kLogViewHeight)]" 102 "-[_remoteVideoView(remoteViewHeight)]-|"; 103 NSArray* constraintFormats = @[ 104 verticalConstraint, 105 @"|-[_roomLabel]", 106 @"|-[_roomField(kRoomFieldWidth)]", 107 @"|-[_scrollView(remoteViewWidth)]-|", 108 @"|-[_remoteVideoView(remoteViewWidth)]-|", 109 ]; 110 for (NSString* constraintFormat in constraintFormats) { 111 NSArray* constraints = 112 [NSLayoutConstraint constraintsWithVisualFormat:constraintFormat 113 options:0 114 metrics:metrics 115 views:viewsDictionary]; 116 for (NSLayoutConstraint* constraint in constraints) { 117 [self addConstraint:constraint]; 118 } 119 } 120 [super updateConstraints]; 121 } 122 123 - (void)displayLogMessage:(NSString*)message { 124 _logView.string = 125 [NSString stringWithFormat:@"%@%@\n", _logView.string, message]; 126 NSRange range = NSMakeRange([_logView.string length], 0); 127 [_logView scrollRangeToVisible:range]; 128 } 129 130 #pragma mark - NSControl delegate 131 132 - (void)controlTextDidEndEditing:(NSNotification*)notification { 133 NSDictionary* userInfo = [notification userInfo]; 134 NSInteger textMovement = [userInfo[@"NSTextMovement"] intValue]; 135 if (textMovement == NSReturnTextMovement) { 136 [self.delegate appRTCMainView:self didEnterRoomId:_roomField.stringValue]; 137 } 138 } 139 140 #pragma mark - RTCNSGLVideoViewDelegate 141 142 - (void)videoView:(RTCNSGLVideoView*)videoView 143 didChangeVideoSize:(NSSize)size { 144 if (videoView == _remoteVideoView) { 145 _remoteVideoSize = size; 146 } else if (videoView == _localVideoView) { 147 _localVideoSize = size; 148 } else { 149 return; 150 } 151 [self setNeedsUpdateConstraints:YES]; 152 } 153 154 #pragma mark - Private 155 156 - (void)setupViews { 157 NSParameterAssert([[self subviews] count] == 0); 158 159 _roomLabel = [[NSTextField alloc] initWithFrame:NSZeroRect]; 160 [_roomLabel setTranslatesAutoresizingMaskIntoConstraints:NO]; 161 [_roomLabel setBezeled:NO]; 162 [_roomLabel setDrawsBackground:NO]; 163 [_roomLabel setEditable:NO]; 164 [_roomLabel setStringValue:@"Enter AppRTC room id:"]; 165 [self addSubview:_roomLabel]; 166 167 _roomField = [[NSTextField alloc] initWithFrame:NSZeroRect]; 168 [_roomField setTranslatesAutoresizingMaskIntoConstraints:NO]; 169 [self addSubview:_roomField]; 170 [_roomField setEditable:YES]; 171 [_roomField setDelegate:self]; 172 173 _logView = [[NSTextView alloc] initWithFrame:NSZeroRect]; 174 [_logView setMinSize:NSMakeSize(0, kLogViewHeight)]; 175 [_logView setMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; 176 [_logView setVerticallyResizable:YES]; 177 [_logView setAutoresizingMask:NSViewWidthSizable]; 178 NSTextContainer* textContainer = [_logView textContainer]; 179 NSSize containerSize = NSMakeSize(kContentWidth, FLT_MAX); 180 [textContainer setContainerSize:containerSize]; 181 [textContainer setWidthTracksTextView:YES]; 182 [_logView setEditable:NO]; 183 184 _scrollView = [[NSScrollView alloc] initWithFrame:NSZeroRect]; 185 [_scrollView setTranslatesAutoresizingMaskIntoConstraints:NO]; 186 [_scrollView setHasVerticalScroller:YES]; 187 [_scrollView setDocumentView:_logView]; 188 [self addSubview:_scrollView]; 189 190 NSOpenGLPixelFormatAttribute attributes[] = { 191 NSOpenGLPFADoubleBuffer, 192 NSOpenGLPFADepthSize, 24, 193 NSOpenGLPFAOpenGLProfile, 194 NSOpenGLProfileVersion3_2Core, 195 0 196 }; 197 NSOpenGLPixelFormat* pixelFormat = 198 [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; 199 _remoteVideoView = [[RTCNSGLVideoView alloc] initWithFrame:NSZeroRect 200 pixelFormat:pixelFormat]; 201 [_remoteVideoView setTranslatesAutoresizingMaskIntoConstraints:NO]; 202 _remoteVideoView.delegate = self; 203 [self addSubview:_remoteVideoView]; 204 205 // TODO(tkchin): create local video view. 206 // https://code.google.com/p/webrtc/issues/detail?id=3417. 207 } 208 209 - (NSSize)remoteVideoViewSize { 210 if (_remoteVideoSize.width > 0 && _remoteVideoSize.height > 0) { 211 return _remoteVideoSize; 212 } else { 213 return NSMakeSize(kContentWidth, kContentHeight); 214 } 215 } 216 217 - (NSSize)localVideoViewSize { 218 return NSZeroSize; 219 } 220 221 @end 222 223 @interface APPRTCViewController () 224 <APPRTCConnectionManagerDelegate, APPRTCMainViewDelegate, APPRTCLogger> 225 @property(nonatomic, readonly) APPRTCMainView* mainView; 226 @end 227 228 @implementation APPRTCViewController { 229 APPRTCConnectionManager* _connectionManager; 230 } 231 232 - (instancetype)initWithNibName:(NSString*)nibName 233 bundle:(NSBundle*)bundle { 234 if (self = [super initWithNibName:nibName bundle:bundle]) { 235 _connectionManager = 236 [[APPRTCConnectionManager alloc] initWithDelegate:self 237 logger:self]; 238 } 239 return self; 240 } 241 242 - (void)dealloc { 243 [self disconnect]; 244 } 245 246 - (void)loadView { 247 APPRTCMainView* view = [[APPRTCMainView alloc] initWithFrame:NSZeroRect]; 248 [view setTranslatesAutoresizingMaskIntoConstraints:NO]; 249 view.delegate = self; 250 self.view = view; 251 } 252 253 - (void)windowWillClose:(NSNotification*)notification { 254 [self disconnect]; 255 } 256 257 #pragma mark - APPRTCConnectionManagerDelegate 258 259 - (void)connectionManager:(APPRTCConnectionManager*)manager 260 didReceiveLocalVideoTrack:(RTCVideoTrack*)localVideoTrack { 261 self.mainView.localVideoView.videoTrack = localVideoTrack; 262 } 263 264 - (void)connectionManager:(APPRTCConnectionManager*)manager 265 didReceiveRemoteVideoTrack:(RTCVideoTrack*)remoteVideoTrack { 266 self.mainView.remoteVideoView.videoTrack = remoteVideoTrack; 267 } 268 269 - (void)connectionManagerDidReceiveHangup:(APPRTCConnectionManager*)manager { 270 [self showAlertWithMessage:@"Remote closed connection"]; 271 [self disconnect]; 272 } 273 274 - (void)connectionManager:(APPRTCConnectionManager*)manager 275 didErrorWithMessage:(NSString*)message { 276 [self showAlertWithMessage:message]; 277 [self disconnect]; 278 } 279 280 #pragma mark - APPRTCLogger 281 282 - (void)logMessage:(NSString*)message { 283 [self.mainView displayLogMessage:message]; 284 } 285 286 #pragma mark - APPRTCMainViewDelegate 287 288 - (void)appRTCMainView:(APPRTCMainView*)mainView 289 didEnterRoomId:(NSString*)roomId { 290 NSString* urlString = 291 [NSString stringWithFormat:@"https://apprtc.appspot.com/?r=%@", roomId]; 292 [_connectionManager connectToRoomWithURL:[NSURL URLWithString:urlString]]; 293 } 294 295 #pragma mark - Private 296 297 - (APPRTCMainView*)mainView { 298 return (APPRTCMainView*)self.view; 299 } 300 301 - (void)showAlertWithMessage:(NSString*)message { 302 NSAlert* alert = [[NSAlert alloc] init]; 303 [alert setMessageText:message]; 304 [alert runModal]; 305 } 306 307 - (void)disconnect { 308 self.mainView.remoteVideoView.videoTrack = nil; 309 [_connectionManager disconnect]; 310 } 311 312 @end 313