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