addPeriodicTimeObserver保留AVPlayer实例

时间:2018-08-06 19:53:07

标签: ios swift avplayer avplayeritem avplayerlayer

在遍历Stack Overflow和互联网最深处之后,我还没有找到解决方案。我希望外面有人可以帮助我解决我已经有好几天的问题。

有问题的应用具有包含很多项目的收藏夹视图。当您单击它时,您将进入预览集合视图。最后(在我需要帮助的地方),当您单击“执行”时,您将进入一个包含整个屏幕的单元格的集合视图。它由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实例以某种方式关闭/消失。尽管现在循环不起作用了。第一个视频最初将循环播放。但是随后我滑到第二个单元,该视频不会循环播放。然后,我滑回了第一个牢房,现在也不会循环。如果我像“尝试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实例。

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

结论

问题是,正如我所看到的,当我添加时间观察者以循环播放他们累积的视频并使AVPlayer实例保持“活动”状态时。记住,没有时间的观察者,我根本没有问题。当应用playerLayer?.player = nil时,似乎可以“保存”实例,但是很难说出何时不知道当前“活动”的AVPlayer实例的数量。简而言之,我想做的就是在视图消失的那一刻删除所有AVPlayer实例,这将是快乐的日子。如果有人能做到这一点,如果您能帮助我,我将非常感激。

1 个答案:

答案 0 :(得分:0)

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

单元格->播放器->观察者关闭->单元格

尝试一下:

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()
        }
    }
})
相关问题