62

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.

Jay Bhalani
  • 4,142
  • 8
  • 37
  • 50
theDuncs
  • 4,649
  • 4
  • 39
  • 63

13 Answers13

66
  playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFit;
Markinson
  • 857
  • 1
  • 7
  • 13
40

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.

Sam
  • 5,375
  • 2
  • 45
  • 54
21

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;-)

Suran
  • 1,209
  • 1
  • 17
  • 21
16

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];
theDuncs
  • 4,649
  • 4
  • 39
  • 63
  • I'm not sure if you need to remove and re-add it, but I've done this by setting that layer's frame to the holder view's bounds in the past. – Tim Johnsen Apr 13 '15 at 22:50
  • i applied to this way to my project and it's working great. Compared with AVPlayerViewController and AVPlayerController, it has better performance and no problem of autolayout warning issue when your project still use autoresizing. – LiangWang Oct 24 '16 at 05:38
15

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
}
PaulMJax
  • 216
  • 2
  • 6
10

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

- (void)layoutSubviews
{
    [super layoutSubviews];
    self.playerLayer.frame = self.bounds;
}
Dave Batton
  • 8,795
  • 1
  • 46
  • 50
8

In Swift 4 use

    playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill

or whichever property of AVLayerVideoGravity you desire.

3

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.

Florian Gössele
  • 4,376
  • 7
  • 25
  • 49
  • This was the solution for me. Now I set the AVPlayerLayer after changing the superLayer. The resize now is ok. Thank you. +1 – Lucio Fonseca Mar 17 '15 at 13:27
2

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)
}
Giordano Scalzo
  • 6,312
  • 3
  • 31
  • 31
1

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.

Jay Wardell
  • 2,230
  • 1
  • 14
  • 13
  • Jay! Thanks! I've tried changing it to various things but no matter what I set it to, it doesn't seem to make a difference to the video. Do I need to call another function after changing the view size to force the resizing? – theDuncs Apr 12 '12 at 15:41
  • Are you calling [videoHolderView setNeedsLayout] somewhere? – Jay Wardell Apr 12 '12 at 15:53
  • I wasn't calling it anywhere, no. I just tried calling it after changing the view frame and still no difference. – theDuncs Apr 12 '12 at 15:56
  • 3
    I'm sorry, I was reading your code as AVPlayer, not AVPlayerLayer. You're going to have to change the layer's bounds explicitly when you change the frame of videoHolderView. – Jay Wardell Apr 12 '12 at 16:42
  • Jay - thanks so so much for your help. I really appreciate this. I tried doing that like this: `AVPlayerLayer* playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer]; playerLayer.frame = videoHolderView.bounds;` however when I do this, the video doesn't display on the device. Should I remove and add it from the view's layer? – theDuncs Apr 13 '12 at 08:30
  • Jay - in the end the trick was to re-add the playerLayer to the view. I don't know why. Thanks so much for all your help. You're a star. – theDuncs Apr 17 '12 at 17:19
1

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
    }
}
Athena
  • 3,200
  • 3
  • 27
  • 35
1

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.

jikigi
  • 176
  • 2
  • 5
0

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
Fahad Rehman
  • 1,189
  • 11
  • 25