AVPlayerLooper 每次迭代后黑闪

问题描述 投票:0回答:2

我正在使用 Apple 的示例代码在

UICollectionViewCell
背景上播放视频。

我正在使用

AVPlayerLooper
,因为它是同一视频的迭代。

我的问题是,当视频到达结尾时,它会显示轻微的黑屏闪烁,也许它正在将视频搜索到0时间,我不确定。

这是代码:

Apple 协议

protocol Looper {

    init(videoURL: URL, loopCount: Int)

    func start(in layer: CALayer)

    func stop()
}

Apple提供的Player Looper类

// Code from Apple
class PlayerLooper: NSObject, Looper {
    // MARK: Types

    private struct ObserverContexts {
        static var isLooping = 0

        static var isLoopingKey = "isLooping"

        static var loopCount = 0

        static var loopCountKey = "loopCount"

        static var playerItemDurationKey = "duration"
    }

    // MARK: Properties

    private var player: AVQueuePlayer?

    private var playerLayer: AVPlayerLayer?

    private var playerLooper: AVPlayerLooper?

    private var isObserving = false

    private let numberOfTimesToPlay: Int

    private let videoURL: URL

    // MARK: Looper

    required init(videoURL: URL, loopCount: Int) {
        self.videoURL = videoURL
        self.numberOfTimesToPlay = loopCount

        super.init()
    }

    func start(in parentLayer: CALayer) {
        player = AVQueuePlayer()
        player?.isMuted = true
        playerLayer = AVPlayerLayer(player: player)

        guard let playerLayer = playerLayer else { fatalError("Error creating player layer") }
        playerLayer.frame = parentLayer.bounds
        parentLayer.addSublayer(playerLayer)

        let playerItem = AVPlayerItem(url: videoURL)
        playerItem.asset.loadValuesAsynchronously(forKeys: [ObserverContexts.playerItemDurationKey], completionHandler: {()->Void in
            /*
                The asset invokes its completion handler on an arbitrary queue when
                loading is complete. Because we want to access our AVPlayerLooper
                in our ensuing set-up, we must dispatch our handler to the main queue.
            */
            DispatchQueue.main.async(execute: {
                guard let player = self.player else { return }

                var durationError: NSError? = nil
                let durationStatus = playerItem.asset.statusOfValue(forKey: ObserverContexts.playerItemDurationKey, error: &durationError)
                guard durationStatus == .loaded else { fatalError("Failed to load duration property with error: \(String(describing: durationError))") }

                self.playerLooper = AVPlayerLooper(player: player, templateItem: playerItem)
                self.startObserving()
                player.play()
            })
        })
    }

    func stop() {
        player?.pause()
        stopObserving()

        playerLooper?.disableLooping()
        playerLooper = nil

        playerLayer?.removeFromSuperlayer()
        playerLayer = nil

        player = nil
    }

    // MARK: Convenience

    private func startObserving() {
        guard let playerLooper = playerLooper, !isObserving else { return }

        playerLooper.addObserver(self, forKeyPath: ObserverContexts.isLoopingKey, options: .new, context: &ObserverContexts.isLooping)
        playerLooper.addObserver(self, forKeyPath: ObserverContexts.loopCountKey, options: .new, context: &ObserverContexts.loopCount)

        isObserving = true
    }

    private func stopObserving() {
        guard let playerLooper = playerLooper, isObserving else { return }

        playerLooper.removeObserver(self, forKeyPath: ObserverContexts.isLoopingKey, context: &ObserverContexts.isLooping)
        playerLooper.removeObserver(self, forKeyPath: ObserverContexts.loopCountKey, context: &ObserverContexts.loopCount)

        isObserving = false
    }

    // MARK: KVO

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if context == &ObserverContexts.isLooping {
            if let loopingStatus = change?[.newKey] as? Bool, !loopingStatus {
                print("Looping ended due to an error")
            }
        }
        else if context == &ObserverContexts.loopCount {
            guard let playerLooper = playerLooper else { return }

            if numberOfTimesToPlay > 0 && playerLooper.loopCount >= numberOfTimesToPlay - 1 {
                print("Exceeded loop limit of \(numberOfTimesToPlay) and disabling looping");
                stopObserving()
                playerLooper.disableLooping()
            }
        }
        else {
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        }
    }
}

我的集合视图单元格循环初始化

var looper: Looper? {
    didSet {
        configLooper()
    }
}
func configLooper() {
    looper?.start(in: layer)

}

我的集合视图委托用于单元格初始化

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! FirstLaunchCollectionViewCell
    let videoURL = Bundle.main.url(forResource: "video3", withExtension: "MOV")
    cell.looper = PlayerLooper(videoURL: videoURL!, loopCount: -1)
    return cell
}

loopCount
设置为
-1
,以便视频无限次播放。

我尝试使用较小的视频大小文件,但它仍然在每次迭代结束时显示黑框。

有谁知道可能导致此问题的原因,或者有更好的方法吗? Apple 源代码可以在这里找到

ios swift uicollectionview avplayer
2个回答
0
投票

您可以采取的另一种方法是添加一个观察者来完成视频,如下所示:

NotificationCenter.default.addObserver(forName: AVPlayerItemDidPlayToEndTime, object: self.player.currentItem, queue: nil, using: { (_) in
   DispatchQueue.main.async {
      self.player.seek(to: kCMTimeZero)
      self.player.play()
   }
})

0
投票

我知道这个问题很老了,但是避免这种眨眼的最性能优化的方法(我有时有,有时没有,但似乎 AVFoundation 经常给我们带来惊喜)是使用 snapshotView,这是 Apple 优化的(可能是与负责 AVFoundation 的团队不同的团队)。

player?.play()
if snapshotView == nil {
     if let snapshotView = snapshotView(afterScreenUpdates: true) {
           playerView.insertSubview(snapshotView, at: 0)
           snapshotView.snp.makeConstraints {
                $0.edges.equalToSuperview()
           }
           self.snapshotView = snapshotView
      }
}

这样您将把第一帧渲染为背景,而不是眨眼,这样对于用户来说它看起来是无缝的

© www.soinside.com 2019 - 2024. All rights reserved.