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