Search code examples
iosswiftavfoundationavplayer

AVPlayer status is updated when app comes to foreground


I'm building a music player in my iOS app, using AVPlayer. I listen to changes for the AVPlayer.status property like this, to know when audio is ready to be played:

player.currentItem!.addObserver(self, forKeyPath: "status", options: .New, context: nil)

And when the status is .ReadyToPlay I automatically start playback:

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        if (keyPath == "status") {
            if let currentItem = self.player?.currentItem {
                let status = currentItem.status
                if (status == .ReadyToPlay) {                    
                    self.play()
                }
            }
        }        
    }
}

Works great. The problem, however, is that if I start playing music in my app, pause the music and then leave the app and start playing music in, for example, Spotify, the AVPlayer's status property seems to be changed to .ReadyToPlay again the next time my app comes to the foreground, which causes the observer to fire, which in turn causes the music to start playing again.

I assume that something happens with the AVPlayer instance when the app gets focus again, which causes the status property to change/refresh.

How do I prevent this behaviour?


Solution

  • This seems like expected behavior. If you want to ensure that you only begin playback the first time the AVPlayerItem status changes, remove the observer after calling play().

    One caveat here is that you should already be removing the observer when the currentItem is changed on the player, so you will need to use an additional flag to track whether you are observing the existing currentItem.

    The owner of the player would keep track of state

    var isObservingCurrentItem = false
    

    And update/check that state when you add the observer

    if currentItem = player.currentItem where isObservingCurrentItem {
        currentItem.removeObserver(self, forKeyPath:"status")
    }
    
    player.currentItem!.addObserver(self, forKeyPath: "status", options: .New, context: nil)
    isObservingCurrentItem = true
    

    Then you can safely remove the observer once the player is ready to play

    override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
    
        if let object = object,
            keyPath = keyPath,
            currentItem = self.player?.currentItem,
            status = currentItem.status
            where status == .ReadyToPlay {
                self.play()
                object.removeObserver(self, forKeyPath:keyPath)
                isObservingCurrentItem = false
        }
    }