AVPlayerViewController中如何隐藏控制条,仅在开始时隐藏。

35
如果将AVPlayerViewController.showsPlaybackControls设置为false,则控件将完全不会显示,即使您点击屏幕也不会出现。
我希望控件一开始是隐藏的,但仍然可以通过点击召唤它们。如果将上述属性设置为true,则它们一开始就可见。 (是的,它们在几秒钟后淡出。)有没有办法使它们一开始处于隐藏状态,但仍然可访问?
6个回答

29

更新:最终我决定制作自己的控件以获得更好的自定义性。这样做更难,但是值得花费时间。请参考苹果的示例代码,它涉及到实现画中画,同时也包括制作自定义控件:https://developer.apple.com/library/prerelease/ios/samplecode/AVFoundationPiPPlayer/Introduction/Intro.html


更新:当点击时,AVPlayerViewController只会触发touchesBegan事件,而不会触发touchesEnded事件。但是这已经足够显示控件了。

首先,您需要隐藏控件。将此代码放在您展示AVPlayerViewController之前。

YourAVPlayerViewController.showsPlaybackControls = false

然后创建 AVPlayerViewController 的子类,并添加以下函数:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {

    self.showsPlaybackControls = true

    super.touchesBegan(touches, withEvent: event)
}

旧解决方案:

我刚刚解决了这个问题。主要思路是在AVPlayerViewController上面放置一个UIView来接收点击手势,并在不需要时隐藏该UIView。

以下是代码:

import AVKit
import UIKit

// Create a custom AVPlayerViewController
@available(iOS 8.0, *)
final class CustomAVPlayerViewController: AVPlayerViewController {

    // Create a UIView to put on top of all
    lazy var topView = UIView(frame: CGRectMake(0, 0, width, height))

    override func viewDidLoad() {
        super.viewDidLoad()

        // For sure, set it to clearcolor
        // (DON'T set alpha = 0 because it will stop receiving user interaction)
        topView.backgroundColor = UIColor.clearColor()

        // Add it to the view of AVPlayerViewController
        self.view.addSubview(topView)

        // Bring it to front
        self.view.bringSubviewToFront(topView)

        // Add a tap gesture recognizer
        topView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "handleTap"))
    }

    // Handle the tap
    func handleTap() {

        // Show the control
        self.showsPlaybackControls = true

        // Hide the topView. You can unhide it when needed later.
        self.topView.hidden = true
    }
}

当你需要隐藏控件时,请执行以下操作:

var AVViewController = CustomAVPlayerViewController()

...

// Hide controls
AVViewController.showsPlaybackControls = false
// Show topView
AVViewController.topView.hidden = false

12
苹果的文档中写道:不要对AVPlayerViewController进行子类化。覆盖此类的方法是不受支持的,并会导致未定义的行为。:/ - Ishan Handa
太对了!我最终自己制作了我的视图控制器 :) - thegathering
是否可能只隐藏其中一个按钮? - Vladimír Slavík
此外,苹果公司表示,“该属性不应用于暂时更改播放控件的可见性,因为这将创建或销毁UI元素”,涉及到showsPlaybackControls - hbk

13

我认为我已经使用了动态手势识别器关系来解决这个问题,该解决方案避免了自定义控件(以保持一致性),仅使用公共API,并且不对 AVPlayerViewController 进行子类化(正如其他答案中指出的那样,这是明确禁止的)。

具体实现方法如下:

  1. 制作一个容器视图控制器来嵌入 AVPlayerViewController。(无论控件如何,这都很有用,因为你需要将播放逻辑放在某个地方。)

  2. 初始时将 showsPlaybackControls 设置为 false

  3. 添加一个 UITapGestureRecognizer 来识别初始轻按操作。

  4. 在手势识别器的动作方法中,将 showsPlaybackControls 设置为 true

  5. 到目前为止,它可以工作,但控件会立即消失。要修复这个问题,将自己设置为手势识别器的代理,实现 gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer: 方法,并返回对于任何其他单击手势识别器都应该返回 true

这里是用 Swift 实现的实际代码;请查看 andreyvit/ModalMoviePlayerViewController 存储库中的最新代码:

import UIKit
import AVKit
import AVFoundation

public class ModalMoviePlayerViewController: UIViewController {

    private let fileName: String
    private let loop: Bool

    private var item: AVPlayerItem!
    private var player: AVPlayer!
    internal private(set) var playerVC: AVPlayerViewController!
    private var waitingToAutostart = true

    public init(fileName: String, loop: Bool = true) {
        self.fileName = fileName
        self.loop = loop
        super.init(nibName: nil, bundle: nil)
    }

    public required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    public override func viewDidLoad() {
        super.viewDidLoad()

        let url = NSBundle.mainBundle().URLForResource(fileName, withExtension: nil)!

        item = AVPlayerItem(URL: url)

        player = AVPlayer(playerItem: item)
        player.actionAtItemEnd = .None
        player.addObserver(self, forKeyPath: "status", options: [], context: nil)

        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ModalMoviePlayerViewController.didPlayToEndTime), name: AVPlayerItemDidPlayToEndTimeNotification, object: item)

        playerVC = AVPlayerViewController()
        playerVC.player = player
        playerVC.videoGravity = AVLayerVideoGravityResizeAspectFill
        playerVC.showsPlaybackControls = false

        let playerView = playerVC.view
        addChildViewController(playerVC)
        view.addSubview(playerView)
        playerView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        playerView.frame = view.bounds
        playerVC.didMoveToParentViewController(self)

        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(ModalMoviePlayerViewController.handleTap))
        tapGesture.delegate = self
        view.addGestureRecognizer(tapGesture)
    }

    deinit {
        player.pause()
        player.removeObserver(self, forKeyPath: "status")
        NSNotificationCenter.defaultCenter().removeObserver(self)
    }

    func togglePlayPause() {
        if isPlaying {
            pause()
        } else {
            play()
        }
    }

    func restart() {
        seekToStart()
        play()
    }

    func play() {
        if player.status == .ReadyToPlay {
            player.play()
        } else {
            waitingToAutostart = true
        }
    }

    func pause() {
        player.pause()
        waitingToAutostart = false
    }

    var isPlaying: Bool {
        return (player.rate > 1 - 1e-6) || waitingToAutostart
    }

    private func performStateTransitions() {
        if waitingToAutostart && player.status == .ReadyToPlay {
            player.play()
        }
    }

    public override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        performStateTransitions()
    }

    @objc func didPlayToEndTime() {
        if isPlaying && loop {
            seekToStart()
        }
    }

    private func seekToStart() {
        player.seekToTime(CMTimeMake(0, 10))
    }

    public override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        if !playerVC.showsPlaybackControls {
            playerVC.showsPlaybackControls = true
        }
        super.touchesBegan(touches, withEvent: event)
    }

}

extension ModalMoviePlayerViewController: UIGestureRecognizerDelegate {

    @IBAction func handleTap(sender: UIGestureRecognizer) {
        if !playerVC.showsPlaybackControls {
            playerVC.showsPlaybackControls = true
        }
    }

    /// Prevents delivery of touch gestures to AVPlayerViewController's gesture recognizer,
    /// which would cause controls to hide immediately after being shown.
    ///
    /// `-[AVPlayerViewController _handleSingleTapGesture] goes like this:
    ///
    ///     if self._showsPlaybackControlsView() {
    ///         _hidePlaybackControlsViewIfPossibleUntilFurtherUserInteraction()
    ///     } else {
    ///         _showPlaybackControlsViewIfNeededAndHideIfPossibleAfterDelayIfPlaying()
    ///     }
    public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailByGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        if !playerVC.showsPlaybackControls {
            // print("\nshouldBeRequiredToFailByGestureRecognizer? \(otherGestureRecognizer)")
            if let tapGesture = otherGestureRecognizer as? UITapGestureRecognizer {
                if tapGesture.numberOfTouchesRequired == 1 {
                    return true
                }
            }
        }
        return false
    }

}

2

thegathering的回答很好。我建议重写touchesCancelled方法,这样控件就不会再显示然后再隐藏。

override public func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
    super.touchesCancelled(touches, withEvent: event)

    // toggle the player controls on if they were set to off
    if !self.showsPlaybackControls {
        self.showsPlaybackControls = true
    }
}

2
在Swift 3中实现这个简单的方法是设置myController.showsPlaybackControls = false,并用一个按钮或手势识别器覆盖整个播放器视图。我将其嵌入到另一个控制器的另一个视图中,以使其简单且不覆盖播放器控制器。然后,技巧是在点击一次按钮后隐藏它,因为播放器控制器此后将跟踪点击以显示/隐藏控件。
@IBAction func enableControls(button:UIButton)
{
    controller?.showsPlaybackControls = true
    button.isHidden = true //The button is only needed once, then the player takes over.
}

1
说实话,这仍然是最好的解决方案!(个人而言,我只是摧毁了按钮,但无论哪种方式都没有问题) - Fattie

0
根据苹果的文档,当播放器在屏幕上时,不应该这样做。
“不要使用此属性来改变播放器视图控制器在屏幕上的可见性。这样做会创建或销毁用户界面元素。”

你的回答可以通过提供更多的支持性信息来改进。请编辑以添加进一步的细节,比如引用或文档,以便他人能够确认你的回答是否正确。你可以在帮助中心找到关于如何撰写好回答的更多信息。 - undefined

0

2023年的一个非常简单的解决方案

设置showsPlaybackControls = false,然后精确地添加以下代码:

///Address insane Apple long-standing issue that you can't "launch it 
///with no controls". There's no perfect solution.
var _firstTap = true
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    
    if _firstTap {
        showsPlaybackControls = true
        _firstTap = false
        return
    }
    super.touchesBegan(touches, with: event)
}

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