AVPlayer does not fire playbackBufferEmpty but does not play either
Asked Answered
A

1

9

I use AVPlayer to play an audio livestream via internet. I like to recover playback if it was paused for longer than 1 minute.

I call player.rate = 1.0 to resume. However if the stream was paused for >1 minute it does not play any more. I need to recreate AVPlayerItem in this case to make it work again.

So how can I catch this case, so I know the playback did not recover?

  • I tried KVO on player.rate. It stays at 1.0 though. The player is not playing!
  • I tried KVO on currentItem.playbackBufferEmpty. It is not called in this case though.
  • currentItem.status does not switch to .Failed. It does not change at all.

The AVPlayer just seems to do nothing in this case. Any ideas?

I build a Playground code to demonstrate the issue:

import UIKit
import AVFoundation

// keep it running forever so it plays audio
import XCPlayground
XCPSetExecutionShouldContinueIndefinitely(true)

class AVPlayerTest {

    let player = AVPlayer()
    let streamurl = NSURL(string: "http://detektor.fm/stream/mp3/musik/")!

    func startTest() {
        let item = AVPlayerItem(URL: streamurl)
        player.replaceCurrentItemWithPlayerItem(item)
        player.play()

        // give it some start time to build a buffer
        NSTimer.scheduledTimerWithTimeInterval(2, target: self, selector: #selector(timerTickedToPause), userInfo: nil, repeats: false)
    }

    @objc func timerTickedToPause(timer: NSTimer) {
        player.pause()
        // pause now for some time. 90s is not enough.
        NSTimer.scheduledTimerWithTimeInterval(120, target: self, selector: #selector(timerTickedToPlay), userInfo: nil, repeats: false)
    }

    @objc func timerTickedToPlay(timer: NSTimer) {
        // try to resume playback

        print(player.rate)

        player.play()

        print(player.rate)

        // check in some seconds if it recovered
        NSTimer.scheduledTimerWithTimeInterval(10, target: self, selector: #selector(timerTickedCheck), userInfo: nil, repeats: false)
    }

    @objc func timerTickedCheck(timer: NSTimer) {

        // it reports rate = 1.0 but is not playing here though!
        // there is no way to know for me it did not recover here!?
        print(player.rate)

        // recover by creating a new item works
        let item = AVPlayerItem(URL: streamurl)
        player.replaceCurrentItemWithPlayerItem(item)
        player.play()
    }
}

let test = AVPlayerTest()
test.startTest()
Auctorial answered 26/4, 2016 at 8:9 Comment(10)
This looks like the same question: #36617732Auctorial
yes, exactly same, it works with fixed (non-livestream) videos, but on livestream it fails, I always re-initialize the player with item :/ If this question gets the solution please post it to mine as well.Pirozzo
I am perfectly ok to recreate AVPlayerItem. However I need to know if it failed.Auctorial
Don't have time to try, but a suggestion, can you try player.pause() then player.play() and check after this if rate is 1Pirozzo
I did that in the debugger already. player.pause() and player.play() is the same as setting player.rate to 0 or 1 like Apple states in its documentation. After calling player.rate = 1 it does not play but rate stays on 1.Auctorial
See my response here - https://mcmap.net/q/1317797/-avplayer-can-39-t-resume-after-paused-some-waitingQuass
A lot of this seems to be fixed by Apple in iOS 10.Auctorial
Same problem here. Did you find the solution?Stromberg
Works for me using the iOS 10 avplayer.Auctorial
What is working. What did you do for? I'am using iOS 10 and AVPlayer to stream a asset url, yp.shoutcast.com/sbin/tunein-station.pls?id=1577011 (for example) and I encounter exactly the same problem you described.Stromberg
W
7

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)")
        }
    }

}

I have the same issue but in my case, it occurs if I go into background while in the paused state. When I come back to forground .play() does not work. 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.

Whoredom answered 27/3, 2018 at 4:22 Comment(2)
didn't knew about timeControlStatus but seems to work like a charm, great answer!Velocipede
I was forced to reload asset in AVPlayer because of the same reason. It must be something about stream properties though. I am playing audio streams from one provider - they play ok after interruption. Streams from another provider are never able to play after interruption (they are VAST ads, if anyone finds this info relevant).Musketeer

© 2022 - 2024 — McMap. All rights reserved.