iosswiftavplayerios9.3

Why is AVPlayer not continuing to next song while in background?


I have been streaming music from remote source using AVPlayer. I get URLs, use one to create an AVPlayerItem, which i then associate with my instance of AVPlayer. I add an observer to the item that I associate with the player to observe when the item finishes playing ( AVPlayerItemDidPlayToEndTimeNotification ). When the observer notifies me at the item end, I then create a new AVPlayerItem and do it all over again. This works well in the foreground AND in the background on iOS 9.2.

Problem: Since I have updated to iOS 9.3 this does not work in the background. Here is the relevant code:

var portionToBurffer = Double()
var player = AVPlayer()


func prepareAudioPlayer(songNSURL: NSURL, portionOfSongToBuffer: Double){

   self.portionToBuffer = portionOfSongToBuffer

   //create AVPlayerItem
   let createdItem = AVPlayerItem(URL: songNSURL)

   //Associate createdItem with AVPlayer
   player = AVPlayer(playerItem: createdItem)

   //Add item end observer
   NSNotificationCenter.defaultCenter().addObserver(self, selector: "playerItemDidReachEnd:", name: AVPlayerItemDidPlayToEndTimeNotification, object: player.currentItem)

   //Use KVO to see how much is loaded
   player.currentItem?.addObserver(self, forKeyPath: "loadedTimeRanges", options: .New, context: nil)

}


override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if keyPath == "loadedTimeRanges" {
    if let loadedRangeAsNSValueArray = player.currentItem?.loadedTimeRanges {
        let loadedRangeAsCMTimeRange = loadedRangeAsNSValueArray[0].CMTimeRangeValue
        let endPointLoaded = CMTimeRangeGetEnd(loadedRangeAsCMTimeRange)
        let secondsLoaded = CMTimeGetSeconds(endPointLoaded)
        print("the endPointLoaded is \(secondsLoaded) and the duration is \(CMTimeGetSeconds((player.currentItem?.duration)!))")


        if secondsLoaded >= portionToBuffer  {
            player.currentItem?.removeObserver(self, forKeyPath: "loadedTimeRanges")
            player.play()
        }
    }
}
}



 func playerItemDidReachEnd(notification: NSNotification){
     recievedItemEndNotification()
 }





func recievedItemEndNotification() {
   //register background task
   bgTasker.registerBackgroundTask()

   if session.playlistSongIndex == session.playlistSongTitles.count-1 {
       session.playlistSongIndex = 0
   } else {
       session.playlistSongIndex += 1
   }

     prepareAudioPlayer(songURL: session.songURLs[session.playlistSongIndex], portionOfSongToBuffer: 30.00)

  }

I have set breakpoints to see that player.play() IS being called when in the background. When i print player.rate it reads 0.0. I have checked the property playbackLikelyToKeepUp of the AVPlayerItem and it is true. I have confirmed also that the new URL is successfully used to create the new AVPlayerItem and associated with the AVPlayer when the app is in the background. I have turned audio and airplay background capabilities on and I have even opened up a finite length background task (in code above as bgTasker.registerBackgroundTask). No idea what is going on.

I found THIS but i'm not sure it helps. Any advice would be great, thanks


Solution

  • When the observer notifies me at the item end, I then create a new AVPlayerItem and do it all over again

    But the problem is that meanwhile play stops, and the rule is that background playing is permitted only so long as you were playing in the foreground and continue to play in the background.

    I would suggest using AVQueuePlayer instead of AVPlayer. This will allow you to enqueue the next item while the current item is still playing — and thus, we may hope, this will count as continuing to play.