addPeriodicTimeObserver 保留 AVPlayer 实例
addPeriodicTimeObserver keeps AVPlayer instances
在遍历 Stack Overflow 和互联网最深处之后,我还没有找到解决方案。我希望外面的人能够帮助我解决困扰我好几天的问题。
有问题的应用程序有一个包含很多项目的集合视图。单击它时,您将进入预览集合视图。最后(我需要帮助的地方)当您单击 "GO" 时,您会看到一个集合视图,其中的单元格填满了整个屏幕。它由一个 AVPlayerLayer
和 AVPlayer
组成。每次向右或向左滚动时,您都会看到另一个视频。 以下代码有效:
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
实例似乎 disappear/close.
现在,当我想循环播放视频时,问题就出现了。使用 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 posts仅举几例:
How many AVPlayers are allowed to be created at the same time?
另请参阅这些文章以获得更多见解:
因为问题只发生在添加时间观察器时,我怀疑它保留了 AVPlayer 实例 "alive",即使我关闭了集合视图。
对 Problem video 的注释:每次我按下 "GO" 都会创建一个 AVPlayer
实例。当我向右滑动时,又创建了两个单元格,因此又创建了两个 AVPlayer
实例。每次总共创建 3 AVPlayer
个实例,最终累积到大约 17 个实例,视频将不再加载。每次按下 "GO" 时,我都尝试滚动浏览所有视频,但这不会改变结果,最多三个单元格一直被重复使用。
我尝试解决的问题
尝试 1:
我将 playerLayer
设为全局变量,并在 UICollectionViewController
内的 viewDidDisappear
中添加:
playerLayer?.player = nil
当我关闭 "GO" 页面上的单元格(即视图消失)时,这导致了一个 AVPlayer
实例到 disappear/close。这意味着我命中 17 个实例的时间比我没有添加此代码时晚了一点。除了上面的代码,我还尝试添加 playerLayer = nil
、playerLayer?.player?.replaceCurrentItem(with: nil)
和 playerLayer?.removeFromSuperlayer()
,但唯一改变的是 playerLayer?.player = nil
。应该注意的是,如果我不滚动并且只是打开第一个视频然后关闭,打开第一个视频然后关闭等等,我可以做到 "forever" 没有任何问题。因此,在这种情况下,创建了一个实例,然后是 closed/disappeared。
视频展示尝试 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
个实例不知何故 closing/disappearing。虽然现在循环不起作用。第一个视频最初会循环播放。但后来我滑动到第二个单元格,这个视频不会循环播放。然后我滑回第一个单元格,现在这也不会循环。如果我像在 "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
块内添加辅助变量(计数器)并发现时间观察者迅速累加时,这一点就很清楚了。在某一时刻,使用此 post 中提供的代码的所有不同组合,我能够在滚动期间的 任何 时间删除时间观察器。但这导致仅从多个时间观察者中删除了一个时间观察者 - 导致与 "Try 1" 中相同的情况,每次我关闭视图时我都能够删除一个 AVPlayer
实例。
一般说明:
为了测试每次我按下 "GO" 并滑动时是否真的只有三个 AVPlayer
实例被创建,我做了一个测试,我滚动浏览了 20 多个视频,但加载它们没有问题。因此,我很确定,如前所述,每次在视图中最多创建三个 AVPlayer
个实例。
结论
问题是,正如我所见,当我添加时间观察器以循环播放视频时,它们会累积并保留 AVPlayer
个实例 "alive"。请记住,没有时间观察者,我完全没有问题。当应用 playerLayer?.player = nil
时,我似乎能够 "save" 一个实例,但是当我不知道当前 AVPlayer
个实例的数量时,又很难说 "active".简而言之,我想做的就是在视图消失的那一刻删除所有 AVPlayer
个实例,这将是快乐的日子。如果有人走到这一步,如果你能帮助我,我将不胜感激。
有一个保留周期。在转义闭包中使用 weak self
。您创建的保留周期是:
cell -> Player -> observer closure -> 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()
}
}
})
在遍历 Stack Overflow 和互联网最深处之后,我还没有找到解决方案。我希望外面的人能够帮助我解决困扰我好几天的问题。
有问题的应用程序有一个包含很多项目的集合视图。单击它时,您将进入预览集合视图。最后(我需要帮助的地方)当您单击 "GO" 时,您会看到一个集合视图,其中的单元格填满了整个屏幕。它由一个 AVPlayerLayer
和 AVPlayer
组成。每次向右或向左滚动时,您都会看到另一个视频。 以下代码有效:
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
实例似乎 disappear/close.
现在,当我想循环播放视频时,问题就出现了。使用 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 posts仅举几例:
How many AVPlayers are allowed to be created at the same time?
另请参阅这些文章以获得更多见解:
因为问题只发生在添加时间观察器时,我怀疑它保留了 AVPlayer 实例 "alive",即使我关闭了集合视图。
对 Problem video 的注释:每次我按下 "GO" 都会创建一个 AVPlayer
实例。当我向右滑动时,又创建了两个单元格,因此又创建了两个 AVPlayer
实例。每次总共创建 3 AVPlayer
个实例,最终累积到大约 17 个实例,视频将不再加载。每次按下 "GO" 时,我都尝试滚动浏览所有视频,但这不会改变结果,最多三个单元格一直被重复使用。
我尝试解决的问题
尝试 1:
我将 playerLayer
设为全局变量,并在 UICollectionViewController
内的 viewDidDisappear
中添加:
playerLayer?.player = nil
当我关闭 "GO" 页面上的单元格(即视图消失)时,这导致了一个 AVPlayer
实例到 disappear/close。这意味着我命中 17 个实例的时间比我没有添加此代码时晚了一点。除了上面的代码,我还尝试添加 playerLayer = nil
、playerLayer?.player?.replaceCurrentItem(with: nil)
和 playerLayer?.removeFromSuperlayer()
,但唯一改变的是 playerLayer?.player = nil
。应该注意的是,如果我不滚动并且只是打开第一个视频然后关闭,打开第一个视频然后关闭等等,我可以做到 "forever" 没有任何问题。因此,在这种情况下,创建了一个实例,然后是 closed/disappeared。
视频展示尝试 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
个实例不知何故 closing/disappearing。虽然现在循环不起作用。第一个视频最初会循环播放。但后来我滑动到第二个单元格,这个视频不会循环播放。然后我滑回第一个单元格,现在这也不会循环。如果我像在 "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
块内添加辅助变量(计数器)并发现时间观察者迅速累加时,这一点就很清楚了。在某一时刻,使用此 post 中提供的代码的所有不同组合,我能够在滚动期间的 任何 时间删除时间观察器。但这导致仅从多个时间观察者中删除了一个时间观察者 - 导致与 "Try 1" 中相同的情况,每次我关闭视图时我都能够删除一个 AVPlayer
实例。
一般说明:
为了测试每次我按下 "GO" 并滑动时是否真的只有三个 AVPlayer
实例被创建,我做了一个测试,我滚动浏览了 20 多个视频,但加载它们没有问题。因此,我很确定,如前所述,每次在视图中最多创建三个 AVPlayer
个实例。
结论
问题是,正如我所见,当我添加时间观察器以循环播放视频时,它们会累积并保留 AVPlayer
个实例 "alive"。请记住,没有时间观察者,我完全没有问题。当应用 playerLayer?.player = nil
时,我似乎能够 "save" 一个实例,但是当我不知道当前 AVPlayer
个实例的数量时,又很难说 "active".简而言之,我想做的就是在视图消失的那一刻删除所有 AVPlayer
个实例,这将是快乐的日子。如果有人走到这一步,如果你能帮助我,我将不胜感激。
有一个保留周期。在转义闭包中使用 weak self
。您创建的保留周期是:
cell -> Player -> observer closure -> 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()
}
}
})