My app crashes with 'Terminated due to memory issue' when using AVPlayerLooper
to loop videos indefinitely. The issue only happens on older devices (it doesn't happen on iPhone 6S and newer) and with very short loops (shorter than 1 second).
I've tried using AVPlayer and observing AVPlayerItemDidPlayToEndTime
and then seeking to beginning manually but this solution causes a short break/hiccup between each loop and this is causing timing issues for my specific need.
Pausing just before calling seek(to: CMTime.zero)
doesn't solve the issue.
Here is my Looper class:
class Looper: NSObject {
private var playerItem: AVPlayerItem!
private var player: AVQueuePlayer!
private let playerLayer = AVPlayerLayer()
private var playerLooper: AVPlayerLooper!
private var isPaused: Bool = false {
didSet {
isPaused ? player.pause() : player.play()
}
}
weak var delegate: LooperDelegate?
required init(videoURL: URL) {
super.init()
let asset = AVURLAsset(url: videoURL, options: ["AVURLAssetPreferPreciseDurationAndTimingKey" : true])
playerItem = AVPlayerItem(asset: asset)
player = AVQueuePlayer(playerItem: playerItem)
playerLayer.player = player
playerLooper = AVPlayerLooper(player: player, templateItem: playerItem)
player.addObserver(self, forKeyPath: #keyPath(AVPlayer.status), options: [.old, .new], context: nil)
player.addObserver(self, forKeyPath: #keyPath(AVPlayer.timeControlStatus), options: [.old, .new], context: nil)
playerLooper.addObserver(self, forKeyPath: #keyPath(AVPlayerLooper.status), options: [.old, .new], context: nil)
}
deinit {
player.removeObserver(self, forKeyPath: #keyPath(AVPlayer.status))
player.removeObserver(self, forKeyPath: #keyPath(AVPlayer.timeControlStatus))
playerLooper.removeObserver(self, forKeyPath: #keyPath(AVPlayerLooper.status))
}
func add(in parentLayer: CALayer) {
playerLayer.frame = parentLayer.bounds
parentLayer.addSublayer(playerLayer)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if object as AnyObject? === self.player &&
keyPath == #keyPath(AVPlayer.status),
let statusNumber = change?[.newKey] as? NSNumber,
AVPlayer.Status(rawValue: statusNumber.intValue)! == .readyToPlay {
self.delegate?.isReadyToPlay(duration: playerItem.asset.duration.seconds)
print("Duration: \(playerItem.asset.duration.seconds)")
} else if object as AnyObject? === self.player &&
keyPath == #keyPath(AVPlayer.timeControlStatus),
let oldKey = change?[.oldKey] as? NSNumber,
let newKey = change?[.newKey] as? NSNumber,
oldKey != 2 && newKey == 2 {
self.delegate?.isLooping()
print("isLooping")
} else if object as AnyObject? === self.playerLooper &&
keyPath == #keyPath(AVPlayerLooper.status),
let statusNumber = change?[.newKey] as? NSNumber {
if AVPlayerLooper.Status(rawValue: statusNumber.intValue)! == .ready {
self.delegate?.isReadyForDisplay()
print("isReadyForDisplay")
}
}
}
}