【问题标题】:addPeriodicTimeObserver keeps AVPlayer instancesaddPeriodicTimeObserver 保留 AVPlayer 实例
【发布时间】:2018-08-06 19:53:07
【问题描述】:

在浏览过 Stack Overflow 和互联网的最深处之后,我还没有找到解决方案。我希望那里的人能够帮助我解决我这几天遇到的问题。

有问题的应用有一个包含大量项目的集合视图。当您单击它时,您将进入预览集合视图。最后(我需要帮助的地方)当您单击“GO”时,您会进入一个集合视图,其中包含填满整个屏幕的单元格。它由AVPlayerLayerAVPlayer 组成。每次向右或向左滚动时,您都会看到另一个视频。 以下代码有效

UICollectionViewCell

class PageCell: UICollectionViewCell {
    var player: AVPlayer?
    var playerLayer: AVPlayerLayer?
    var playerItem: AVPlayerItem?
    var videoAsset: AVAsset?
    var indexPath: IndexPath?

    var page: Page? {
         didSet {
             guard let unwrappedPage = page, let unwrappedIndexPath = indexPath else { return }
             addingVideo(videoID: unwrappedPage.steps[unwrappedIndexPath.item].video)
        }
    }

    func setupPlayerView() {
        player = AVPlayer()
        playerLayer = AVPlayerLayer(player: player)
        playerLayer?.videoGravity = .resizeAspectFill
        videoView.layer.addSublayer(playerLayer!)
        layoutIfNeeded()
        playerLayer?.frame = videoView.bounds
    }

    func addingVideo(videoID: String) {
        setupPlayerView()

        guard let url = URL(string: videoID) else { return }
        videoAsset = AVAsset(url: url)
        activityIndicatorView.startAnimating()
        videoAsset?.loadValuesAsynchronously(forKeys: ["duration"]) {
            guard self.videoAsset?.statusOfValue(forKey: "duration", error: nil) == .loaded else { return }
            self.player?.play()
            self.playerItem = AVPlayerItem(asset: self.videoAsset!)
            self.player?.replaceCurrentItem(with: self.playerItem)
    }
}

UICollectionViewController 中,我正在重用单元格。由于无法知道AVPlayer 实例的数量,我只是在PageCell 中创建了一个辅助变量,看起来三个单元格正在被重用(出列和重用单元格时的正常数量)。现在,当我关闭此 UICollectionViewController 时,AVPlayer 实例似乎消失/关闭。

现在,当我想循环播放视频时,问题就出现了。使用AVPlayerLooper 不是一种选择,因为它太迟钝了(我已经以不同的方式实现了它,但没有运气)。所以我的解决方案是在videoAsset?.loadValuesAsynchronously 块内使用周期观察器:

self.timeObserver = self.player?.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 2),
    queue: DispatchQueue.global(), using: { (progressTime) in
    if let totalDuration = self.player?.currentItem?.duration{
        if progressTime == totalDuration {
            self.player?.seek(to: kCMTimeZero)
            self.player?.play()
        }
    }
})

问题

视频展示问题:Problem video

当同时运行 +17 AVPlayer 实例时会出现问题,然后突然视频将不再加载。 iOS 设备的硬件限制从 4 到 18 个AVPlayer 实例同时运行(很可能取决于 RAM),请参阅以下 Stack Overflow 帖子,仅举几例:

AVPlayerItemStatusFailed error when too many AVPlayer instances created

How many AVPlayers are allowed to be created at the same time?

AVPlayer not able to play after playing some items

Loading issues with AVPlayerItem from local URL

另请参阅这些文章以了解更多信息:

Building native video Pins

Too Many AVPlayers?

因为该问题仅在添加时间观察器时出现,我怀疑即使我关闭了集合视图,它也会使 AVPlayer 实例保持“活动”状态。

Problem video 的注释:每次我按“GO”时,都会创建一个AVPlayer 实例。当我向右滑动时,又创建了两个单元格,因此又创建了两个 AVPlayer 实例。总而言之,每次都会创建 3 个 AVPlayer 实例,最终累积到大约 17 个实例,并且视频将不再加载。每次按“GO”时,我都尝试滚动浏览所有视频,但这并不会改变结果,因为一直重复使用最多三个单元格。

我尝试解决的问题

尝试 1: 我将playerLayer 设为全局变量,并在viewDidDisappear 中添加了UICollectionViewController

playerLayer?.player = nil

当我将单元格关闭到“GO”页面时,这导致一个 AVPlayer 实例消失/关闭(即视图消失了)。这意味着我比不添加此代码时晚了 17 个实例。连同上面的代码,我还尝试添加playerLayer = nilplayerLayer?.player?.replaceCurrentItem(with: nil)playerLayer?.removeFromSuperlayer(),但唯一改变的是playerLayer?.player = nil。需要注意的是,如果我不滚动并简单地打开第一个视频并关闭,打开第一个视频并关闭等等,我可以“永远”做到这一点而没有任何问题。因此,在这种情况下,创建了一个实例,然后关闭/消失。

视频展示尝试 1:Try 1 video

尝试 2: 我将addPeriodicTimeObserver 块更改为:

self.timeObserver = playerLayer?.player?.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 
    2), queue: DispatchQueue.global(), using: { (progressTime) in
    if let totalDuration = playerLayer?.player?.currentItem?.duration{
        if progressTime == totalDuration {
            playerLayer?.player?.seek(to: kCMTimeZero)
            playerLayer?.player?.play()
        }
    }
})

在这个区块中,我基本上将所有self.player? 更改为playerLayer?.player?

这使我能够随心所欲地打开和关闭视频视图 - 因此AVPlayer 实例以某种方式关闭/消失。虽然现在循环不起作用。第一个视频最初会循环播放。但后来我刷到第二个单元格,这个视频不会循环播放。然后我滑回第一个单元格,现在这也不会循环。如果我像在“Try 1”中那样添加playerLayer?.player = nil,则无论打开、关闭还是循环都不会产生任何效果。

视频展示尝试 2:Try 2 video

尝试 3: 我将timeObserver 变量设为全局变量并尝试了很多东西。但是当我试图删除观察者时,它总是导致以下错误:

'NSInvalidArgumentException', reason: 'An instance of AVPlayer cannot remove a time observer that was added by a different instance of AVPlayer.'

错误表明时间观察者显然无处不在。当我在 addPeriodicTimeObserver 块内添加辅助变量(计数器)并发现时间观察者迅速加起来时,这一点很清楚。在这篇文章中提供的所有不同代码组合的某一时刻,我能够在滚动期间的任何时间删除时间观察器。但这导致仅从众多时间观察者中删除了一个 - 导致与“尝试 1”中相同的情况,每次关闭视图时我都能够删除单个 AVPlayer 实例。

一般说明: 为了测试是否真的只有三个AVPlayer 实例在我每次按“GO”并滑动时创建,我做了一个测试,滚动浏览了 20 多个视频,但加载它们没有问题。因此,如前所述,我很确定每次在视图中最多创建三个 AVPlayer 实例。

结论

问题是,正如我所见,当我添加时间观察者以循环播放它们累积的视频并保持 AVPlayer 实例“活动”时。请记住,如果没有时间观察者,我完全没有问题。在应用playerLayer?.player = nil 时,似乎我能够“保存”一个实例,但是当我不知道当前“活动”的AVPlayer 实例的数量时,又很难说清楚。简单地说,我想做的就是在视图消失的那一刻删除所有AVPlayer 实例,这将是快乐的日子。如果有人能走到这一步,如果你能帮助我,我将不胜感激。

【问题讨论】:

  • 个人认为你的问题应该简明扼要,这样人们才能真正阅读它。

标签: ios swift avplayer avplayeritem avplayerlayer


【解决方案1】:

有一个保留周期。在转义闭包中使用weak self。您创建的保留周期是:

cell -> Player -> 观察者闭包 -> cell

试试这个:

self.timeObserver = self.player?.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 2),
    queue: DispatchQueue.global(), using: { [weak self] (progressTime) in
    if let totalDuration = self?.player?.currentItem?.duration{
        if progressTime == totalDuration {
            self?.player?.seek(to: kCMTimeZero)
            self?.player?.play()
        }
    }
})

【讨论】:

  • 这样就可以了,谢谢!但是时间观察者永远不会被移除呢?我的测试计数器告诉我观察者很快就会加起来——我应该担心失控时间观察者的数量吗?让他们做自己的事是一种好习惯吗?
  • @Caravan 您需要使用player.removeTimeObserver(timeObserver) 删除观察者。删除它的好地方是在集合视图单元格中覆盖 prepareToReuse()
  • 我已经这样做了,但这并没有删除所有观察者。此外,我还尝试在关闭 viewDidDisappear 中的视图时删除观察者,但仍然有观察者。如何删除所有这些?
  • @Caravan 不幸的是,您必须自己跟踪所有观察者。例如,将它们放在一个数组中,并找出一种方法来确定您何时用完它们并删除它们。
  • 好的,我会试试的。感谢您的帮助,非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多