Too many AVPlayers causes Terminated due to memory issue
Asked Answered
C

1

1

I have a vc that has an AVPlayer inside of it. From that vc I can push on a different vc with another player inside of it and I can keep pushing on more vcs with a player inside them also. After about the 14th vc getting pushed on the app crashes with Terminated due to memory issue.

When I look at the memory graph (9th icon in left pane) it's at about 70mb so there isn't an obscene jump in memory. All of my video files are saved to and retrieved from disk and whenever I pop a vc I have a print statement inside Deinit that always runs so there isn't anything else causing the memory issue. This led me to believe the other SO answers that said that there is a limit to 16 AVPlayers at the same time. The reason I think all of these players are causing this memory crash is because once I comment out the player initialization code I can push on 30 vcs with no crashes whatsoever.

I was about to completely remove the player, playerItem, its observers, and player layer from the parent vc in viewWillDisappear/viewDidDisappear and then once the child is popped reinitialize everything again in viewWillAppear/viewDidAppear but then I came across this blog that says

platform limitation on the number of video “render pipelines” shared between apps on the device. It turns out that setting the AVPlayer to nil does not free up a playback pipeline and that it is actually the association of a playerItem with a player that creates the pipeline in the first place

and this answer that says

It is not a limit on the number of instances of AVPlayer, or AVPlayerItem. Rather,it is the association of AVPlayerItem with an AVPlayer which creates a "render pipeline"

The question is when pushing/popping on a new vc (it will have a player inside of it) do I need to completely remove/readd everything associated with the player or will setting the AVPlayerItem to nil then reinitializing it again resolve the issue?

If the render pipelines are causing the problem it would seem that the limit isn't on the players but on the playerItems.

code:

override func viewDidLoad() {
    super.viewDidLoad()

    configurePlayer(with: self.videoUrl)
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    // only runs when popping back
    if !isMovingToParent {

        // I can either
        let asset = AVAsset(url: selfvideoUrl)
        self.playerItem = AVPlayerItem(asset: asset)
        self.player?.replaceCurrentItem(with: playerItem!)

        // or just reinitialize everything
        configurePlayer(with: self.videoUrl)
    }
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    
    // would these 2 lines be enough suffice to prevent the issue?
    self.player?.replaceCurrentItem(with: nil)
    self.playerItem = nil

    // or do I also need to nil out everything?
    self.player = nil
    self.avPlayerView.removeFromSuperView()
    self.playerStatusObserver = nil
    self.playerRateObserver = nil
    self.playerTimeControlStatusObserver = nil
}


func configurePlayer(with videoUrl: URL) {

    let asset = AVAsset(url: videoUrl)
    self.playerItem = AVPlayerItem(asset: asset)

    self.player = AVPlayer()
    self.playerLayer = AVPlayerLayer(player: player)
    self.playerLayer?.videoGravity = AVLayerVideoGravity.resizeAspect
    self.player?.automaticallyWaitsToMinimizeStalling = false
    self.playerItem.preferredForwardBufferDuration = TimeInterval(1.0)

    view.addSubview(avPlayerView) // this is just a view with a CALayer for the playerLayer
    self.playerLayer?.frame = avPlayerView.bounds
    self.avPlayerView.layer.addSublayer(playerLayer!)
    self.avPlayerView.playerLayer = playerLayer

    self.player?.replaceCurrentItem(with: playerItem!)

    // add endTimeNotification

    setNSKeyValueObservers()
}

func setNSKeyValueObservers() {

    self.playerStatusObserver = player?.observe(\.currentItem?.status, options: [.new, .old]) {
        [weak self] (player, change) in ... }

    self.playerRateObserver = player?.observe(\.rate, options:  [.new, .old], changeHandler: { 
        [weak self](player, change) in ... }

    self.playerTimeControlStatusObserver = player?.observe(\.timeControlStatus, options: [.new, .old]) {
        [weak self](player, change) in ... }
}
Chappell answered 22/10, 2020 at 15:18 Comment(0)
C
3

I just tested it and setting this to nil and reinitializing it is what allowed me to push on 30 vcs each with an AVPlayer inside of each one without any crashes whatsoever.

player?.replaceCurrentItem(with: nil)

So the issue isn't the total amount of AVPlayers, it's like this guy said, the association of AVPlayerItem with an AVPlayer which creates a "render pipeline and too many of them at the same time is what causes the problem.

override func viewDidLoad() {
    super.viewDidLoad()

    configurePlayer(with: self.videoUrl)
}


override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    if let playerItem = playerItem {
        self.player?.replaceCurrentItem(with: playerItem)
    }
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    
    self.player?.replaceCurrentItem(with: nil)
}
Chappell answered 22/10, 2020 at 15:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.