ARDVideoCallView.m (7845B)
1 /* 2 * Copyright 2015 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 "ARDVideoCallView.h" 12 13 #import <AVFoundation/AVFoundation.h> 14 15 #import "sdk/objc/components/renderer/metal/RTCMTLVideoView.h" 16 17 #import "UIImage+ARDUtilities.h" 18 19 static CGFloat const kButtonPadding = 16; 20 static CGFloat const kButtonSize = 48; 21 static CGFloat const kLocalVideoViewSize = 120; 22 static CGFloat const kLocalVideoViewPadding = 8; 23 static CGFloat const kStatusBarHeight = 20; 24 25 @interface ARDVideoCallView () <RTC_OBJC_TYPE (RTCVideoViewDelegate)> 26 @end 27 28 @implementation ARDVideoCallView { 29 UIButton *_routeChangeButton; 30 UIButton *_cameraSwitchButton; 31 UIButton *_hangupButton; 32 CGSize _remoteVideoSize; 33 } 34 35 @synthesize statusLabel = _statusLabel; 36 @synthesize localVideoView = _localVideoView; 37 @synthesize remoteVideoView = _remoteVideoView; 38 @synthesize statsView = _statsView; 39 @synthesize delegate = _delegate; 40 41 - (instancetype)initWithFrame:(CGRect)frame { 42 self = [super initWithFrame:frame]; 43 if (self) { 44 _remoteVideoView = 45 [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectZero]; 46 47 [self addSubview:_remoteVideoView]; 48 49 _localVideoView = 50 [[RTC_OBJC_TYPE(RTCCameraPreviewView) alloc] initWithFrame:CGRectZero]; 51 [self addSubview:_localVideoView]; 52 53 _statsView = [[ARDStatsView alloc] initWithFrame:CGRectZero]; 54 _statsView.hidden = YES; 55 [self addSubview:_statsView]; 56 57 _routeChangeButton = [UIButton buttonWithType:UIButtonTypeCustom]; 58 _routeChangeButton.backgroundColor = [UIColor grayColor]; 59 _routeChangeButton.layer.cornerRadius = kButtonSize / 2; 60 _routeChangeButton.layer.masksToBounds = YES; 61 UIImage *image = [UIImage imageForName:@"ic_surround_sound_black_24dp.png" 62 color:[UIColor whiteColor]]; 63 [_routeChangeButton setImage:image forState:UIControlStateNormal]; 64 [_routeChangeButton addTarget:self 65 action:@selector(onRouteChange:) 66 forControlEvents:UIControlEventTouchUpInside]; 67 [self addSubview:_routeChangeButton]; 68 69 // TODO(tkchin): don't display this if we can't actually do camera switch. 70 _cameraSwitchButton = [UIButton buttonWithType:UIButtonTypeCustom]; 71 _cameraSwitchButton.backgroundColor = [UIColor grayColor]; 72 _cameraSwitchButton.layer.cornerRadius = kButtonSize / 2; 73 _cameraSwitchButton.layer.masksToBounds = YES; 74 image = [UIImage imageForName:@"ic_switch_video_black_24dp.png" 75 color:[UIColor whiteColor]]; 76 [_cameraSwitchButton setImage:image forState:UIControlStateNormal]; 77 [_cameraSwitchButton addTarget:self 78 action:@selector(onCameraSwitch:) 79 forControlEvents:UIControlEventTouchUpInside]; 80 [self addSubview:_cameraSwitchButton]; 81 82 _hangupButton = [UIButton buttonWithType:UIButtonTypeCustom]; 83 _hangupButton.backgroundColor = [UIColor redColor]; 84 _hangupButton.layer.cornerRadius = kButtonSize / 2; 85 _hangupButton.layer.masksToBounds = YES; 86 image = [UIImage imageForName:@"ic_call_end_black_24dp.png" 87 color:[UIColor whiteColor]]; 88 [_hangupButton setImage:image forState:UIControlStateNormal]; 89 [_hangupButton addTarget:self 90 action:@selector(onHangup:) 91 forControlEvents:UIControlEventTouchUpInside]; 92 [self addSubview:_hangupButton]; 93 94 _statusLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 95 _statusLabel.font = [UIFont fontWithName:@"Roboto" size:16]; 96 _statusLabel.textColor = [UIColor whiteColor]; 97 [self addSubview:_statusLabel]; 98 99 UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] 100 initWithTarget:self 101 action:@selector(didTripleTap:)]; 102 tapRecognizer.numberOfTapsRequired = 3; 103 [self addGestureRecognizer:tapRecognizer]; 104 } 105 return self; 106 } 107 108 - (void)layoutSubviews { 109 CGRect bounds = self.bounds; 110 if (_remoteVideoSize.width > 0 && _remoteVideoSize.height > 0) { 111 // Aspect fill remote video into bounds. 112 CGRect remoteVideoFrame = 113 AVMakeRectWithAspectRatioInsideRect(_remoteVideoSize, bounds); 114 CGFloat scale = 1; 115 if (remoteVideoFrame.size.width > remoteVideoFrame.size.height) { 116 // Scale by height. 117 scale = bounds.size.height / remoteVideoFrame.size.height; 118 } else { 119 // Scale by width. 120 scale = bounds.size.width / remoteVideoFrame.size.width; 121 } 122 remoteVideoFrame.size.height *= scale; 123 remoteVideoFrame.size.width *= scale; 124 _remoteVideoView.frame = remoteVideoFrame; 125 _remoteVideoView.center = 126 CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds)); 127 } else { 128 _remoteVideoView.frame = bounds; 129 } 130 131 // Aspect fit local video view into a square box. 132 CGRect localVideoFrame = 133 CGRectMake(0, 0, kLocalVideoViewSize, kLocalVideoViewSize); 134 // Place the view in the bottom right. 135 localVideoFrame.origin.x = CGRectGetMaxX(bounds) - 136 localVideoFrame.size.width - kLocalVideoViewPadding; 137 localVideoFrame.origin.y = CGRectGetMaxY(bounds) - 138 localVideoFrame.size.height - kLocalVideoViewPadding; 139 _localVideoView.frame = localVideoFrame; 140 141 // Place stats at the top. 142 CGSize statsSize = [_statsView sizeThatFits:bounds.size]; 143 _statsView.frame = CGRectMake(CGRectGetMinX(bounds), 144 CGRectGetMinY(bounds) + kStatusBarHeight, 145 statsSize.width, 146 statsSize.height); 147 148 // Place hangup button in the bottom left. 149 _hangupButton.frame = 150 CGRectMake(CGRectGetMinX(bounds) + kButtonPadding, 151 CGRectGetMaxY(bounds) - kButtonPadding - kButtonSize, 152 kButtonSize, 153 kButtonSize); 154 155 // Place button to the right of hangup button. 156 CGRect cameraSwitchFrame = _hangupButton.frame; 157 cameraSwitchFrame.origin.x = 158 CGRectGetMaxX(cameraSwitchFrame) + kButtonPadding; 159 _cameraSwitchButton.frame = cameraSwitchFrame; 160 161 // Place route button to the right of camera button. 162 CGRect routeChangeFrame = _cameraSwitchButton.frame; 163 routeChangeFrame.origin.x = CGRectGetMaxX(routeChangeFrame) + kButtonPadding; 164 _routeChangeButton.frame = routeChangeFrame; 165 166 [_statusLabel sizeToFit]; 167 _statusLabel.center = 168 CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds)); 169 } 170 171 #pragma mark - RTC_OBJC_TYPE(RTCVideoViewDelegate) 172 173 - (void)videoView:(id<RTC_OBJC_TYPE(RTCVideoRenderer)>)videoView 174 didChangeVideoSize:(CGSize)size { 175 if (videoView == _remoteVideoView) { 176 _remoteVideoSize = size; 177 } 178 [self setNeedsLayout]; 179 } 180 181 #pragma mark - Private 182 183 - (void)onCameraSwitch:(UIButton *)sender { 184 sender.enabled = false; 185 [_delegate videoCallView:self 186 shouldSwitchCameraWithCompletion:^(NSError *error) { 187 dispatch_async(dispatch_get_main_queue(), ^(void) { 188 sender.enabled = true; 189 }); 190 }]; 191 } 192 193 - (void)onRouteChange:(UIButton *)sender { 194 sender.enabled = false; 195 __weak ARDVideoCallView *weakSelf = self; 196 [_delegate videoCallView:self 197 shouldChangeRouteWithCompletion:^(void) { 198 ARDVideoCallView *strongSelf = weakSelf; 199 if (strongSelf) { 200 dispatch_async(dispatch_get_main_queue(), ^(void) { 201 sender.enabled = true; 202 }); 203 } 204 }]; 205 } 206 207 - (void)onHangup:(id)sender { 208 [_delegate videoCallViewDidHangup:self]; 209 } 210 211 - (void)didTripleTap:(UITapGestureRecognizer *)recognizer { 212 [_delegate videoCallViewDidEnableStats:self]; 213 } 214 215 @end