Полноэкранный режим: AVPlayerLayer не изменяется при изменении размера родительского представления
Я разрабатываю видеопроигрыватель с AVPlayer API из AV Foundation в MonoTouch (но решение в objective-c тоже может быть приятным). Я пытаюсь реализовать полноэкранный режим.
Чтобы отобразить видеофрагменты, у меня есть UIView (пусть вызывается просмотр воспроизведения), где я добавил AVPlayerLayer в качестве подсмотра слоя просмотра воспроизведения:
UIView *playbackView = ...
AVPlayerLayer *playerLayer = ...
[playbackView.layer addSublayer:playerLayer];
свойства слоя задаются следующим образом:
playerLayer.needsDisplayOnBoundsChange = YES;
playbackView.layer.needsDisplayOnBoundsChange = YES;
чтобы быть уверенным, что они изменены, если размер вида воспроизведения изменен. Первоначально представление воспроизведения имеет координаты (0, 0, 320, 180) (отображается только в верхней части пользовательского интерфейса). Затем я делаю полноэкранную анимацию, устанавливая размер кадра просмотра как размер окна:
playbackView.frame = playbackView.window.bounds;
Он отлично работает. Теперь представление воспроизведения заполняет все окно (установите цвет фона, чтобы увидеть его). Но мой AVPlayerLayer по-прежнему остается в верхней части представления, как раньше, в не полноэкранном режиме.
Почему уровень AVPlayer не изменяется в соответствии с новым видом просмотра? Нужно ли делать анимацию на AVPlayerLayer? Обновление с помощью setNeedsLayout, layoutSubviews (похоже, что-то не меняет...)? Удалить AVPlayerLayer и добавить его снова?
Ответы
Ответ 1
Не добавляйте в качестве подуровня. Просто используйте layerViewView как AVPlayerLayer, например:
+ (Class)layerClass {
return [AVPlayerLayer class];
}
- (AVPlayer*)player {
return [(AVPlayerLayer *)[self layer] player];
}
- (void)setPlayer:(AVPlayer *)player {
[(AVPlayerLayer *)[self layer] setPlayer:player];
}
См. Руководство по программированию AV Foundation - Просмотр Player
Ответ 2
Если вы добавите AVPlayerLayer в качестве подуровня, вам необходимо обновить его фрейм
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
self.avPlayerLayer.frame = self.movieContainerView.bounds;
}
Ответ 3
Существует лучший способ добиться этого. Вот AVPlayerLayer
-backed view, который пытается сохранить соотношение сторон видео. Просто используйте 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
}
}
}
}
}