| /* | |
| * Copyright 2015 The WebRTC Project Authors. All rights reserved. | |
| * | |
| * Use of this source code is governed by a BSD-style license | |
| * that can be found in the LICENSE file in the root of the source | |
| * tree. An additional intellectual property rights grant can be found | |
| * in the file PATENTS. All contributing project authors may | |
| * be found in the AUTHORS file in the root of the source tree. | |
| */ | |
| #import "ARDVideoCallView.h" | |
| #import <AVFoundation/AVFoundation.h> | |
| #import "sdk/objc/components/renderer/metal/RTCMTLVideoView.h" | |
| #import "UIImage+ARDUtilities.h" | |
| static CGFloat const kButtonPadding = 16; | |
| static CGFloat const kButtonSize = 48; | |
| static CGFloat const kLocalVideoViewSize = 120; | |
| static CGFloat const kLocalVideoViewPadding = 8; | |
| static CGFloat const kStatusBarHeight = 20; | |
| @interface ARDVideoCallView () <RTC_OBJC_TYPE (RTCVideoViewDelegate)> | |
| @end | |
| @implementation ARDVideoCallView { | |
| UIButton *_routeChangeButton; | |
| UIButton *_cameraSwitchButton; | |
| UIButton *_hangupButton; | |
| CGSize _remoteVideoSize; | |
| } | |
| @synthesize statusLabel = _statusLabel; | |
| @synthesize localVideoView = _localVideoView; | |
| @synthesize remoteVideoView = _remoteVideoView; | |
| @synthesize statsView = _statsView; | |
| @synthesize delegate = _delegate; | |
| - (instancetype)initWithFrame:(CGRect)frame { | |
| if (self = [super initWithFrame:frame]) { | |
| _remoteVideoView = [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectZero]; | |
| [self addSubview:_remoteVideoView]; | |
| _localVideoView = [[RTC_OBJC_TYPE(RTCCameraPreviewView) alloc] initWithFrame:CGRectZero]; | |
| [self addSubview:_localVideoView]; | |
| _statsView = [[ARDStatsView alloc] initWithFrame:CGRectZero]; | |
| _statsView.hidden = YES; | |
| [self addSubview:_statsView]; | |
| _routeChangeButton = [UIButton buttonWithType:UIButtonTypeCustom]; | |
| _routeChangeButton.backgroundColor = [UIColor grayColor]; | |
| _routeChangeButton.layer.cornerRadius = kButtonSize / 2; | |
| _routeChangeButton.layer.masksToBounds = YES; | |
| UIImage *image = [UIImage imageForName:@"ic_surround_sound_black_24dp.png" | |
| color:[UIColor whiteColor]]; | |
| [_routeChangeButton setImage:image forState:UIControlStateNormal]; | |
| [_routeChangeButton addTarget:self | |
| action:@selector(onRouteChange:) | |
| forControlEvents:UIControlEventTouchUpInside]; | |
| [self addSubview:_routeChangeButton]; | |
| // TODO(tkchin): don't display this if we can't actually do camera switch. | |
| _cameraSwitchButton = [UIButton buttonWithType:UIButtonTypeCustom]; | |
| _cameraSwitchButton.backgroundColor = [UIColor grayColor]; | |
| _cameraSwitchButton.layer.cornerRadius = kButtonSize / 2; | |
| _cameraSwitchButton.layer.masksToBounds = YES; | |
| image = [UIImage imageForName:@"ic_switch_video_black_24dp.png" color:[UIColor whiteColor]]; | |
| [_cameraSwitchButton setImage:image forState:UIControlStateNormal]; | |
| [_cameraSwitchButton addTarget:self | |
| action:@selector(onCameraSwitch:) | |
| forControlEvents:UIControlEventTouchUpInside]; | |
| [self addSubview:_cameraSwitchButton]; | |
| _hangupButton = [UIButton buttonWithType:UIButtonTypeCustom]; | |
| _hangupButton.backgroundColor = [UIColor redColor]; | |
| _hangupButton.layer.cornerRadius = kButtonSize / 2; | |
| _hangupButton.layer.masksToBounds = YES; | |
| image = [UIImage imageForName:@"ic_call_end_black_24dp.png" | |
| color:[UIColor whiteColor]]; | |
| [_hangupButton setImage:image forState:UIControlStateNormal]; | |
| [_hangupButton addTarget:self | |
| action:@selector(onHangup:) | |
| forControlEvents:UIControlEventTouchUpInside]; | |
| [self addSubview:_hangupButton]; | |
| _statusLabel = [[UILabel alloc] initWithFrame:CGRectZero]; | |
| _statusLabel.font = [UIFont fontWithName:@"Roboto" size:16]; | |
| _statusLabel.textColor = [UIColor whiteColor]; | |
| [self addSubview:_statusLabel]; | |
| UITapGestureRecognizer *tapRecognizer = | |
| [[UITapGestureRecognizer alloc] | |
| initWithTarget:self | |
| action:@selector(didTripleTap:)]; | |
| tapRecognizer.numberOfTapsRequired = 3; | |
| [self addGestureRecognizer:tapRecognizer]; | |
| } | |
| return self; | |
| } | |
| - (void)layoutSubviews { | |
| CGRect bounds = self.bounds; | |
| if (_remoteVideoSize.width > 0 && _remoteVideoSize.height > 0) { | |
| // Aspect fill remote video into bounds. | |
| CGRect remoteVideoFrame = | |
| AVMakeRectWithAspectRatioInsideRect(_remoteVideoSize, bounds); | |
| CGFloat scale = 1; | |
| if (remoteVideoFrame.size.width > remoteVideoFrame.size.height) { | |
| // Scale by height. | |
| scale = bounds.size.height / remoteVideoFrame.size.height; | |
| } else { | |
| // Scale by width. | |
| scale = bounds.size.width / remoteVideoFrame.size.width; | |
| } | |
| remoteVideoFrame.size.height *= scale; | |
| remoteVideoFrame.size.width *= scale; | |
| _remoteVideoView.frame = remoteVideoFrame; | |
| _remoteVideoView.center = | |
| CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds)); | |
| } else { | |
| _remoteVideoView.frame = bounds; | |
| } | |
| // Aspect fit local video view into a square box. | |
| CGRect localVideoFrame = | |
| CGRectMake(0, 0, kLocalVideoViewSize, kLocalVideoViewSize); | |
| // Place the view in the bottom right. | |
| localVideoFrame.origin.x = CGRectGetMaxX(bounds) | |
| - localVideoFrame.size.width - kLocalVideoViewPadding; | |
| localVideoFrame.origin.y = CGRectGetMaxY(bounds) | |
| - localVideoFrame.size.height - kLocalVideoViewPadding; | |
| _localVideoView.frame = localVideoFrame; | |
| // Place stats at the top. | |
| CGSize statsSize = [_statsView sizeThatFits:bounds.size]; | |
| _statsView.frame = CGRectMake(CGRectGetMinX(bounds), | |
| CGRectGetMinY(bounds) + kStatusBarHeight, | |
| statsSize.width, statsSize.height); | |
| // Place hangup button in the bottom left. | |
| _hangupButton.frame = | |
| CGRectMake(CGRectGetMinX(bounds) + kButtonPadding, | |
| CGRectGetMaxY(bounds) - kButtonPadding - | |
| kButtonSize, | |
| kButtonSize, | |
| kButtonSize); | |
| // Place button to the right of hangup button. | |
| CGRect cameraSwitchFrame = _hangupButton.frame; | |
| cameraSwitchFrame.origin.x = | |
| CGRectGetMaxX(cameraSwitchFrame) + kButtonPadding; | |
| _cameraSwitchButton.frame = cameraSwitchFrame; | |
| // Place route button to the right of camera button. | |
| CGRect routeChangeFrame = _cameraSwitchButton.frame; | |
| routeChangeFrame.origin.x = | |
| CGRectGetMaxX(routeChangeFrame) + kButtonPadding; | |
| _routeChangeButton.frame = routeChangeFrame; | |
| [_statusLabel sizeToFit]; | |
| _statusLabel.center = | |
| CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds)); | |
| } | |
| #pragma mark - RTC_OBJC_TYPE(RTCVideoViewDelegate) | |
| - (void)videoView:(id<RTC_OBJC_TYPE(RTCVideoRenderer)>)videoView didChangeVideoSize:(CGSize)size { | |
| if (videoView == _remoteVideoView) { | |
| _remoteVideoSize = size; | |
| } | |
| [self setNeedsLayout]; | |
| } | |
| #pragma mark - Private | |
| - (void)onCameraSwitch:(UIButton *)sender { | |
| sender.enabled = false; | |
| [_delegate videoCallView:self | |
| shouldSwitchCameraWithCompletion:^(NSError *error) { | |
| dispatch_async(dispatch_get_main_queue(), ^(void) { | |
| sender.enabled = true; | |
| }); | |
| }]; | |
| } | |
| - (void)onRouteChange:(UIButton *)sender { | |
| sender.enabled = false; | |
| __weak ARDVideoCallView *weakSelf = self; | |
| [_delegate videoCallView:self | |
| shouldChangeRouteWithCompletion:^(void) { | |
| ARDVideoCallView *strongSelf = weakSelf; | |
| if (strongSelf) { | |
| dispatch_async(dispatch_get_main_queue(), ^(void) { | |
| sender.enabled = true; | |
| }); | |
| } | |
| }]; | |
| } | |
| - (void)onHangup:(id)sender { | |
| [_delegate videoCallViewDidHangup:self]; | |
| } | |
| - (void)didTripleTap:(UITapGestureRecognizer *)recognizer { | |
| [_delegate videoCallViewDidEnableStats:self]; | |
| } | |
| @end |