简体   繁体   中英

How to make a AVPlayerViewController go to fullscreen programmatically?

I'm trying to make a AVPlayerViewController go to full screen mode programmatically, coming from "embedded" mode, however this does not appear to be possible with the published API.

Is there a workaround that I'm missing? I'm interested in obtaining the same animation to the one that you get when the user presses the full screen button on the bottom right of the controls.

Using MPMoviePlayerController is not a viable alternative since I might have more than one video playing at a time.

Thanks.

AVPlayerViewController is a subclass of UIViewController, so it is presentable like any other view controller subclass. Are you able to use presentViewController:animated:completion ?

self.avPlayerController.modalPresentationStyle = UIModalPresentationOverFullScreen;
[self presentViewController:self.avPlayerController animated:YES completion:nil];

This then shows the "Done" button in the top left-hand corner.

Updated for iOS 11

There is no supported way to programmatically go fullscreen with AVPlayerViewController (a bit of an oversight in my opinion).

However, AVPlayerViewController does contain a private method that does exactly that. You'll have to decide for yourself whether you'd want to use it or not given you're not supposed to call private methods.

AVPlayerViewController+Fullscreen.h

#import <AVKit/AVKit.h>

@interface AVPlayerViewController (Fullscreen)

-(void)goFullscreen;

@end

AVPlayerViewController+Fullscreen.m

#import "AVPlayerViewController+Fullscreen.h"

@implementation AVPlayerViewController (Fullscreen)

-(void)goFullscreen {
    NSString *selectorForFullscreen = @"transitionToFullScreenViewControllerAnimated:completionHandler:";
    if (@available(iOS 11.3, *)) {
        selectorForFullscreen = @"transitionToFullScreenAnimated:interactive:completionHandler:";
    } else if (@available(iOS 11.0, *)) {
        selectorForFullscreen = @"transitionToFullScreenAnimated:completionHandler:";
    }
    SEL fsSelector = NSSelectorFromString([@"_" stringByAppendingString:selectorForFullscreen]);
    if ([self respondsToSelector:fsSelector]) {
        NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:fsSelector]];
        [inv setSelector:fsSelector];
        [inv setTarget:self];

        NSInteger index = 2; //arguments 0 and 1 are self and _cmd respectively, automatically set
        BOOL animated = YES;
        [inv setArgument:&(animated) atIndex:index];
        index++;

        if (@available(iOS 11.3, *)) {
            BOOL interactive = YES;
            [inv setArgument:&(interactive) atIndex:index]; //arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation
            index++;
        }

        id completionBlock = nil;
        [inv setArgument:&(completionBlock) atIndex:index];
        [inv invoke];
    }
}

@end

In iOS11 there are 2 new properties for AVPlayerViewController : entersFullScreenWhenPlaybackBegins and exitsFullScreenWhenPlaybackEnds . You can enable full screen mode right after playback begins and disable it when playback ends with these properties. If you need to enable fullscreen mode after some delay you can use private API methods as ToddH mentioned in his answer . However in iOS11 _transitionToFullScreenViewControllerAnimated:completionHandler: method is not available anymore, there is the same method called _transitionToFullScreenAnimated:completionHandler: . The second method accepts the same arguments as the first one.

I can show an example how to use it. First of all you need to create AVPlayerViewController instance in your UIViewController :

private let playerController : AVPlayerViewController = {

    if let urlForPlayer = URL(string: "your_video_url") {

        $0.player = AVPlayer(url: urlForPlayer)
    }
    return $0
} (AVPlayerViewController())

Then you need to setup view for AVPlayerViewController and add it to your current controller view. Function setupAVplayerController can do it for you:

private func setupAVplayerController() {

    self.addChildViewController(self.playerController)
    self.playerController.view.frame = CGRect(x: 0.0, y: 0.0, width: 200.0, height: 200.0)
    self.view.addSubview(self.playerController.view)
    self.playerController.didMove(toParentViewController: self)
}

Function enterFullscreen forces full screen mode for AVPlayerViewController :

private func enterFullscreen(playerViewController:AVPlayerViewController) {

    let selectorName : String = {

        if #available(iOS 11, *) {

            return "_transitionToFullScreenAnimated:completionHandler:"
        } else {

            return "_transitionToFullScreenViewControllerAnimated:completionHandler:"
        }
    }()
    let selectorToForceFullScreenMode = NSSelectorFromString(selectorName)
    if playerViewController.responds(to: selectorToForceFullScreenMode) {

            playerViewController.perform(selectorToForceFullScreenMode, with: true, with: nil)
    }
}

And now you need to call all these functions where you need it, for example in viewDidAppear :

override func viewDidAppear(_ animated: Bool) {

    super.viewDidAppear(animated)

    //Your code

    self.setupAVplayerController()
    self.playerController.player?.play()
    DispatchQueue.main.asyncAfter(deadline: .now() + 10) {

        self.enterFullscreen(playerViewController:self.playerController)
    }
}

Don't forget that this solution based on private API calls that is not recommended to use.

UPDATE: Swift 4 version of ToddH's answer:

private func enterFullscreen(playerViewController: AVPlayerViewController) {

    let selectorName: String = {
        if #available(iOS 11.3, *) {
            return "_transitionToFullScreenAnimated:interactive:completionHandler:"
        } else if #available(iOS 11, *) {
            return "_transitionToFullScreenAnimated:completionHandler:"
        } else {
            return "_transitionToFullScreenViewControllerAnimated:completionHandler:"
        }
    }()
    let selectorToForceFullScreenMode = NSSelectorFromString(selectorName)

    if playerViewController.responds(to: selectorToForceFullScreenMode) {
        playerViewController.perform(selectorToForceFullScreenMode, with: true, with: nil)
    }
}

As a little iOS 14 update to ToddH's answer: the private API to call is enterFullScreenAnimated:completionHandler: . So here's an extension on AVPlayerViewController to enter full screen.

extension AVPlayerViewController {
    func enterFullScreen(animated: Bool) {
        perform(NSSelectorFromString("enterFullScreenAnimated:completionHandler:"), with: animated, with: nil)
    }
    func exitFullScreen(animated: Bool) {
        perform(NSSelectorFromString("exitFullScreenAnimated:completionHandler:"), with: animated, with: nil)
    }
}

This implementation doesn't offer a completion callback. If you pass a Swift closure to the completionHandler parameter, it crashes in the underlying Obj-C API. I haven't investigated how to pass the closure to make it work.

Swift 3 version for the answer of ToddH:

extension AVPlayerViewController {

    func goFullScreen() {
        let selector = NSSelectorFromString("_transitionToFullScreenViewControllerAnimated:completionHandler:")
        if self.responds(to: selector) {
            // first argument is animated (true for me), second is completion handler (nil in my case)
            self.perform(selector, with: true, with: nil)
        }
    }
}

You can just set the videoGravity property of AVPlayerViewController.

if(fullscreen)
{
    [self.avPlayerController 
     setVideoGravity:AVLayerVideoGravityResizeAspectFill];
}
else
{
    [self.avPlayerController 
    setVideoGravity:AVLayerVideoGravityResizeAspect];
}

I did not have the need to use any restricted code.

For this, I am assuming that you have added the AVPlayerViewController as a child view controller.

Then for that you will first have to remove the child view controller and then present it again as a fullscreen controller as well attach the AVPlayer view properly to it's parent view.

Here is how I did it. Please note that I am using a library called Easy Peasy for restoring the playerVC.view constraints - one can do that with proper constraints as well.

    @objc func fullscreenButtonClicked() {

        playerVC.willMove(toParentViewController: nil)
        playerVC.view.removeFromSuperview()
        playerVC.removeFromParentViewController()


        self.present(self.playerVC, animated: false, completion: {
            self.playerVC.view.easy.layout(Top(), Right(), Left(), Bottom())
        })
    }

For an 'embedded' AVPlayerViewController instance, it is quite easy to programmatically have it start playback in full screen mode, and without hacking anything (calling private methods). You just need to set its entersFullScreenWhenPlaybackBegins property to true.

You need to add the controller as a child VC to the main VC, and that's basically it. In viewDidAppear(_:) you need to call play() method on the controller's player property - playback will be automatically started in fullscreen.

It's often best to check Apple sample code for these kind of tricky APIs; I think this one might be useful for a lot of AVPlayer use cases: Using AVKit in iOS .

Its pretty simple, just set playerViewController.videoGravity =.resizeAspectFill

and it goes full screen:)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM