1

I am trying to observe a time in the timeline of my AVPlayer.

I tried this on the main queue; which did not work. I then switched to a background queue, as advised from this stack overflow post; which did not with either. Looking for a working solution or an explanation as to why this isn't working.

//add boundary time notification to global queue
avLayer.player!.addBoundaryTimeObserver(forTimes: [NSValue(time: avLayer.player!.currentItem!.duration)], queue: DispatchQueue.main){

                        self.avLayer.player!.pause()
                    }


//add boundary time notification to background queue                
avLayer.player!.addBoundaryTimeObserver(forTimes: [NSValue(time: avLayer.player!.currentItem!.duration)], queue: DispatchQueue.global(qos: .userInteractive)){

                        self.avLayer.player!.pause()
                    }

Update: After retaining a strong reference to the return value of the observer, I set a breakpoint in the callback. It is still not working.

 //add boundary time notification
                    boundaryTimeObserver = avLayer.player!.addBoundaryTimeObserver(forTimes: [NSValue(time: avLayer.player!.currentItem!.duration)], queue: DispatchQueue.main){

                        self.avLayer.player!.pause()
                    }
Phantômaxx
  • 37,901
  • 21
  • 84
  • 115
wizeOnes
  • 119
  • 16

2 Answers2

3

2019 simple example ..

var player = AVPlayer()
var token: Any?

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    let u = "https... "
    
    let playerItem = AVPlayerItem(url: URL(string: u)!)
    player = AVPlayer(playerItem: playerItem)
    player.play()

    token = player.addBoundaryTimeObserver(
       forTimes: [0.5 as NSValue],
       queue: DispatchQueue.main) { [weak self] in
         self?.spinner.stopAnimating()
         print("The audio is in fact beginning about now...")
    }
}

Works perfectly.

Important .. it won't find "0"

Use a small value to find the "beginning" as a quick solution.

Community
  • 1
  • 1
Fattie
  • 27,874
  • 70
  • 431
  • 719
2

There may be two problems:

  1. As the documentation for addBoundaryTimeObserver states:

    You must maintain a strong reference to the returned value as long as you want the time observer to be invoked by the player

    As your initial code does not keep a reference to the returned internal opaque time observer, the observer probably is released immediately and thus is never called.

  2. Make sure the time you register for observing actually has the correct value:

    • playerItem.duration may be indefinite (see documentation of this property)
    • even the duration of the playerItem's asset may be unknown, or an in-precise estimation, depending on the type of the asset and loading state (again, see documentation of AVAsset.duration on this).

    As a consequence, the time you register for observing may never be reached (note that the time can easily be checked by inserting a CMTimeShow(duration))

Approaches to resolve this:

  • if you just want to stop the player when the playerItem's end is reached, setting player.actionAtItemEnd to pause may be sufficient

  • if you need to execute some custom logic when the item's end is reached, register an observer for AVPlayerItemDidPlayToEndTime notifications with the playerItem as object. This mechanism is independent from possibly in-precise durations and so hopefully more reliable

NoHalfBits
  • 604
  • 1
  • 5
  • 10