如何以编程方式使AVPlayerViewController全屏?

37

我正在尝试通过编程的方式将 AVPlayerViewController 从"嵌入式"模式切换到全屏模式,但是通过公开的API似乎不可能实现。

有没有我错过的解决方法? 我想获取与用户按控件右下角的全屏按钮时相同的动画。

使用MPMoviePlayerController不是可行的替代方案,因为可能会同时播放多个视频。

谢谢。


我跟你的处境一样。目前似乎没有解决办法。@Stefan的回答并没有真正实现“全屏”播放器,而只是使它变大,正如你所说,这会导致控件不能正确更新,你也无法知道用户是否点击了完成按钮或折叠按钮。虽然这并不重要,因为它始终会以扩展箭头开始,这是不正确的。在我的情况下,我将不得不修改应用程序来解决AVPlayerViewController的限制问题。希望他们能在某个时候修复这个问题。 - Brian F Leighty
同样的问题。这很奇怪,因为有一个苹果技术讲座,他们说你应该呈现视图控制器使其全屏。 - Matt Mc
10个回答

16

AVPlayerViewController是UIViewController的子类,因此像其他视图控制器子类一样可呈现。你能使用presentViewController:animated:completion吗?

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

这将在左上角显示“完成”按钮。


无法实现此功能,当设备处于网络共享状态(蓝色顶部栏)时。旧版的MPMoviePlayer即使在这种情况下也能全屏播放。 - JOM
这不会使用默认的过渡动画来全屏显示,对吧?相反,它使用了一个模态框。 - Petr Peller

15

更新至iOS 11

AVPlayerViewController没有官方支持的方法可以编程实现全屏(在我看来这是一个小疏忽)。

不过,AVPlayerViewController 包含一个私有方法,可以完美实现这一功能。但鉴于私有方法不应该调用,你需要自己决定是否使用它。

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

这在iOS 10中还有效吗?我看到控制器响应选择器,但实际调用时什么也没有发生。 - Petr Peller
1
谢谢@ToddH,它起作用了!我们如何为这个全屏和小屏幕转换添加观察者? - Vinu David Jose
@VinuDavidJose,在AVPlayerViewController上,你必须添加addObserver: forKeyPath:"videoBounds",然后在被调用的observeValue方法中:DispatchQueue.main.async { isFullscreen = playerViewController.view.bounds != playerViewController.contentOverlayView?.bounds ... } - ToddH
@ToddH 我使用了这个想法从tableviewcell进入全屏模式。但在展示全屏后,在iOS10设备上会崩溃,而在iOS 11 iPhone X上完美运行。 - Vinu David Jose
请查找崩溃日志 *** 终止应用程序,原因是未捕获的异常 'UIViewControllerHierarchyInconsistency',原因是:'子视图控制器:<AVEmbeddedPlaybackControlsViewController: 0x7f8e9ca22200> 应该有父视图控制器:<AVPlayerViewController: 0x7f8e9bb62e00>,但实际上父视图控制器是:<AVFullScreenViewController: 0x7f8e9adc4fa0>'。 - Vinu David Jose
显示剩余2条评论

11

更新:ToddH的回答的Swift 4版本:

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

7
在iOS11中,AVPlayerViewController有两个新属性:entersFullScreenWhenPlaybackBeginsexitsFullScreenWhenPlaybackEnds。您可以使用这些属性在播放开始后立即启用全屏模式,并在播放结束时禁用它。如果您需要在一定延迟后启用全屏模式,则可以像ToddH他的回答中提到的那样使用私有API方法。但是,在iOS11中,_transitionToFullScreenViewControllerAnimated:completionHandler:方法不再可用,而是有一个名为_transitionToFullScreenAnimated:completionHandler:的相同方法。第二种方法接受与第一种方法相同的参数。
我可以展示如何使用它的示例。首先,您需要在您的UIViewController中创建AVPlayerViewController实例:
private let playerController : AVPlayerViewController = {

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

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

然后您需要为AVPlayerViewController设置视图并将其添加到当前控制器视图中。函数setupAVplayerController可以为您完成此操作:

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

enterFullscreen 函数强制 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)
    }
}

现在,您需要在需要使用它的地方调用所有这些函数,例如在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)
    }
}

不要忘记,此解决方案基于私有 API 调用,不建议使用。

@karthikeyan 如果你需要强制AVPlayerViewController进入全屏模式,并且可以使用私有API调用,只需将我的答案中的代码复制到你的项目中,然后调用myPlayerViewController.forceFullScreenMode()。或者你在这个答案中发现了一些错误吗? - Roman Podymov
@karthikeyan 你在测试时使用的是哪个操作系统/设备? - Roman Podymov
@karthikeyan 你也可以尝试将函数“_transitionToFullScreenAnimated:completionHandler:”的第一个参数从“true”更改为“false”。这样做是否解决了你的问题? - Roman Podymov
能否提供一个示例?我不明白扩展将如何在我的场景中工作。你理解我的流程吗? - karthikeyan
让我们在聊天中继续这个讨论 - Roman Podymov
显示剩余5条评论

7
作为对ToddH答案的小小iOS 14更新:调用的私有APIenterFullScreenAnimated:completionHandler:。因此,这里提供了一个AVPlayerViewController的扩展来进入全屏模式。
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)
    }
}

这个实现没有提供完成回调函数。如果你将一个Swift闭包传递给completionHandler参数,它会在底层的Obj-C API中崩溃。我没有研究如何传递闭包使其工作。


2
你可以通过设置AVPlayerViewController的videoGravity属性来实现。
if(fullscreen)
{
    [self.avPlayerController 
     setVideoGravity:AVLayerVideoGravityResizeAspectFill];
}
else
{
    [self.avPlayerController 
    setVideoGravity:AVLayerVideoGravityResizeAspect];
}

2

ToddH的答案适用于Swift 3版本:

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

1

我没有使用任何受限制的代码。

为此,我假设您已将AVPlayerViewController添加为子视图控制器。

然后,您需要先删除子视图控制器,然后再将其作为全屏控制器呈现,并将AVPlayer视图正确附加到其父视图上。

这是我所做的。请注意,我正在使用一个名为Easy Peasy的库来恢复playerVC.view约束 - 也可以使用适当的约束来实现。

    @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())
        })
    }


我可以使用 UIViewController.attemptRotationToDeviceOrientation() 来代替设置 playerVC.view 的约束。 - Tuan Luong

0
对于嵌入式的`AVPlayerViewController`实例,以编程方式使其在全屏模式下开始播放并且无需任何修改(调用私有方法)是相当容易的。您只需要将其`entersFullScreenWhenPlaybackBegins`属性设置为true即可。
您需要将控制器添加为主控制器的子VC,基本上就这样了。在`viewDidAppear(_ :)`中,您需要在控制器的`player`属性上调用`play()`方法 - 播放将自动在全屏模式下启动。
对于这些棘手的API,查看Apple的示例代码通常是最好的选择;我认为这个链接可能对许多AVPlayer使用情况有用:Using AVKit in iOS

2
听起来很简单。但是即使将 entersFullScreenWhenPlaybackBegins 设置为 true,我也无法在全屏模式下开始播放。 - funkybro
1
嗯。请注意该属性的Apple文档:https://developer.apple.com/documentation/avkit/avplayerviewcontroller/2875792-entersfullscreenwhenplaybackbegi -- 一个布尔值,用于确定当用户点击播放按钮时,播放器是否自动全屏显示。确实,当手动按下播放按钮开始播放时,播放器会进入全屏模式。但是在编程启动时不会自动进入全屏模式。 - funkybro

-1

很简单,只需设置

playerViewController.videoGravity = .resizeAspectFill

它就会全屏显示:)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接