首页 > 解决方案 > 使用 replaceCurrentItem() 时 AVPlayer 闪烁 - Swift - 以编程方式

问题描述

我正在以这种方式设置一个AVPlayerUIView

fileprivate func setUpAVPlayer(){
    
    self.cardModel?.urlStrings?.forEach({ (urlString) in
        
        guard let url = URL(string: urlString) else { return }
        let asset = AVAsset(url: url)
        let playerItem = AVPlayerItem(asset: asset, automaticallyLoadedAssetKeys: nil)
        playerItem.addObserver(self, forKeyPath: #keyPath(AVPlayerItem.status), options: [.old, .new], context: nil)
        self.playerItemArray.append(playerItem)
    })
    player = AVPlayer(playerItem: playerItemArray[0])
}

layoutSubviews我布置了显示播放器的 playerLayer:

let playerLayer = AVPlayerLayer(player: player)
playerLayer.videoGravity = .resizeAspectFill
self.playerView.layer.insertSublayer(playerLayer, at: 0)
playerLayer.frame = playerView.bounds

在某个事件中,我更改了播放器的当前项目:

fileprivate func goToNextVideo(){
    counter = counter+1
    counter = min(playerItemArray.count-1, counter)
    
    self.player?.replaceCurrentItem(with: self.playerItemArray[self.counter])
    self.player?.seek(to: .zero)
    self.player?.play()
}

当我更改项目时,过渡不平滑,我可以看到显示view 以下内容的闪光灯。有一些我不明白的地方:

  1. 为什么如果我在开始时预加载了所有内容(当我循环所有 url 字符串时),当它们成为AVPlayerAVPlayerItems时,我仍然必须等待它们被加载?currentItem

  2. 关于闪烁效果的问题,我在网上搜索了一下,没有找到解决办法,所以我仔细检查了AVPlayer每次变化的Item的行为,我是这样描述的:

首先它显示一个白色视图(下图)

其次它显示了它将显示的视频帧(更多或更少的时间帧是视频的一半)

第三它实际显示视频

这种行为真的很烦人,我想知道如何解决它。

标签: swiftavfoundationavplayeravplayeritemavplayerlayer

解决方案


import UIKit
import GPUImage
import AVFoundation

class ViewController: UIViewController {

    lazy var movie: GPUImageMovie = {
        let movie = GPUImageMovie()
        movie.yuvConversionSetup()
        movie.addTarget(imageView)
        return movie
    }()
    
    lazy var imageView: GPUImageView = {
        let view = GPUImageView()
        view.setBackgroundColorRed(1, green: 1, blue: 1, alpha: 1)
        return view
    }()
    
    lazy var player: AVPlayer = {
        let player = AVPlayer()
        return player
    }()
    
    lazy var videoURLs: [URL] = {
        return [
            resource(filename: "video0.mp4"),
            resource(filename: "video1.mp4"),
            resource(filename: "video2.mp4"),
            resource(filename: "video3.mp4"),
        ].compactMap({ $0 })
    }()
    
    lazy var button: UIButton = {
        let button = UIButton()
        button.setTitle("replaceCurrentItem", for: .normal)
        button.setTitleColor(.gray, for: .normal)
        button.addTarget(self, action: #selector(action(_:)), for: .touchUpInside)
        return button
    }()
    
    var index = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        view.addSubview(imageView)
        view.addSubview(button)
        imageView.frame = view.bounds
        button.frame = CGRect(x: 0, y: view.bounds.maxY - 100, width: view.bounds.width, height: 44)
    }

    func resource(filename: String) -> URL? {
        return Bundle.main.url(forResource: filename, withExtension: nil)
    }
    
    @objc func action(_ sender: UIButton) {
        if let url = getVideoURL() {
            player.pause()
            movie.endProcessing()
            movie.playerItem = AVPlayerItem(url: url)
            player.replaceCurrentItem(with: movie.playerItem)
            player.play()
            movie.startProcessing()
        }
    }
    
    func getVideoURL() -> URL? {
        if videoURLs.indices.contains(index) {
            let url = videoURLs[index]
            index = (index + 1) % videoURLs.count
            return url
        } else {
            return nil
        }
    }
    
}

播客文件:

platform :ios, '9.0'
source 'https://github.com/CocoaPods/Specs.git'

target 'Test' do
  use_frameworks!
  pod 'GPUImage'
end

当您调用时replaceCurrentItem,它会清理图像缓冲区,然后等待渲染下一帧。所以,关键是要保留屏幕上的最后一帧:您可以使用OpenGL ES(或 Metal)获取帧并渲染它AVPlayerLayer,而不是使用渲染视频帧。AVPlayerItemVideoOutput您还可以决定何时清理图像缓冲区。


推荐阅读