使用replaceCurrentItem时AVPlayer闪烁-Swift-以编程方式

问题描述

我正在通过以下方式在AVPlayer中设置UIView

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. 为什么要在开始时预加载所有AVPlayerItems(当我循环所有url字符串时),当它们成为AVPlayer的currentItem时,我仍然必须等待它们被加载?

  2. 出于什么考虑,我在网上搜索了闪烁效果,却没有找到解决方案,因此,每当更改Item时,我都会仔细检查AVPlayer的行为,并以此方式进行描述:

第一(白色)(下面的视图)

第二显示显示的视频帧(时间越多,占视频一半)

第三实际上显示了视频

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

解决方法

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

Podfile:

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

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

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