AVPlayer layer inside a view does not resize when UIView frame changes

Objective CUiviewCalayerAvplayer

Objective C Problem Overview


I have a UIView which contains an AVPlayer to show a video. When changing orientation, I need to change the size and location of the video.

I'm not very experienced with resizing layers, so I'm having problems making the video resize.

I start by creating the AVPlayer and adding its player to my videoHolderView's layer:

NSURL *videoURL = [NSURL fileURLWithPath:videoPath];
self.avPlayer = [AVPlayer playerWithURL:videoURL];

AVPlayerLayer* playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer];
playerLayer.frame = videoHolderView.bounds;
playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
playerLayer.needsDisplayOnBoundsChange = YES;

[videoHolderView.layer addSublayer:playerLayer];
videoHolderView.layer.needsDisplayOnBoundsChange = YES;

Then, at a later point, I change the size and location of the videoHolderView's frame:

[videoHolderView setFrame:CGRectMake(0, 0, 768, 502)];

At this point, I need the avPlayer to resize to these same dimension. This doesn't happen automatically - the avPlayer stays at it's small size within the videoHolderView.

If anyone can help, I'd really appreciate any advice.

Thanks guys.

Objective C Solutions


Solution 1 - Objective C

  playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFit;

Solution 2 - Objective C

Converting @Suran's solution to Swift:

First, create a class inheriting UIView where you override only 1 variable:

import UIKit
import AVFoundation

class AVPlayerView: UIView {
    override class var layerClass: AnyClass {
        return AVPlayerLayer.self
    }
}

Then add a simple UIView using the interface builder and change its class to the one you just created: AVPlayerView

change class of regular UIView to AVPlayerView

Then, make an outlet for that view. Call it avPlayerView

@IBOutlet weak var avPlayerView: AVPlayerView!

Now, you can use that view inside your viewcontroller and access its avlayer like this:

let avPlayer = AVPlayer(url: video)
let castLayer = avPlayerView.layer as! AVPlayerLayer
castLayer.player = avPlayer
avPlayer.play()

The layer will now follow the constraints just like a regular layer would do. No need to manually change bounds or sizes.

Solution 3 - Objective C

Actually you shouldn't add AVPlayer's layer as a sublayer. Instead of that you should use the following method, in the subclass of view in which you want to display AVPlayer.

+ (Class)layerClass
{
     return [AVPlayerLayer class];
}

And use the following line to add(set) the player layer.

[(AVPlayerLayer *)self.layer setPlayer:self.avPlayer];

Hope it helps;-)

Solution 4 - Objective C

In the end I solved this by re-adding the AVPlayerLayer to the UIView. I'm not sure why changing the frame removed the layer, but it did. Here's the final solution:

//amend the frame of the view
[videoHolderView setFrame:CGRectMake(0, 0, 768, 502)];

//reset the layer's frame, and re-add it to the view
AVPlayerLayer* playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer];
playerLayer.frame = videoHolderView.bounds;
[videoHolderView.layer addSublayer:playerLayer];

Solution 5 - Objective C

Found a great article by Marco Santarossa that shows multiple approaches to fixing. https://marcosantadev.com/calayer-auto-layout-swift/

I used his first suggestion to reset the layer frame during viewDidLayoutSubViews() event.

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    playerLayer.frame = view.layer.bounds
}

Solution 6 - Objective C

You can change the layer's frame by overriding the -layoutSubviews method:

- (void)layoutSubviews
{
    [super layoutSubviews];
    self.playerLayer.frame = self.bounds;
}

Solution 7 - Objective C

In Swift 4 use

    playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill

or whichever property of AVLayerVideoGravity you desire.

Solution 8 - Objective C

theDunc's answer did not work for me. I found a solution that is more simple: I just needed to adjust the frame of the AVPlayerLayer after changing it in the UIView:

avPlayerLayer.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);

In this blog is stated, that a View's frame also affect that of the layer in it.

> When you change a view’s frame, it’s simply changing the layer’s frame.

For this case, it is not true.

Solution 9 - Objective C

I had this problem in Swift 2.3, and I solved writing a proper PlayerView class and setting it as subview:

import UIKit
import AVKit
import AVFoundation

class PlayerPreviewView: UIView {

    override class func layerClass() -> AnyClass {
        return AVPlayerLayer.self
    }
    
    var player: AVPlayer? {
        get {
            return playerLayer.player
        }
        
        set {
            playerLayer.player = newValue
        }
    }
    
    var playerLayer: AVPlayerLayer {
        return layer as! AVPlayerLayer
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        playerLayer.videoGravity = AVLayerVideoGravityResize
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        playerLayer.videoGravity = AVLayerVideoGravityResize
    }

}

In the presenting ViewController:

private func play(asset asset: AVURLAsset){
    let playerItem = AVPlayerItem(asset: asset)
    
    player = AVPlayer(playerItem: playerItem)
    player?.actionAtItemEnd = .None
    player?.muted = true

    playerPreviewView = PlayerPreviewView(frame: CGRectZero)
    view.addSubview(playerPreviewView)
    playerPreviewView.player = player
    
    
    playerPreviewView.translatesAutoresizingMaskIntoConstraints = false
    view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[subview]-0-|", options: .DirectionLeadingToTrailing, metrics: nil, views: ["subview": playerPreviewView]))
    view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[subview]-0-|", options: .DirectionLeadingToTrailing, metrics: nil, views: ["subview": playerPreviewView]))


    player?.play()
    
    NSNotificationCenter.defaultCenter().addObserver(self,
                                                     selector: #selector(SplashScreenViewController.videoDidFinish),
                                                     name: AVPlayerItemDidPlayToEndTimeNotification,
                                                     object: nil)
}

Solution 10 - Objective C

First step: check out UIView's autoresizing property in UIVIew.h

> @property(nonatomic) UIViewAutoresizing autoresizingMask; // simple resize. default is UIViewAutoresizingNone

This property correspondents to the "springs and struts" controls in IB, though it will take you some experimentation to get the results you want.

Solution 11 - Objective C

To get to playerLayer, you need to loop through videoHolderView.layer.sublayers and change each one.

this is what I did in Swift 2.2

if let sublayers = videoHolderView.layer.sublayers{
    for layer in sublayers where layer is AVPlayerLayer{
        layer.frame.size = ... // change size of the layer here
    }
}

Solution 12 - Objective C

For those of you who are only concerned with resizing the AVPlayer during device rotation, you can alter your layer frame in the viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) method as shown below.

//Swift 4
//Method in UIViewController
//Variable definitions are:
//self.layer is the AVPlayerLayer
//self.videoPlayerView is the view that the AVPlayerLayer is the sublayer of
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
    coordinator.animate(alongsideTransition: { context in
        self.layer.layoutIfNeeded()
        UIView.animate(
            withDuration: context.transitionDuration,
            animations: {
                self.layer.frame = self.videoPlayerView.bounds
            }
        )
    }, completion: nil)
}

This will animate your layer frame along with all the other views during the rotation.

Solution 13 - Objective C

Any one searching for Xamarin Version as i was searching

don't add the AVPlayerLayer as sublayer but set the layer to Avplayer Layer

[Export("layerClass")]
public static Class LayerClass()
{
    return new Class(typeof(AVPlayerLayer));
}

The following line set the layer

(this.Layer as AVPlayerLayer).Player = _player;   // Acplayer

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiontheDuncsView Question on Stackoverflow
Solution 1 - Objective CMarkinsonView Answer on Stackoverflow
Solution 2 - Objective CSamView Answer on Stackoverflow
Solution 3 - Objective CSuranView Answer on Stackoverflow
Solution 4 - Objective CtheDuncsView Answer on Stackoverflow
Solution 5 - Objective CPaulMJaxView Answer on Stackoverflow
Solution 6 - Objective CDave BattonView Answer on Stackoverflow
Solution 7 - Objective Cuser1300214View Answer on Stackoverflow
Solution 8 - Objective CFlorian GösseleView Answer on Stackoverflow
Solution 9 - Objective CGiordano ScalzoView Answer on Stackoverflow
Solution 10 - Objective CJay WardellView Answer on Stackoverflow
Solution 11 - Objective CMisha KouznetsovView Answer on Stackoverflow
Solution 12 - Objective CjikigiView Answer on Stackoverflow
Solution 13 - Objective CFahad RehmanView Answer on Stackoverflow