14

I have PlayerView class for displaying AVPlayer's playback. Code from documentation.

#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>

@interface PlayerView : UIView
@property (nonatomic) AVPlayer *player;
@end

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

I set up my AVPlayer (contains video asset with size 320x240) in this PlayerView (with frame.size.width = 100, frame.size.height = 100) and my video is resized. How can i get size of video after adding in PlayerView?

Cœur
  • 34,719
  • 24
  • 185
  • 251
Andrey Banshchikov
  • 1,522
  • 2
  • 15
  • 26

6 Answers6

35

In iOS 7.0 added new feature:

AVPlayerLayer has property videoRect.

Andrey Banshchikov
  • 1,522
  • 2
  • 15
  • 26
  • 16
    For me, videoRect just returns CGRectZero even though I can see the video on screen. I'm using iOS 8 – tettoffensive Aug 11 '15 at 18:17
  • wait for the currentItem.status to == AVPlayerStatusReadyToPlay, and then you will get the frame – cohen72 May 08 '16 at 15:16
  • 1
    This doesn't work, even I have added KVO to wait for the status to be AVPlayerStatusReadyToPlay. The videoRect still returns all zeros – codingrhythm May 25 '16 at 00:47
  • 4
    After another try, it works. So you need to KVO on the AVPlayerItem, not AVPlayer, and the status to check is AVPlayerItemStatus.ReadyToPlay, not AVPlayerStatusReadyToPlay. This approach is correct, but the comment by @Yocoh is a bit misleading. – codingrhythm May 25 '16 at 00:52
  • That works for aspect `fit` but not for aspect `fill`. According to the [docs](https://developer.apple.com/documentation/avfoundation/avplayerlayer/1385745-videorect), this is the "display rect of the video image _within_ the layer’s bounds" – Gobe Oct 25 '18 at 18:06
6

This worked for me. When you don't have the AVPLayerLayer.

- (CGRect)videoRect {
    // @see http://stackoverflow.com/a/6565988/1545158

    AVAssetTrack *track = [[self.player.currentItem.asset tracksWithMediaType:AVMediaTypeVideo] firstObject];
    if (!track) {
        return CGRectZero;
    }

    CGSize trackSize = [track naturalSize];
    CGSize videoViewSize = self.videoView.bounds.size;

    CGFloat trackRatio = trackSize.width / trackSize.height;
    CGFloat videoViewRatio = videoViewSize.width / videoViewSize.height;

    CGSize newSize;

    if (videoViewRatio > trackRatio) {
        newSize = CGSizeMake(trackSize.width * videoViewSize.height / trackSize.height, videoViewSize.height);
    } else {
        newSize = CGSizeMake(videoViewSize.width, trackSize.height * videoViewSize.width / trackSize.width);
    }

    CGFloat newX = (videoViewSize.width - newSize.width) / 2;
    CGFloat newY = (videoViewSize.height - newSize.height) / 2;

    return CGRectMake(newX, newY, newSize.width, newSize.height);

}
Andrey Banshchikov
  • 1,522
  • 2
  • 15
  • 26
5

Found a solution:

Add to PlayerView class:

- (CGRect)videoContentFrame {
    AVPlayerLayer *avLayer = (AVPlayerLayer *)[self layer];
    // AVPlayerLayerContentLayer
    CALayer *layer = (CALayer *)[[avLayer sublayers] objectAtIndex:0];
    CGRect transformedBounds = CGRectApplyAffineTransform(layer.bounds, CATransform3DGetAffineTransform(layer.sublayerTransform));
    return transformedBounds;
}
Andrey Banshchikov
  • 1,522
  • 2
  • 15
  • 26
  • 1
    It's not working for me in Xcode4.5, iOS6 sdk, build target 4.3. get size (0,0). Is it still working for you now? – Mingming Oct 08 '12 at 08:19
4

Here's the solution that's working for me, takes into account the positioning of the AVPlayer within the view. I just added this to the PlayerView custom class. I had to solve this because doesn't appear that videoRect is working in 10.7.

- (NSRect) videoRect {
    NSRect theVideoRect = NSMakeRect(0,0,0,0);
    NSRect theLayerRect = self.playerLayer.frame;

    NSSize theNaturalSize = NSSizeFromCGSize([[[self.movie asset] tracksWithMediaType:AVMediaTypeVideo][0] naturalSize]);
    float movieAspectRatio = theNaturalSize.width/theNaturalSize.height;
    float viewAspectRatio = theLayerRect.size.width/theLayerRect.size.height;
    if (viewAspectRatio < movieAspectRatio) {
        theVideoRect.size.width = theLayerRect.size.width;
        theVideoRect.size.height = theLayerRect.size.width/movieAspectRatio;
        theVideoRect.origin.x = 0;
        theVideoRect.origin.y = (theLayerRect.size.height/2) - (theVideoRect.size.height/2);
}
    else if (viewAspectRatio > movieAspectRatio) {

        theVideoRect.size.width = movieAspectRatio * theLayerRect.size.height;
        theVideoRect.size.height = theLayerRect.size.height;
        theVideoRect.origin.x = (theLayerRect.size.width/2) - (theVideoRect.size.width/2);
        theVideoRect.origin.y = 0;
    }
    return theVideoRect;
}
1

Here is Eric Badros' answer ported to iOS. I also added preferredTransform handling. This assumes _player is an AVPlayer

- (CGRect) videoRect {
    CGRect theVideoRect = CGRectZero;

    // Replace this with whatever frame your AVPlayer is playing inside of:
    CGRect theLayerRect = self.playerLayer.frame;

    AVAssetTrack *track = [_player.currentItem.asset tracksWithMediaType:AVMediaTypeVideo][0];
    CGSize theNaturalSize = [track naturalSize];
    theNaturalSize = CGSizeApplyAffineTransform(theNaturalSize, track.preferredTransform);
    theNaturalSize.width = fabs(theNaturalSize.width);
    theNaturalSize.height = fabs(theNaturalSize.height);

    CGFloat movieAspectRatio = theNaturalSize.width / theNaturalSize.height;
    CGFloat viewAspectRatio = theLayerRect.size.width / theLayerRect.size.height;

    // Note change this *greater than* to a *less than* if your video will play in aspect fit mode (as opposed to aspect fill mode)
    if (viewAspectRatio > movieAspectRatio) {
        theVideoRect.size.width = theLayerRect.size.width;
        theVideoRect.size.height = theLayerRect.size.width / movieAspectRatio;
        theVideoRect.origin.x = 0;
        theVideoRect.origin.y = (theLayerRect.size.height - theVideoRect.size.height) / 2;
    } else if (viewAspectRatio < movieAspectRatio) {
        theVideoRect.size.width = movieAspectRatio * theLayerRect.size.height;
        theVideoRect.size.height = theLayerRect.size.height;
        theVideoRect.origin.x = (theLayerRect.size.width - theVideoRect.size.width) / 2;
        theVideoRect.origin.y = 0;
    }
    return theVideoRect;
}
Community
  • 1
  • 1
0

Here is a Swift solution based on Andrey Banshchikov's answer. His solution is especially useful when you don't have access to AVPLayerLayer.

func currentVideoFrameSize(playerView: AVPlayerView, player: AVPlayer) -> CGSize {
    // See https://stackoverflow.com/a/40164496/1877617
    let track = player.currentItem?.asset.tracks(withMediaType: .video).first
    if let track = track {
        let trackSize      = track.naturalSize
        let videoViewSize  = playerView.bounds.size
        let trackRatio     = trackSize.width / trackSize.height
        let videoViewRatio = videoViewSize.width / videoViewSize.height
        
        var newSize: CGSize

        if videoViewRatio > trackRatio {
            newSize = CGSize(width: trackSize.width * videoViewSize.height / trackSize.height, height: videoViewSize.height)
        } else {
            newSize = CGSize(width: videoViewSize.width, height: trackSize.height * videoViewSize.width / trackSize.width)
        }
        
        return newSize
    }
    return CGSize.zero
}
ThePedestrian
  • 770
  • 2
  • 9
  • 22