15

I am developing a video player with the AVPlayer API from AV Foundation in MonoTouch (but a solution in objective-c could be nice too). I am trying to implement a fullscreen mode.

To display the video frames, I have a UIView (let's call it playback view) where I added the AVPlayerLayer as subview of the playback view layer:

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

the layer properties are set like that:

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

to be sure that they are resized if the playback view size is changed. Initially, the playback view has the coordinates (0, 0, 320, 180) (only displayed at the top of the UI). Then, I am doing a fullscreen animation by setting the playback view frame size as being the window size:

playbackView.frame = playbackView.window.bounds;

It's works fine. The playback view is now filling all the window (set a background color to see it). But my AVPlayerLayer is still staying at the top of the view like previously in non-fullscreen mode.

Why the AVPlayer layer is not resized according to the playback view new size? Do I need to make an animation on the AVPlayerLayer as well? A refresh with setNeedsLayout, layoutSubviews (it doesn't seems to change something...)? Remove the AVPlayerLayer and add it again?

nicolas
  • 489
  • 1
  • 3
  • 22
  • One solution is to resize also the layer frame according to the playback view frame during the animation (playerLayer.frame = playbackView.frame). But if we do that, the layer is not animated but directly displayed with the final size. How we can animate also this change? Use Core Animation instead of UIView animation? – nicolas Mar 02 '12 at 07:55

3 Answers3

27

Don't add as sublayer. Just use playbackView's layer as AVPlayerLayer, like this:

+ (Class)layerClass {
    return [AVPlayerLayer class];
}
- (AVPlayer*)player {
    return [(AVPlayerLayer *)[self layer] player];
}
- (void)setPlayer:(AVPlayer *)player {
    [(AVPlayerLayer *)[self layer] setPlayer:player];
}

See AV Foundation Programming Guide - The Player View

Jin
  • 1,671
  • 1
  • 14
  • 13
  • What if you need to add as a sublayer? For example, using a layer-hosted view that contains many sublayers, including an AVPlayerLayer. – Dalmazio Aug 21 '15 at 02:28
  • 4
    Apple updated their AVPlayer example with a version that demonstrates how to implement this using swift: https://developer.apple.com/library/prerelease/ios/samplecode/AVFoundationSimplePlayer-iOS/Listings/Swift_AVFoundationSimplePlayer_iOS_PlayerView_swift.html#//apple_ref/doc/uid/TP40016103-Swift_AVFoundationSimplePlayer_iOS_PlayerView_swift-DontLinkElementID_14 – morgman Mar 04 '16 at 19:22
  • Any idea how to do this with an NSView? NSView doesn't have `layerClass` – iluvcapra Aug 28 '16 at 21:03
  • @iluvcapra On OS X, CALayer has `autoresizingMask` property and NSView can resize sublayers automatically. So for resizing, you can just add player's layer as sublayer of a NSView and set the autoresizingMask to `kCALayerWidthSizable|kCALayerHeightSizable`. Another step is turn on core animations support for that view: Check the checkbox in View Effects inspector for nib file, or set `wantsLayer` property to `YES` programatically. see https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreAnimation_guide/SettingUpLayerObjects/SettingUpLayerObjects.html – Jin Aug 29 '16 at 11:23
7

In case you add AVPlayerLayer as sublayer, you need to update its frame

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    self.avPlayerLayer.frame = self.movieContainerView.bounds;
}
onmyway133
  • 45,645
  • 31
  • 257
  • 263
0

There is a better way to achieve this. Here is AVPlayerLayer-backed view, that is trying to keep video aspect ratio. Just use AutoLayout as usually.

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

And the same solution on 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
                }
            }
        }
    }
}
k06a
  • 17,755
  • 10
  • 70
  • 110