【发布时间】:2020-04-01 04:00:47
【问题描述】:
我有一个带有多个单元格的UICollectionView (FeedCell)。这些单元格还可以包含一个UICollectionView,它也可以有多个单元格,最多五个(MediaSliderCell)。基本上,设置与 Instagram 相同:您有一个帖子,一个帖子可以有多个图像或视频。
我现在面临的问题是,有时在错误的单元格中显示了错误的视频。我使用 imageView 在视频尚未播放时显示占位符,当视频开始播放(点击播放按钮)时,此占位符被隐藏。
我认为当视频不播放时一切正常,但当我播放视频时出现问题。单元格被切换,这意味着MediaSliderCell indexPath.item 5 的视频显示在MediaSLiderCell indexPath.item 2 中。
起初,我认为问题出在单元格上,不能很好地重复使用,但这也意味着照片可能会被切换,但从未发生过。所以我觉得问题出在我的 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(),但似乎无法解决问题。我觉得我越来越近了。有什么想法吗?
【问题讨论】:
标签: ios swift uicollectionview uicollectionviewcell avplayer