全屏:调整其父视图的大小时不调整 AVPlayerLayer 的大小

Posted

技术标签:

【中文标题】全屏:调整其父视图的大小时不调整 AVPlayerLayer 的大小【英文标题】:Fullscreen: AVPlayerLayer is not resized when resizing its parent view 【发布时间】:2012-02-29 10:55:53 【问题描述】:

我正在使用 MonoTouch 中的 AV Foundation 的 AVPlayer API 开发视频播放器(但 Objective-c 中的解决方案也可能很好)。我正在尝试实现全屏模式。

为了显示视频帧,我有一个 UIView(我们称之为播放视图),我在其中添加了 AVPlayerLayer 作为播放视图层的子视图:

UIView *playbackView = ...
AVPlayerLayer *playerLayer = ...
[playbackView.layer addSublayer:playerLayer];

图层属性设置如下:

playerLayer.needsDisplayOnBoundsChange = YES;
playbackView.layer.needsDisplayOnBoundsChange = YES;

以确保在播放视图大小发生更改时调整它们的大小。最初,播放视图的坐标为 (0, 0, 320, 180)(仅显示在 UI 顶部)。然后,我通过将播放视图帧大小设置为窗口大小来制作全屏动画:

playbackView.frame = playbackView.window.bounds;

它工作正常。播放视图现在填充了所有窗口(设置背景颜色以查看它)。但是我的 AVPlayerLayer 仍然像以前在非全屏模式下一样停留在视图的顶部。

为什么AVPlayer层没有根据播放视图的新大小调整大小?我还需要在 AVPlayerLayer 上制作动画吗?用 setNeedsLayout、layoutSubviews 刷新(它似乎没有改变什么......)?把AVPlayerLayer去掉再重新添加?

【问题讨论】:

一种解决方案是根据动画期间的播放视图帧(playerLayer.frame =playbackView.frame)也调整图层帧的大小。但是如果我们这样做,图层就不是动画的,而是直接以最终大小显示的。我们如何才能动画化这种变化?使用 Core Animation 代替 UIView 动画? 【参考方案1】:

不要添加为子图层。只需将playbackView的图层用作AVPlayerLayer,如下所示:

+ (Class)layerClass 
    return [AVPlayerLayer class];

- (AVPlayer*)player 
    return [(AVPlayerLayer *)[self layer] player];

- (void)setPlayer:(AVPlayer *)player 
    [(AVPlayerLayer *)[self layer] setPlayer:player];

参见AV Foundation Programming Guide - 播放器视图

【讨论】:

如果需要添加为子层怎么办?例如,使用包含许多子图层(包括 AVPlayerLayer)的图层托管视图。 Apple 更新了他们的 AVPlayer 示例,其版本演示了如何使用 swift 实现这一点:developer.apple.com/library/prerelease/ios/samplecode/… 知道如何使用 NSView 执行此操作吗? NSView 没有layerClass @iluvcapra 在 OS X 上,CALayer 有autoresizingMask 属性,NSView 可以自动调整子层的大小。因此,对于调整大小,您只需将播放器层添加为 NSView 的子层并将 autoresizingMask 设置为kCALayerWidthSizable|kCALayerHeightSizable。另一个步骤是为该视图打开核心动画支持:选中视图效果检查器中的复选框以获取 nib 文件,或以编程方式将 wantsLayer 属性设置为 YES。见developer.apple.com/library/ios/documentation/Cocoa/Conceptual/…【参考方案2】:

如果您将 AVPlayerLayer 添加为子图层,则需要更新其框架

- (void)viewDidLayoutSubviews 
    [super viewDidLayoutSubviews];

    self.avPlayerLayer.frame = self.movieContainerView.bounds;

【讨论】:

坏主意。因为是播放器,所以每次指标时间更新时都会派发这个函数 好答案,对我很有帮助 这在 iOS 13 上不起作用。播放器层保持原始大小【参考方案3】:

有更好的方法来实现这一点。这是AVPlayerLayer-backed 视图,它试图保持视频纵横比。像往常一样使用 AutoLayout。

PlayerView.h:

@interface PlayerView : UIView

@property (strong, nonatomic) AVPlayerLayer *layer;

@end

PlayerView.m:

@interface PlayerView ()

@property (strong, nonatomic) NSLayoutConstraint *aspectConstraint;

@end

@implementation PlayerView

@dynamic layer;

+ (Class)layerClass 
    return [AVPlayerLayer class];


- (instancetype)init 
    self = [super init];
    if (self) 
        self.userInteractionEnabled = NO;

        [self addObserver:self forKeyPath:@"layer.videoRect" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew context:nil];
    
    return self;


- (void)dealloc 
    [self removeObserver:self forKeyPath:@"layer.videoRect"];


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context 
    if ([keyPath isEqualToString:@"layer.videoRect"]) 
        CGSize size = [change[NSKeyValueChangeNewKey] CGRectValue].size;
        if (change[NSKeyValueChangeNewKey] && size.width && size.height) 
            self.aspectConstraint.active = NO;
            self.aspectConstraint = [self.widthAnchor constraintEqualToAnchor:self.heightAnchor multiplier:size.width / size.height];
            self.aspectConstraint.priority = UILayoutPriorityDefaultHigh;
            self.aspectConstraint.active = YES;
        
        else 
            self.aspectConstraint.active = NO;
        
    


@end

Swift 上的解决方案相同:

import UIKit
import AVFoundation

class PlayerView : UIView 

    override var layer: AVPlayerLayer 
        return super.layer as! AVPlayerLayer
    

    override class var layerClass: AnyClass 
        return AVPlayerLayer.self
    

    var aspectConstraint: NSLayoutConstraint?

    override init(frame: CGRect) 
        super.init(frame: frame)
        self.isUserInteractionEnabled = false
        self.addObserver(self, forKeyPath:"layer.videoRect", options:[.initial, .new], context:nil)
    

    required init?(coder aDecoder: NSCoder) 
        super.init(coder: aDecoder)
        self.isUserInteractionEnabled = false
        self.addObserver(self, forKeyPath:"layer.videoRect", options:[.initial, .new], context:nil)
    

    deinit 
        self.removeObserver(self, forKeyPath: "layer.videoRect")
    

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) 
        if (keyPath == "layer.videoRect") 
            if let rect = change?[.newKey] as? NSValue 
                let size = rect.cgRectValue.size
                if (size.width > 0 && size.height > 0) 
                    self.aspectConstraint?.isActive = false
                    self.aspectConstraint = self.widthAnchor.constraint(equalTo: self.heightAnchor, multiplier:size.width / size.height)
                    self.aspectConstraint?.priority = UILayoutPriorityDefaultHigh
                    self.aspectConstraint?.isActive = true
                
                else 
                    self.aspectConstraint?.isActive = false
                
            
        
    

【讨论】:

以上是关于全屏:调整其父视图的大小时不调整 AVPlayerLayer 的大小的主要内容,如果未能解决你的问题,请参考以下文章

根据视频的纵横比调整 AVPlayerItem 容器视图的大小

调整移动屏幕大小时不居中对齐

动画调整全屏 UIView 的大小以在屏幕底部显示另一个 UIView

UINavigationController 总是全屏不调整大小

QTextEdit 在调整大小时不显示水平滚动条

WatchOS SwiftUI 动画视图调整父级大小