title: addPeriodicTimeObserver keeps AVPlayer instances
body: <p>After being all over Stack Overflow and the deepest corners of the internet, I'm yet to find the solution. I hope someone out there will be able help me out with a problem I've had for days now.</p>
  
  <p>The app in question has a collection view with lots of items. When you click on it you get to a preview collection view. And finally (where I need help) when you click "GO" you come to a collection view with cells that fills out the entire screen. It consists of an <code>AVPlayerLayer</code> and <code>AVPlayer</code>. Each time you scroll to right or left you see another video. <strong>The following code works</strong>:</p>
  
  <p><strong>UICollectionViewCell</strong></p>
  
  <pre><code>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)
      }
  }
  </code></pre>
  
  <p>In the <code>UICollectionViewController</code> I'm reusing cells. Since there is no way to know the number of <code>AVPlayer</code> instances, I simply made a helper variable inside the <code>PageCell</code> and it seems like three cells are getting reused (the normal amount when dequeuing and reusing cells). Now when I close this <code>UICollectionViewController</code> the <code>AVPlayer</code> instances seems to disappear/close.</p>
  
  <p>Now, the problem arises when I want to loop the videos. Using <code>AVPlayerLooper</code> is not an option because it simply is too laggy (I've implemented it in a dusin different ways without luck). So my solution was to use a period time observer inside the <code>videoAsset?.loadValuesAsynchronously</code> block:</p>
  
  <pre><code>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()
          }
      }
  })
  </code></pre>
  
  <h2>Problem</h2>
  
  <p>Video showcasing problem: <a href="https://app.box.com/s/zv3du1m69uc3uko57xcbd2zinlkyy4yy" rel="nofollow noreferrer">Problem video</a></p>
  
  <p>The problem arises when having +17 <code>AVPlayer</code> instances running simultaneously then suddenly the videos won't load anymore. iOS devices have a hardware limitation of everything from 4 to 18 <code>AVPlayer</code> instances running at the same time (most likely depending on RAM), see the following Stack Overflow posts just to mention a few:</p>
  
  <p><a href="https://stackoverflow.com/questions/34009913/avplayeritemstatusfailed-error-when-too-many-avplayer-instances-created">AVPlayerItemStatusFailed error when too many AVPlayer instances created</a></p>
  
  <p><a href="https://stackoverflow.com/questions/40474480/how-many-avplayers-are-allowed-to-be-created-at-the-same-time/40476819#40476819">How many AVPlayers are allowed to be created at the same time?</a></p>
  
  <p><a href="https://stackoverflow.com/questions/39073964/avplayer-not-able-to-play-after-playing-some-items/45020914#45020914">AVPlayer not able to play after playing some items</a></p>
  
  <p><a href="https://stackoverflow.com/questions/34541105/loading-issues-with-avplayeritem-from-local-url/34649381#34649381">Loading issues with AVPlayerItem from local URL</a></p>
  
  <p>See also these articles for more insight:</p>
  
  <p><a href="https://medium.com/@Pinterest_Engineering/building-native-video-pins-7ff89ad3ec33" rel="nofollow noreferrer">Building native video Pins</a></p>
  
  <p><a href="https://beckyhansmeyer.com/2017/08/30/too-many-avplayers/" rel="nofollow noreferrer">Too Many AVPlayers?</a></p>
  
  <p>Because the problem only occurs when adding the time observer I suspect that it keeps the AVPlayer instances "alive" even though I've closed the collection view.</p>
  
  <p>Notes to <a href="https://app.box.com/s/zv3du1m69uc3uko57xcbd2zinlkyy4yy" rel="nofollow noreferrer">Problem video</a>: Every time I press "GO" one <code>AVPlayer</code> instance is created. When I swipe to the right two more cells are created hence two more <code>AVPlayer</code> instances. All in all 3 <code>AVPlayer</code> instances are created each time accumulating in the end to about 17 instances and the videos will not load anymore. I've tried scrolling through all the videos each time I've pressed "GO" but this does not change the outcome with a maximum of three cells being reused all the time.</p>
  
  <h2>What I've tried to solve the problem</h2>
  
  <p><strong>Try 1:</strong>
  I made <code>playerLayer</code> a global variable and in <code>viewDidDisappear</code> inside the <code>UICollectionViewController</code> I added:</p>
  
  <p><code>playerLayer?.player = nil</code></p>
  
  <p>This resulted in one <code>AVPlayer</code> instance to disappear/close when I closed the cells onto the "GO" page (i.e. the view disappeared). This meant that I hit 17 instances a bit later than when I didn't add this code. Together with the code above I also tried adding <code>playerLayer = nil</code>, <code>playerLayer?.player?.replaceCurrentItem(with: nil)</code> and <code>playerLayer?.removeFromSuperlayer()</code> but the only thing that changed anything was <code>playerLayer?.player = nil</code>. It should be noted that if I do not scroll and simply open the first video and close, open the first video and close and so on, I could do it "forever" without any problems. So, in this case one instance was created and then closed/disappeared afterwards.</p>
  
  <p>Video showcasing try 1: <a href="https://app.box.com/s/uc387tlud0jgqd2rotsw1j8vc5d156rq" rel="nofollow noreferrer">Try 1 video</a></p>
  
  <p><strong>Try 2:</strong>
  I changed the <code>addPeriodicTimeObserver</code> block to:</p>
  
  <pre><code>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()
          }
      }
  })
  </code></pre>
  
  <p>In this block I essentially changed all <code>self.player?</code> to <code>playerLayer?.player?</code>.</p>
  
  <p>This made me able to open and close the view with the videos as much as I wanted - so the <code>AVPlayer</code> instances were somehow closing/disappearing. Though now the looping didn't work. The first video would loop initially. But then I swiped to the second cell and this video would not loop. Then I swiped back to the first cell and now this wouldn't loop either. If I added <code>playerLayer?.player = nil</code> like in "Try 1" no effect occurred with neither the open and close nor the loops.</p>
  
  <p>Video showcasing try 2: <a href="https://app.box.com/s/oor4qmd5cnl5qx9jfk6240y0e1ydnpui" rel="nofollow noreferrer">Try 2 video</a></p>
  
  <p><strong>Try 3:</strong>
  I made the <code>timeObserver</code> variable global and tried many many things. But when I tried to remove the observer(s) it always resulted in the following error:</p>
  
  <pre><code>'NSInvalidArgumentException', reason: 'An instance of AVPlayer cannot remove a time observer that was added by a different instance of AVPlayer.'
  </code></pre>
  
  <p>The error indicates that the time observers are clearly all over the place. This was clear when I added helper variables (counters) inside the <code>addPeriodicTimeObserver</code> block and found out that the time observers were quickly adding up. At one point with all the different combinations of the code presented in this post, I was able to remove the time observer at <em>any</em> time during the scrolling. But this resulted in only removing a single time observer out of many - resulting in the same situation as in "Try 1" where I was able to remove a single <code>AVPlayer</code> instance each time I closed the view.</p>
  
  <p><strong>General note:</strong>
  To test whether or not it really was only three <code>AVPlayer</code> instances that were created each time I press "GO" and swiped I made a test where I scrolled through over 20 videos, but there was no problem loading them. Therefore I'm quite sure, as mentioned earlier, that a maximum of three <code>AVPlayer</code> instances are created in the view each time.</p>
  
  <h2>Conclusion</h2>
  
  <p>The problem is, as I see it, that when I add the time observers in order to loop the videos they accumulate and keep the <code>AVPlayer</code> instances "alive". Remember that I had no problem at all without the time observers. When applying <code>playerLayer?.player = nil</code> it seems as though, I was able to "save" an instance but then again it's so hard to tell when I don't know the amount of <code>AVPlayer</code> instances currently "active". Simply put, all I wanna do is to delete all <code>AVPlayer</code> instances the moment the view disappears and it will be happy days. If anyone got this far, I will be overly grateful if you are able to help me out.</p>