我有一个带有多个单元格的UICollectionView
(FeedCell
)。这些单元格还可以包含一个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
)。下面有很多代码,但是大多数是控件和用户界面的东西。重要的是要知道,我使用了两个库:Cache和CachingPlayerItem。这是为了避免一遍又一遍地下载相同的视频,因此当在缓存中找不到该视频时,我将下载该项目,而在下载视频时,将其保存在缓存中以供以后重复使用。我猜一切都说得通。我觉得问题出在哪里,或者在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的所有代码,以避免出现任何错误或其他重要部分。有人可以帮助我吗?对我有很大帮助,因为我已经研究了好几个星期了。我已经尝试了所有关于出队的建议,我已经尝试了所有可能的重用单元格的方法,但是都没有用。因此,我想应该在AVPlayer
,Cache
库或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)。基本上,设置是...
不要在tableView单元格中存储AVQueuePlayer
。请记住,您可以保留的AVPlayer实例数量有上限。