AVPlayer stops playing and doesn't resume again

前端 未结 9 1541
离开以前
离开以前 2020-12-02 06:03

In my application I have to play audio files stored on a web server. I\'m using AVPlayer for it. I have all the play/pause controls and all delegates and observ

9条回答
  •  萌比男神i
    2020-12-02 06:31

    I also ran into this issue as described here

    I tested this answer below multiple times and it worked every time so far.

    Here is what I came up with for the Swift 5 version of @wallace's answer.

    1- instead of observing the keyPath "playbackLikelyToKeepUp" I use the .AVPlayerItemPlaybackStalled Notification and inside there I check to see if the buffer is full or not via if !playerItem.isPlaybackLikelyToKeepUp {...}

    2- instead of using his PlayerHangingNotification I use a function named playerIsHanging()

    3- instead of using his PlayerContinueNotification I use a function named checkPlayerTryCount()

    4- and inside checkPlayerTryCount() I do everything the same as his (void)playerContinue function except when I ran into } else if playerTryCount == 0 { nothing would happen. To avoid that I added 2 lines of code above the return statement

    5- like @PranoyC suggested under @wallace's comments I set the playerTryCount to a max of 20 instead of 10. I also set it as a class property let playerTryCountMaxLimit = 20

    You have to add/remove your activity indicator/spinner where the comment suggests to do so

    code:

    NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemPlaybackStalled(_:)),
                                           name: NSNotification.Name.AVPlayerItemPlaybackStalled,
                                           object: playerItem)
    
    @objc func playerItemPlaybackStalled(_ notification: Notification) {
    // The system may post this notification on a thread other than the one used to registered the observer: https://developer.apple.com/documentation/foundation/nsnotification/name/1387661-avplayeritemplaybackstalled
    
        guard let playerItem = notification.object as? AVPlayerItem else { return }
        
        // playerItem.isPlaybackLikelyToKeepUp == false && if the player's current time is greater than zero && the player's current time is not equal to the player's duration
        if (!playerItem.isPlaybackLikelyToKeepUp) && (CMTimeCompare(playerItem.currentTime(), .zero) == 1) && (CMTimeCompare(playerItem.currentTime(), playerItem.duration) != 0) {
            
            DispatchQueue.main.async { [weak self] in
                self?.playerIsHanging()
            }
        }
    }
    
    var playerTryCount = -1 // this should get set to 0 when the AVPlayer starts playing
    let playerTryCountMaxLimit = 20
    
    func playerIsHanging() {
        
        if playerTryCount <= playerTryCountMaxLimit {
            
            playerTryCount += 1
    
            // show spinner
    
            checkPlayerTryCount()
    
        } else {
            // show spinner, show alert, or possibly use player?.replaceCurrentItem(with: playerItem) to start over ***BE SURE TO RESET playerTryCount = 0 ***
            print("1.-----> PROBLEM")
        }
    }
    
    func checkPlayerTryCount() {
        
        guard let player = player, let playerItem = player.currentItem else { return }
        
        // if the player's current time is equal to the player's duration
        if CMTimeCompare(playerItem.currentTime(), playerItem.duration) == 0 {
            
            // show spinner or better yet remove spinner and show a replayButton or auto rewind to the beginning ***BE SURE TO RESET playerTryCount = 0 ***
            
        } else if playerTryCount > playerTryCountMaxLimit {
            
            // show spinner, show alert, or possibly use player?.replaceCurrentItem(with: playerItem) to start over ***BE SURE TO RESET playerTryCount = 0 ***
            print("2.-----> PROBLEM")
    
        } else if playerTryCount == 0 {
    
            // *** in his answer he has nothing but a return statement here but when it would hit this condition nothing would happen. I had to add these 2 lines of code for it to continue ***
            playerTryCount += 1
            retryCheckPlayerTryCountAgain()
            return // protects against a race condition
            
        } else if playerItem.isPlaybackLikelyToKeepUp {
            
            // remove spinner and reset playerTryCount to zero
            playerTryCount = 0
            player?.play()
    
        } else { // still hanging, not at end
            
            playerTryCount += 1
            
            /*
              create a 0.5-second delay using .asyncAfter to see if buffering catches up
              then call retryCheckPlayerTryCountAgain() in a manner that attempts to avoid any recursion or threading nightmares
            */
    
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                DispatchQueue.main.async { [weak self] in
    
                    // test playerTryCount again to protect against changes that might have happened during the 0.5 second delay
                    if self!.playerTryCount > 0 {
                        
                        if self!.playerTryCount <= self!.playerTryCountMaxLimit {
                            
                          self!.retryCheckPlayerTryCountAgain()
                            
                        } else {
                            
                          // show spinner, show alert, or possibly use player?.replaceCurrentItem(with: playerItem) to start over ***BE SURE TO RESET playerTryCount = 0 ***
                          print("3.-----> PROBLEM")
                        }
                    }
                }
            }
        }
    }
    
    func retryCheckPlayerTryCountAgain() {
        checkPlayerTryCount()
    }
    

提交回复
热议问题