15

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
  • 37,241
  • 25
  • 195
  • 267
Andrey Banshchikov
  • 1,558
  • 2
  • 15
  • 27

8 Answers8

36

In iOS 7.0 added new feature:

AVPlayerLayer has property videoRect.

Andrey Banshchikov
  • 1,558
  • 2
  • 15
  • 27
  • 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
  • 5
    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
7

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,558
  • 2
  • 15
  • 27
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,558
  • 2
  • 15
  • 27
  • 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
  • 819
  • 2
  • 11
  • 22
  • I see two problems: this only returns the size, and we need the whole frame. And AVPlayerView is macOS only, maybe changing that for UIView is ok for iOS. – Jonny Jun 30 '22 at 10:12
0

Andrey's answer worked for file/local/downloaded video, but didn't work for streamed HLS video. As a 2nd attempt, I was able to find the video track right inside of the "tracks" property of currentItem. Also rewritten to Swift.

private func videoRect() -> CGRect {
    // Based on https://stackoverflow.com/a/40164496 - originally objective-c
            
    var trackTop: AVAssetTrack? = nil
    
    if let track1 = self.player?.currentItem?.asset.tracks(withMediaType: AVMediaType.video).first {
        trackTop = track1
    }
    else {
        // For some reason the above way wouldn't find the "track" for streamed HLS video.
        // This seems to work for streamed HLS.
        if let tracks = self.player?.currentItem?.tracks {
            for avplayeritemtrack in tracks {
                if let assettrack = avplayeritemtrack.assetTrack {
                    if assettrack.mediaType == .video {
                        // Found an assetTrack here?
                        trackTop = assettrack
                        break
                    }
                }
            }
        }
    }
    
    guard let track = trackTop else {
        print("Failed getting track")
        return CGRect.zero
    }
    
    let trackSize = track.naturalSize
    let videoViewSize = self.view.bounds.size

    let trackRatio = trackSize.width / trackSize.height
    let videoViewRatio = videoViewSize.width / videoViewSize.height
    
    let newSize: CGSize
    
    if videoViewRatio > trackRatio {
        newSize = CGSize.init(width: trackSize.width * videoViewSize.height / trackSize.height, height: videoViewSize.height)
    }
    else {
        newSize = CGSize.init(width: videoViewSize.width, height: trackSize.height * videoViewSize.width / trackSize.width)
    }

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

    return CGRect.init(x: newX, y: newY, width: newSize.width, height: newSize.height)
}

Much simpler approach

I found another way. Seems to be much easier, if you're using AVPlayerViewController. Why didn't I find this earlier.

return self.videoBounds
Mamad Farrahi
  • 394
  • 1
  • 5
  • 20
Jonny
  • 15,955
  • 18
  • 111
  • 232
0

2022 SwiftUI

struct PlayerViewController: UIViewControllerRepresentable {
    
    private let avController =  AVPlayerViewController()
    var videoURL: URL?
    private var player: AVPlayer {
        return AVPlayer(url: videoURL!)
    }
    
    //  HERE 
    func getVideoFrame() -> CGRect {
        self.avController.videoBounds
    }
    //  HERE 

    func makeUIViewController(context: Context) -> AVPlayerViewController {
        avController.modalPresentationStyle = .fullScreen
        avController.player = player
        avController.player?.play()
        return avController
    }

    func updateUIViewController(_ playerController: AVPlayerViewController, context: Context) {}
}
Mamad Farrahi
  • 394
  • 1
  • 5
  • 20