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