Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AVPlayer can't resume after paused + some waiting

After .pause() if I call .play() ok it continues but if I wait 30-60 sec after .pause() and try to .play() it sometimes fail to play,

  • AVPlayerStatus.Failed returns false

  • AVPlayerStatus.ReadyToPlay returns true

I should re-initialize player with url to make it work. Now I want to do that, if player can be played, I want to just call .play() but if not, I want to re-initialize it, my question is how to detect if player is playable? By the way it is a radio link with .pls extension

like image 541
turushan Avatar asked Apr 14 '16 08:04

turushan


2 Answers

Indeed theres no indication that when resuming after a large period of time the player is in limbo. (Except that in my tests I saw that in this situation the meta received from AVPlayerItem is null)

Anyway.. From what I've gathered from the internet (there's no proper documentation on this), when you pause, the player will keep buffering in the background and .. if you try to resume after 50-60 seconds it just cannot. A stop function would be good here.

My solution: A simple timer to check if that 50 seconds passed and if so update a flag to know that when the resume method is called I want to start on a new player.

func pausePlayer() {
   ..
    player.pause()
   ..

    // Will count to default 50 seconds or the indicated interval and only then set the bufferedInExcess flag to true
    startCountingPlayerBufferingSeconds()
    bufferedInExcess = false
}


func startCountingPlayerBufferingSeconds(interval: Double = 50) {
    timer = NSTimer.scheduledTimerWithTimeInterval(interval, target: self, selector: Selector("setExcessiveBufferedFlag"), userInfo: nil, repeats: false)
}

func setExcessiveBufferedFlag() {
    if DEBUG_LOG {
        print("Maximum player buffering interval reached.")
    }
    bufferedInExcess = true
}

func stopCountingPlayerBufferingSeconds() {
    timer.invalidate()
}

func resumePlayer() {
    if haveConnectivity() {
        if (.. || bufferedInExcess)  {
            startPlaying(true)
        } else {
            ..
            player.play
        }
       ..
    }
}

func startPlaying(withNewPlayer: Bool = false) {
    if (withNewPlayer) {
        if DEBUG_LOG {
            print("Starting to play on a fresh new player")
        }

        // If we need another player is very important to fist remove any observers for
        // the current AVPlayer/AVPlayerItem before reinitializing them and add again the needed observers
        initPlayer()

        player.play()
        ...
    }
    ...
}
like image 60
Mugurel Avatar answered Oct 07 '22 00:10

Mugurel


Assuming you have checked the AVPlayerItem.status == .playable before issuing .play()

Then use the timeControlStatus of the AVPlayer instance which "indicates whether playback is currently in progress, paused indefinitely, or suspended while waiting for appropriate network conditions."

let status = player.timeControlStatus

switch status {
case .paused:
    print("timeControlStatus.paused")

case .playing:
    print("timeControlStatus.playing")

case .waitingToPlayAtSpecifiedRate:
    print("timeControlStatus.waitingToPlayAtSpecifiedRate")
    if let reason = player.reasonForWaitingToPlay {

        switch reason {
        case .evaluatingBufferingRate:
            print("reasonForWaitingToPlay.evaluatingBufferingRate")

        case .toMinimizeStalls:
            print("reasonForWaitingToPlay.toMinimizeStalls")

        case .noItemToPlay:
            print("reasonForWaitingToPlay.noItemToPlay")

        default:
            print("Unknown \(reason)")
        }
    }

}

In my case it gets stuck the waitingToPlayAtSpecifiedRate.evaluatingBufferingRate mode. At that point the AVPlayerItem.status instance is readToPlay.

At this time of writing whenever a startCommand is received I reset the AVplayer to be sure. But this seems clunky. Looking for a smoother solution.

like image 23
David Mayes Avatar answered Oct 06 '22 23:10

David Mayes