// // Copyright (c) 2017 Open Whisper Systems. All rights reserved. // #import "RemoteVideoView.h" #import "UIFont+OWS.h" #import "UIView+OWS.h" #import #import #import #import #import #import NS_ASSUME_NONNULL_BEGIN // As of RTC M61, iOS8 crashes when ending calls while de-alloc'ing the EAGLVideoView. // WebRTC doesn't seem to support iOS8 - e.g. their Podfile requires iOS9+, and they // unconditionally require MetalKit on a 64bit iOS8 device (which crashes). // Until WebRTC supports iOS8, we show a "upgrade iOS to see remote video" view // to our few remaining iOS8 users @interface NullVideoRenderer : UIView @end @implementation NullVideoRenderer - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (!self) { return self; } self.backgroundColor = UIColor.blackColor; UILabel *label = [UILabel new]; label.numberOfLines = 0; label.text = NSLocalizedString(@"CALL_REMOTE_VIDEO_DISABLED", @"Text shown on call screen in place of remote video"); label.textAlignment = NSTextAlignmentCenter; label.font = [UIFont ows_boldFontWithSize:ScaleFromIPhone5(20)]; label.textColor = UIColor.whiteColor; label.lineBreakMode = NSLineBreakByWordWrapping; [self addSubview:label]; [label autoVCenterInSuperview]; [label autoPinWidthToSuperviewWithMargin:ScaleFromIPhone5(16)]; return self; } #pragma mark - RTCVideoRenderer /** The size of the frame. */ - (void)setSize:(CGSize)size { // Do nothing. } /** The frame to be displayed. */ - (void)renderFrame:(nullable RTCVideoFrame *)frame { // Do nothing. } @end @interface RemoteVideoView () @property (nonatomic, readonly) __kindof UIView *videoRenderer; // Used for legacy EAGLVideoView @property (nullable, nonatomic) NSMutableArray *remoteVideoConstraints; @end @implementation RemoteVideoView - (instancetype)init { self = [super init]; if (!self) { return self; } // On iOS8: prints a message saying the feature is unavailable. if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(9, 0)) { _videoRenderer = [NullVideoRenderer new]; [self addSubview:_videoRenderer]; [_videoRenderer autoPinEdgesToSuperviewEdges]; } // Currently RTC only supports metal on 64bit machines #if defined(RTC_SUPPORTS_METAL) // On 64-bit, iOS9+: uses the MetalKit backed view for improved battery/rendering performance. if (_videoRenderer == nil) { // It is insufficient to check the RTC_SUPPORTS_METAL macro to determine Metal support. // RTCMTLVideoView requires the MTKView class, available only in iOS9+ // So check that it exists before proceeding. if ([MTKView class]) { _videoRenderer = [[RTCMTLVideoView alloc] initWithFrame:CGRectZero]; [self addSubview:_videoRenderer]; [_videoRenderer autoPinEdgesToSuperviewEdges]; // HACK: Although RTCMTLVideo view is positioned to the top edge of the screen // It's inner (private) MTKView is below the status bar. for (UIView *subview in [_videoRenderer subviews]) { if ([subview isKindOfClass:[MTKView class]]) { [NSLayoutConstraint autoSetPriority:UILayoutPriorityRequired forConstraints:^{ [subview autoPinEdgesToSuperviewEdges]; }]; } else { OWSFail(@"New subviews added to MTLVideoView. Reconsider this hack."); } } } } #elif defined(__arm64__) // Canary incase the upstream RTC_SUPPORTS_METAL macro changes semantics OWSFail(@"should only use legacy video view on 32bit systems"); #endif // On 32-bit iOS9+ systems, use the legacy EAGL backed view. if (_videoRenderer == nil) { RTCEAGLVideoView *eaglVideoView = [RTCEAGLVideoView new]; eaglVideoView.delegate = self; _videoRenderer = eaglVideoView; [self addSubview:_videoRenderer]; // Pinning legacy RTCEAGL view discards aspect ratio. // So we have a more verbose layout in the RTCEAGLVideoViewDelegate methods // [_videoRenderer autoPinEdgesToSuperviewEdges]; } // We want the rendered video to go edge-to-edge. _videoRenderer.layoutMargins = UIEdgeInsetsZero; return self; } #pragma mark - RTCVideoRenderer /** The size of the frame. */ - (void)setSize:(CGSize)size { [self.videoRenderer setSize:size]; } #pragma mark - RTCEAGLVideoViewDelegate - (void)videoView:(RTCEAGLVideoView *)videoView didChangeVideoSize:(CGSize)remoteVideoSize { AssertIsOnMainThread(); if (remoteVideoSize.height <= 0) { OWSFail(@"Illegal video height: %f", remoteVideoSize.height); return; } CGFloat aspectRatio = remoteVideoSize.width / remoteVideoSize.height; DDLogVerbose(@"%@ Remote video size: width: %f height: %f ratio: %f", self.logTag, remoteVideoSize.width, remoteVideoSize.height, aspectRatio); UIView *containingView = self.superview; if (containingView == nil) { DDLogDebug(@"%@ Cannot layout video view without superview", self.logTag); return; } if (![self.videoRenderer isKindOfClass:[RTCEAGLVideoView class]]) { OWSFail(@"%@ Unexpected video renderer: %@", self.logTag, self.videoRenderer); return; } [NSLayoutConstraint deactivateConstraints:self.remoteVideoConstraints]; NSMutableArray *constraints = [NSMutableArray new]; if (remoteVideoSize.width > 0 && remoteVideoSize.height > 0 && containingView.bounds.size.width > 0 && containingView.bounds.size.height > 0) { // to approximate "scale to fill" contentMode // - Pin aspect ratio // - Width and height is *at least* as wide as superview [constraints addObject:[videoView autoPinToAspectRatio:aspectRatio]]; [constraints addObject:[videoView autoSetDimension:ALDimensionWidth toSize:containingView.width relation:NSLayoutRelationGreaterThanOrEqual]]; [constraints addObject:[videoView autoSetDimension:ALDimensionHeight toSize:containingView.height relation:NSLayoutRelationGreaterThanOrEqual]]; [constraints addObjectsFromArray:[videoView autoCenterInSuperview]]; // Low priority constraints force view to be no larger than necessary. [NSLayoutConstraint autoSetPriority:UILayoutPriorityDefaultLow forConstraints:^{ [constraints addObjectsFromArray:[videoView autoPinEdgesToSuperviewEdges]]; }]; } else { [constraints addObjectsFromArray:[videoView autoPinEdgesToSuperviewEdges]]; } self.remoteVideoConstraints = constraints; // We need to force relayout to occur immediately (and not // wait for a UIKit layout/render pass) or the remoteVideoView // (which presumably is updating its CALayer directly) will // ocassionally appear to have bad frames. [videoView setNeedsLayout]; [[videoView superview] setNeedsLayout]; [videoView layoutIfNeeded]; [[videoView superview] layoutIfNeeded]; } /** The frame to be displayed. */ - (void)renderFrame:(nullable RTCVideoFrame *)frame { [self.videoRenderer renderFrame:frame]; } @end NS_ASSUME_NONNULL_END