对自定义AVPlayer的错误引用

问题描述 投票:1回答:1

我有一个带有多个单元格的UICollectionViewFeedCell)。这些单元格还可以包含一个UICollectionView,也可以包含多个单元格,最多五个(MediaSliderCell)。基本上,设置与Instagram相同:您有一个帖子,一个帖子可以包含多个图像或视频。

我现在面临的问题是,有时在错误的单元格中显示了错误的视频。我使用imageView在尚未播放视频时显示一个占位符,当视频开始播放时(点击播放按钮),该占位符被隐藏。]

我认为当视频不播放时一切都会很好,但是当我播放视频时就会出现问题。单元格被切换,例如,MediaSliderCell indexPath.item 5的视频显示在MediaSLiderCell indexPath.item 2中。

起初,我认为问题出在单元中,不能很好地重复使用,但这也意味着照片可能会被调换,这种情况永远不会发生。因此,我觉得问题出在我的AVPlayer中,该AVPlayer使用了错误的引用或错误的URL。让我演示一下我的代码:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "mediaSliderCell", for: indexPath) as! MediaSliderCell
    cell.layer.shouldRasterize = true
    cell.layer.rasterizationScale = UIScreen.main.scale
        if(HomeControllerID.userFeed.userFeed[feedCellID!].userMedia!.count > 0) {
            if(HomeControllerID.userFeed.userFeed[feedCellID!].userMedia![indexPath.item].mediaType == .photo) {
                    let mediaURL = URL(string: "https://myfileserver.com/media/\(HomeControllerID.userFeed.userFeed[feedCellID!].feedID!)/\(HomeControllerID.userFeed.userFeed[feedCellID!].userMedia![indexPath.item].mediaURL)")
                    cell.photoView.kf.setImage(with: mediaURL, options: [.targetCache(mediaCache)])
                    cell.photoView.isHidden = false
                    cell.videoView.isHidden = true
            } else if(HomeControllerID.userFeed.userFeed[feedCellID!].userMedia![indexPath.item].mediaType == .video) {
                let mediaThumbURL = URL(string: "https://myfileserver.com/media/\(HomeControllerID.userFeed.userFeed[feedCellID!].feedID!)/\(HomeControllerID.userFeed.userFeed[feedCellID!].userMedia![indexPath.item].mediaThumbURL!)")
                let mediaURL = URL(string: "https://myfileserver.com/media/\(HomeControllerID.userFeed.userFeed[feedCellID!].feedID!)/\(HomeControllerID.userFeed.userFeed[feedCellID!].userMedia![indexPath.item].mediaURL)")!
                cell.videoView.placeholderView.kf.setImage(with: mediaThumbURL, options: [.targetCache(mediaCache)])
                cell.videoView.mediaURL = mediaURL
                cell.photoView.isHidden = true
                cell.videoView.isHidden = false
            }
    }
    return cell
}

MediaSLiderCell是一些非常基本的UICollectionViewCell素材:

class MediaSliderCell: UICollectionViewCell {
    override func prepareForReuse() {
        super.prepareForReuse()
        photoView.isHidden = false
        videoView.isHidden = true
        videoView.mediaURL = nil
        videoView.placeholderView.kf.cancelDownloadTask()
        videoView.placeholderView.image = UIImage()
        photoView.image = UIImage()
    }

    var photoView: UIImageView = {
        let photoView = UIImageView()
        photoView.translatesAutoresizingMaskIntoConstraints = false
        photoView.backgroundColor = .black
        photoView.isHidden = true
        return photoView
    }()

    var videoView: VideoView = {
        let videoView = VideoView()
        videoView.translatesAutoresizingMaskIntoConstraints = false
        videoView.backgroundColor = .black
        return videoView
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupViews()
    }

    func setupViews() {
        addSubview(photoView)
        addSubview(videoView)
        photoView.topAnchor.constraint(equalTo: topAnchor).isActive = true
        photoView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
        photoView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        photoView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
        videoView.topAnchor.constraint(equalTo: topAnchor).isActive = true
        videoView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
        videoView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        videoView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
    }

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

我认为以上所有方法都很好。但是,对于VideoView,我使用自定义UIView,在其中设置了占位符(UIImageView)。下面有很多代码,但是大多数是控件和用户界面的东西。重要的是要知道,我使用了两个库:CacheCachingPlayerItem。这是为了避免一遍又一遍地下载相同的视频,因此当在缓存中找不到该视频时,我将下载该项目,而在下载视频时,将其保存在缓存中以供以后重复使用。我猜一切都说得通。我觉得问题出在哪里,或者在AVPlayer本身。看一下代码:

class VideoView: UIView, CachingPlayerItemDelegate {
    var playerItem: CachingPlayerItem?
    func playerItem(_ playerItem: CachingPlayerItem, didFinishDownloadingData data: Data) {
        // A track is downloaded. Saving it to the cache asynchronously.
        print("Saving video to cache on device")
        storage?.async.setObject(data, forKey: mediaURL.absoluteString, completion: { _ in} )
    }
    var playerLooper: NSObject?
    var playerLayer: AVPlayerLayer!
    var queuePlayer: AVQueuePlayer?
    var mediaURL: URL!
    var placeholderView: UIImageView = {
        let placeholderView = UIImageView()
        placeholderView.translatesAutoresizingMaskIntoConstraints = false
        placeholderView.backgroundColor = .black
        return placeholderView
    }()

    var playerView: UIView = {
        let playerView = UIView()
        playerView.translatesAutoresizingMaskIntoConstraints = false
        playerView.backgroundColor = .clear
        playerView.isHidden = true
        return playerView
    }()

    var playButton: UIImageView = {
        let playButton = UIImageView()
        playButton.image = UIImage(named: "playButton")
        playButton.translatesAutoresizingMaskIntoConstraints = false
        playButton.isUserInteractionEnabled = true
        playButton.backgroundColor = .clear
        return playButton
    }()

    var pauseButton: UIImageView = {
        let pauseButton = UIImageView()
        pauseButton.image = UIImage(named: "pauseButton")
        pauseButton.translatesAutoresizingMaskIntoConstraints = false
        pauseButton.backgroundColor = .clear
        pauseButton.isUserInteractionEnabled = true
        pauseButton.alpha = 0
        return pauseButton
    }()

    var volumeOnButton: UIImageView = {
        let volumeOnButton = UIImageView()
        volumeOnButton.image = UIImage(named: "volumeOn")
        volumeOnButton.translatesAutoresizingMaskIntoConstraints = false
        volumeOnButton.backgroundColor = .clear
        volumeOnButton.isUserInteractionEnabled = true
        volumeOnButton.alpha = 0
        return volumeOnButton
    }()

    var volumeOffButton: UIImageView = {
        let volumeOffButton = UIImageView()
        volumeOffButton.image = UIImage(named: "volumeOff")
        volumeOffButton.translatesAutoresizingMaskIntoConstraints = false
        volumeOffButton.backgroundColor = .clear
        volumeOffButton.isUserInteractionEnabled = true
        volumeOffButton.alpha = 0
        return volumeOffButton
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.setupViews()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.setupViews()
    }

    func setupViews() {
        addSubview(placeholderView)
        placeholderView.topAnchor.constraint(equalTo: topAnchor).isActive = true
        placeholderView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
        placeholderView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        placeholderView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
        addSubview(playerView)
        playerView.topAnchor.constraint(equalTo: topAnchor).isActive = true
        playerView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
        playerView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        playerView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
        let tapView = UITapGestureRecognizer(target: self, action: #selector(showControls))
        playerView.addGestureRecognizer(tapView)
        addSubview(playButton)
        playButton.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
        playButton.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
        playButton.heightAnchor.constraint(equalToConstant: profilePicWidth * 2).isActive = true
        playButton.widthAnchor.constraint(equalToConstant: profilePicWidth * 2).isActive = true
        let tapPlayButton = UITapGestureRecognizer(target: self, action: #selector(playVideo))
        playButton.addGestureRecognizer(tapPlayButton)
        addSubview(pauseButton)
        pauseButton.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
        pauseButton.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
        pauseButton.heightAnchor.constraint(equalToConstant: profilePicWidth * 2).isActive = true
        pauseButton.widthAnchor.constraint(equalToConstant: profilePicWidth * 2).isActive = true
        let tapPauseButton = UITapGestureRecognizer(target: self, action: #selector(pauseVideo))
        pauseButton.addGestureRecognizer(tapPauseButton)
        addSubview(volumeOnButton)
        volumeOnButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -(normalSpacing + 2)).isActive = true
        volumeOnButton.topAnchor.constraint(equalTo: topAnchor, constant: normalSpacing + 2).isActive = true
        volumeOnButton.heightAnchor.constraint(equalToConstant: 32).isActive = true
        volumeOnButton.widthAnchor.constraint(equalToConstant: 32).isActive = true
        let tapVolumeOnButton = UITapGestureRecognizer(target: self, action: #selector(volumeAction))
        volumeOnButton.addGestureRecognizer(tapVolumeOnButton)
        addSubview(volumeOffButton)
        volumeOffButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -(normalSpacing + 2)).isActive = true
        volumeOffButton.topAnchor.constraint(equalTo: topAnchor, constant: normalSpacing + 2).isActive = true
        volumeOffButton.heightAnchor.constraint(equalToConstant: 32).isActive = true
        volumeOffButton.widthAnchor.constraint(equalToConstant: 32).isActive = true
        let tapVolumeOffButton = UITapGestureRecognizer(target: self, action: #selector(volumeAction))
        volumeOffButton.addGestureRecognizer(tapVolumeOffButton)
    }

    @objc func volumeAction() {
        buttonTimer?.invalidate()
        volumeTimer?.invalidate()
        if UserDefaults.exists(key: "volumeOn") {
            if(UserDefaults.standard.bool(forKey: "volumeOn") == false) {
                self.queuePlayer?.isMuted = false
                UserDefaults.standard.set(true, forKey: "volumeOn")
                self.volumeOnButton.alpha = 1
                self.volumeOffButton.alpha = 0
            } else {
                self.queuePlayer?.isMuted = true
                UserDefaults.standard.set(false, forKey: "volumeOn")
                self.volumeOnButton.alpha = 0
                self.volumeOffButton.alpha = 1
            }
        } else {
            self.queuePlayer?.isMuted = false
            UserDefaults.standard.set(true, forKey: "volumeOn")
            self.volumeOnButton.alpha = 1
            self.volumeOffButton.alpha = 0
        }
        volumeTimer = Timer.scheduledTimer(timeInterval: 2.5, target: self, selector: #selector(fadeVolumeButton), userInfo: nil, repeats: false)
        buttonTimer = Timer.scheduledTimer(timeInterval: 2.5, target: self, selector: #selector(fadePauseButton), userInfo: nil, repeats: false)
    }

    @objc func checkVolume() {
        if UserDefaults.exists(key: "volumeOn") {
            if(UserDefaults.standard.bool(forKey: "volumeOn") == false) {
                self.queuePlayer?.isMuted = true
            } else {
                self.queuePlayer?.isMuted = false
            }
        } else {
            self.queuePlayer?.isMuted = true
        }
    }

    @objc func showControls() {
        buttonTimer?.invalidate()
        volumeTimer?.invalidate()
        if(self.volumeOnButton.alpha > 0 || self.volumeOffButton.alpha > 0) {
            UIView.animate(withDuration: 0.2) {
                self.volumeOnButton.alpha = 0
                self.volumeOffButton.alpha = 0
            }
        } else {
            if UserDefaults.exists(key: "volumeOn") {
                if(UserDefaults.standard.bool(forKey: "volumeOn") == false) {
                    UIView.animate(withDuration: 0.2) {
                        self.volumeOffButton.alpha = 1
                        self.volumeOnButton.alpha = 0
                    }
                } else {
                    UIView.animate(withDuration: 0.2) {
                        self.volumeOffButton.alpha = 0
                        self.volumeOnButton.alpha = 1
                    }
                }
            } else {
                UIView.animate(withDuration: 0.2) {
                    self.volumeOffButton.alpha = 1
                    self.volumeOnButton.alpha = 0
                }
            }
            volumeTimer = Timer.scheduledTimer(timeInterval: 2.5, target: self, selector: #selector(fadeVolumeButton), userInfo: nil, repeats: false)
        }

        if(self.queuePlayer?.timeControlStatus == .playing) {
            if(self.pauseButton.alpha > 0) {
                UIView.animate(withDuration: 0.2) { self.pauseButton.alpha = 0 }
            } else {
                UIView.animate(withDuration: 0.2) { self.pauseButton.alpha = 1 }
                buttonTimer = Timer.scheduledTimer(timeInterval: 2.5, target: self, selector: #selector(fadePauseButton), userInfo: nil, repeats: false)
            }
        } else if(self.queuePlayer?.timeControlStatus == .paused) {
            if(self.playButton.alpha > 0) {
                UIView.animate(withDuration: 0.2) { self.pauseButton.alpha = 0 }
            } else {
                UIView.animate(withDuration: 0.2) { self.playButton.alpha = 1 }
            }
        }
    }

    lazy var storage: Cache.Storage? = {
        return try? Storage(diskConfig: diskConfig, memoryConfig: memoryConfig, transformer: TransformerFactory.forData())
    }()

    func loadVideo() {
        // Trying to retrieve a track from cache asynchronously.
        storage?.async.entry(forKey: mediaURL.absoluteString, completion: { result in
            switch result {
            case .error:
                // The track is not cached.
                print("Downloading from network")
                self.playerItem = CachingPlayerItem(url: self.mediaURL)
            case .value(let entry):
                // The track is cached.
                print("Downloading from cached library on device")
                self.playerItem = CachingPlayerItem(data: entry.object, mimeType: "video/mp4", fileExtension: "mp4")
            }
            self.playerItem?.delegate = self
            DispatchQueue.main.async {
                if let playerItem = self.playerItem {
                    self.queuePlayer = AVQueuePlayer(items: [playerItem])
                    self.queuePlayer?.automaticallyWaitsToMinimizeStalling = false
                    self.playerLayer = AVPlayerLayer(player: self.queuePlayer)
                    self.playerLooper = AVPlayerLooper(player: self.queuePlayer!, templateItem: playerItem)
                    self.playerView.layer.addSublayer(self.playerLayer!)
                    self.playerLayer?.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.width * 1.25)
                    self.checkVolume()
                    do {
                       try AVAudioSession.sharedInstance().setCategory(.playback)
                    } catch(let error) {
                        print(error.localizedDescription)
                    }
                    self.queuePlayer?.play()
                    self.queuePlayer?.addObserver(self, forKeyPath: "timeControlStatus", options: .initial, context:nil)
                }
            }
        })
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if(keyPath == "timeControlStatus") {
            if(self.playerView.isHidden && self.queuePlayer?.timeControlStatus == .playing) {
                self.playerView.isHidden = false
            }
        }
    }

    var buttonTimer: Timer?
    var volumeTimer: Timer?

    @objc func playVideo() {
        buttonTimer?.invalidate()
        volumeTimer?.invalidate()
        if(self.queuePlayer?.currentItem == nil) {
            self.loadVideo()
        }
        if(self.queuePlayer?.timeControlStatus == .paused) {
            do {
               try AVAudioSession.sharedInstance().setCategory(.playback)
            } catch(let error) {
                print(error.localizedDescription)
            }
            self.queuePlayer?.play()
        }
        self.playButton.alpha = 0
        buttonTimer = Timer.scheduledTimer(timeInterval: 1.5, target: self, selector: #selector(fadePauseButton), userInfo: nil, repeats: false)
        volumeTimer = Timer.scheduledTimer(timeInterval: 1.5, target: self, selector: #selector(fadeVolumeButton), userInfo: nil, repeats: false)
    }

    @objc func pauseVideo() {
        buttonTimer?.invalidate()
        self.queuePlayer?.pause()
        self.playButton.alpha = 1
        self.pauseButton.alpha = 0
        self.volumeOnButton.alpha = 0
        self.volumeOffButton.alpha = 0
    }

    @objc func fadePauseButton() {
        UIView.animate(withDuration: 0.8) {
            self.pauseButton.alpha = 0
        }
    }

    @objc func fadeVolumeButton() {
        UIView.animate(withDuration: 0.8) {
            self.volumeOnButton.alpha = 0
            self.volumeOffButton.alpha = 0
        }
    }
}

我认为问题应该在loadVideo()中的某个地方。但是,我提供了自定义UIView VideoView的所有代码,以避免出现任何错误或其他重要部分。有人可以帮助我吗?对我有很大帮助,因为我已经研究了好几个星期了。我已经尝试了所有关于出队的建议,我已经尝试了所有可能的重用单元格的方法,但是都没有用。因此,我想应该在AVPlayerCache库或CachingPlayerItem库中找到解决方案。将不胜感激任何帮助或建议。预先谢谢你。

为了记录,我忘记了一些东西,但是应该从代码中清除:AVPlayer未显示,因此最初未调用loadVideo()。仅当用户点击播放按钮(playVideo())时。如果我也不要播放视频,一切都会顺利进行。照片显示正确,占位符显示正确,但是视频混合了之后

我开始播放一个或多个视频(点按“播放”按钮,该呼叫称为playVideo(),该呼叫称为loadVideo())。

编辑:好的,所以我知道了。单元重新使用后,视频本身似乎并没有改变。我的意思是,在loadVideo()中,设置了AVQueuePlayer(),这就是更改随后显示和播放的视频的原因。我觉得我需要设置此块:

    // Trying to retrieve a track from cache asynchronously.
    storage?.async.entry(forKey: mediaURL.absoluteString, completion: { result in

        switch result {
        case .error:
            // The track is not cached.
            print("Downloading from network")
            DispatchQueue.main.async {
            self.playerItem = CachingPlayerItem(url: self.mediaURL)
            }
        case .value(let entry):
            // The track is cached.
            print("Downloading from cached library on device")
            DispatchQueue.main.async {
            self.playerItem = CachingPlayerItem(data: entry.object, mimeType: "video/mp4", fileExtension: "mp4")
            }
        }

        self.playerItem?.delegate = self

        DispatchQueue.main.async {

            if let playerItem = self.playerItem {
                self.queuePlayer = AVQueuePlayer(items: [playerItem])
                self.queuePlayer?.automaticallyWaitsToMinimizeStalling = false
                self.playerLayer = AVPlayerLayer(player: self.queuePlayer)
                self.playerLooper = AVPlayerLooper(player: self.queuePlayer!, templateItem: playerItem)
                self.playerView.layer.addSublayer(self.playerLayer!)
                self.playerLayer?.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.width * 1.25)
                self.checkVolume()
                do {
                   try AVAudioSession.sharedInstance().setCategory(.playback)
                } catch(let error) {
                    print(error.localizedDescription)
                }
                self.queuePlayer?.addObserver(self, forKeyPath: "timeControlStatus", options: .initial, context:nil)

            }

        }

    })

在其他地方,根本不播放视频。因为只有在轻按“播放”按钮时才会调用此方法,但是显示的视频却不正确。尝试直接在单元格中调用loadVideo(),但似乎无法解决问题。我觉得我越来越近了。有什么想法吗?

我有一个带有多个单元格的UICollectionView(FeedCell)。这些单元格还可以包含一个UICollectionView,它也可以具有多个单元格,最多五个(MediaSliderCell)。基本上,设置是...

ios swift uicollectionview uicollectionviewcell avplayer
1个回答
0
投票

不要在tableView单元格中存储AVQueuePlayer。请记住,您可以保留的AVPlayer实例数量有上限。

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