1

I'm using AVPlayer and sometimes the player randomly pauses. I'm observing \.timeControlStatus but the only response I get .paused. I'm also observing \.isPlaybackLikelyToKeepUp, \.isPlaybackBufferEmpty, and \.isPlaybackBufferFull but nothing fires for those. However using Notification.Name.AVPlayerItemPlaybackStalled I do get a print statement that says "stalled".

Even though \.rate fires when the the player pauses, that fires before .AVPlayerItemPlaybackStalled fires, sometimes \.timeControlStatus .paused fires after .AVPlayerItemPlaybackStalled and the player just sits there.

What should I do once .AVPlayerItemPlaybackStalled runs and the player is sitting paused?

NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemPlaybackStalled(_:)),
                                               name: NSNotification.Name.AVPlayerItemPlaybackStalled,
                                               object: playerItem)

@objc fileprivate func playerItemPlaybackStalled(_ notification: Notification) {
    print("playerItemPlaybackStalled")
}

Here is my full code:

let player = AVPlayer()
player.automaticallyWaitsToMinimizeStalling = false
playerLayer = AVPlayerLayer(player: player)
playerLayer?.videoGravity = AVLayerVideoGravity.resizeAspect
// add KVO observers and NotificationCenter observers
// playerItem keys are already loaded
playerItem.preferredForwardBufferDuration = TimeInterval(1.0)
player.replaceCurrentItem(with: playerItem)


playerStatusObserver = player?.observe(\.currentItem?.status, options: [.new, .old]) { [weak self] (player, change) in
        
    switch (player.status) {
    case .readyToPlay:
        DispatchQueue.main.async {
            // play video
        }
    case .failed, .unknown:
        print("Video Failed to Play")
    @unknown default:
        break
    }
}
    
playerRateObserver = player?.observe(\.rate, options:  [.new, .old], changeHandler: { [weak self](player, change) in

    if player.rate == 1  {
        DispatchQueue.main.async {
            // if player isn't playing play it
        }
    } else {
        DispatchQueue.main.async {
           // is player is playing pause it
        }
    }
})
    
playerTimeControlStatusObserver = player?.observe(\.timeControlStatus, options: [.new, .old]) { [weak self](player, change) in
        
    switch (player.timeControlStatus) {
    case .playing:
            DispatchQueue.main.async { [weak self] in
                // if player isn't playing pay it
            }
    case .paused:
            print("timeControlStatus is paused") // *** SOMETIMES PRINTS after .AVPlayerItemPlaybackStalled runs***

    case .waitingToPlayAtSpecifiedRate:
            print("timeControlStatus- .waitingToPlayAtSpecifiedRate")

    if let reason = player.reasonForWaitingToPlay {
                
            switch reason {
            case .evaluatingBufferingRate:
                    print("timeControlStatus- .evaluatingBufferingRate") // never prints
                    
            case .toMinimizeStalls:
                    print("timeControlStatus- .toMinimizeStalls") // never prints
                    
            case .noItemToPlay:
                    print("timeControlStatus- .noItemToPlay") // never prints
                    
            default:
                    print("Unknown \(reason)")
            }
        }
    @unknown default:
        break
    }
}
    
playbackLikelyToKeepUpObserver = player?.currentItem?.observe(\.isPlaybackLikelyToKeepUp, options: [.old, .new]) {  (playerItem, change) in
    print("isPlaybackLikelyToKeepUp") // never prints
}
    
playbackBufferEmptyObserver = player?.currentItem?.observe(\.isPlaybackBufferEmpty, options: [.old, .new]) { (playerItem, change) in
    print("isPlaybackBufferEmpty") // never prints
}
    
playbackBufferFullObserver = player?.currentItem?.observe(\.isPlaybackBufferFull, options: [.old, .new]) { (playerItem, change) in
    print("isPlaybackBufferFull") // never prints
}
    
NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemDidReachEnd(_:)),
                                               name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
                                               object: playerItem)

NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemFailedToPlayToEndTime(_:)),
                                           name: .AVPlayerItemFailedToPlayToEndTime,
                                           object: playerItem)
    
    
NotificationCenter.default.addObserver(self, selector: #selector(playerItemNewError(_:)),
                                           name: .AVPlayerItemNewErrorLogEntry,
                                           object: playerItem)

NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemPlaybackStalled(_:)),
                                           name: NSNotification.Name.AVPlayerItemPlaybackStalled,
                                           object: playerItem)

@objc func playerItemDidReachEnd(_ notification: Notification) {
    // show replay button
}

@objc func playerItemFailedToPlayToEndTime(_ notification: Notification) {
    print("playerItemFailedToPlayToEndTime") // never prints

    if let error = notification.userInfo?["AVPlayerItemFailedToPlayToEndTime"] as? Error {
        print(error.localizedDescription) // never prints
    }
}

@objc func playerItemNewError(_ notification: Notification) {
    print("playerItemNewErrorLogEntry") // never prints
}

@objc func playerItemPlaybackStalled(_ notification: Notification) {
    print("playerItemPlaybackStalled") // *** PRINTS ***
}
Lance Samaria
  • 17,576
  • 18
  • 108
  • 256
  • @matt btw thanks for the advice, I'll put up an activity indicator and won't touch the player. – Lance Samaria Jun 20 '20 at 16:54
  • @matt it keeps happening and the problem is nothing occurs afterwards meaning none of the other observers or notifications get called. The video is only 15 secs, it froze after 5 secs or so, I let it sit for 5 minutes expecting something to happen but nada. If it's trying to fill the buffer shouldn't it have been filled within that 5 min period and one of the other methods fired off? – Lance Samaria Jun 20 '20 at 16:55
  • Well, I guess I'm wrong. I don't know why the video would stall after just five seconds, more info is probably needed. – matt Jun 20 '20 at 16:56
  • Also I don't quite get why you're setting `automaticallyWaitsToMinimizeStalling` to `false`, this sort of defeats the purpose. – matt Jun 20 '20 at 16:57
  • I added **automaticallyWaitsToMinimizeStalling = false** thinking maybe it'll help it play faster. What do you mean it defeats the purpose? I'll comment it out to see if the stall still occurs – Lance Samaria Jun 20 '20 at 17:00
  • I just commented it out, the stall still occurs – Lance Samaria Jun 20 '20 at 17:02
  • Yeah, weird. The idea of automatically waiting is that we fill the buffer up front, I think. But clearly there's some other issue. As I said, it sounds like more info is needed. – matt Jun 20 '20 at 17:03
  • ok thanks, I'll keep playing around with it to see if I can find something out. I have question about something from your iOS 10 book: https://stackoverflow.com/q/62489214/4833705 – Lance Samaria Jun 20 '20 at 17:20

1 Answers1

0

I found the answer here. Basically inside the stalled notification I check to see if the buffer is full or not. If not I run the code from the link.

Lance Samaria
  • 17,576
  • 18
  • 108
  • 256