I want to detect if my AVPlayer is buffering for the current location, so that I can show a loader or something. But I can't seem to find anything in the documentation for AVPlayer.
15 Answers
You can observe the values of your player.currentItem
:
playerItem.addObserver(self, forKeyPath: "playbackBufferEmpty", options: .New, context: nil)
playerItem.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: .New, context: nil)
playerItem.addObserver(self, forKeyPath: "playbackBufferFull", options: .New, context: nil)
then
override public func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if object is AVPlayerItem {
switch keyPath {
case "playbackBufferEmpty":
// Show loader
case "playbackLikelyToKeepUp":
// Hide loader
case "playbackBufferFull":
// Hide loader
}
}
}

- 4,058
- 1
- 29
- 49
-
10When AVPlayer is seeking to certain position, it doesn't work. – Frank Cheng Mar 20 '19 at 03:21
-
@Marco_Santarossa I've used the above code and it is working. But after view controller is popOut the code is crashing can you help me with this. – Arun K May 16 '19 at 07:25
-
Hey @ArunK. Are you removing the observer when you dismiss your view ? Make also sure you're stopping and destroying all the objects before removing the view otherwise you might have some memory leaks. I would need an example of your code to understand if this is the case – Marco Santarossa May 17 '19 at 09:56
-
2"playbackBufferFull" is never being called in my case. Can you plz suggest me the reason? @Marco – Harish Singh May 18 '19 at 09:38
-
1@FrankCheng try setting the observation options to 0 instead of .new – Patrick Cary Apr 21 '20 at 20:44
-
@PatrickCary how to check buffer status if avPlayer has just seeked to a specific position? – Frankenxtein Jul 25 '20 at 02:31
-
Code crashing all the time. – Kudos Jul 09 '21 at 10:58
The accepted answer didn't work for me, I used the code below to show the loader efficiently.
Swift 3
//properties
var observer:Any!
var player:AVPlayer!
self.observer = self.player.addPeriodicTimeObserver(forInterval: CMTimeMake(1, 600), queue: DispatchQueue.main) {
[weak self] time in
if self?.player.currentItem?.status == AVPlayerItemStatus.readyToPlay {
if let isPlaybackLikelyToKeepUp = self?.player.currentItem?.isPlaybackLikelyToKeepUp {
//do what ever you want with isPlaybackLikelyToKeepUp value, for example, show or hide a activity indicator.
}
}
}

- 1,842
- 24
- 32
-
Dear @aytek, would you please be so kind and translate your solution to `Swift 4`? :) – ixany Aug 31 '17 at 09:41
-
Dear @ixany, I didn't have a chance to install the latest build of Xcode. I'll add the Swift 4 version as soon as possible. Thanks for your comment. – aytek Sep 25 '17 at 12:30
-
What I have observed is that you have to register to those observers on the AVPlayerItem instance and not the AVPlayer's one, otherwise it doesn't work. In fact, the accepted answer does that. – fasteque Nov 07 '17 at 14:42
For me above accepted answer didn't worked but this method does.You can use timeControlStatus but it is available only above iOS 10.
According to apple's official documentation
A status that indicates whether playback is currently in progress, paused indefinitely, or suspended while waiting for appropriate network conditions
Add this observer to the player.
player.addObserver(self, forKeyPath: “timeControlStatus”, options: [.old, .new], context: nil)
Then,Observe the changes in
func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
method.Use below code inside above method
override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "timeControlStatus", let change = change, let newValue = change[NSKeyValueChangeKey.newKey] as? Int, let oldValue = change[NSKeyValueChangeKey.oldKey] as? Int {
let oldStatus = AVPlayer.TimeControlStatus(rawValue: oldValue)
let newStatus = AVPlayer.TimeControlStatus(rawValue: newValue)
if newStatus != oldStatus {
DispatchQueue.main.async {[weak self] in
if newStatus == .playing || newStatus == .paused {
self?.loaderView.isHidden = true
} else {
self?.loaderView.isHidden = false
}
}
}
}
}
This is tested on iOS 11 above with swift 4 and It is working.

- 669
- 5
- 8
Swift 4 observations:
var playerItem: AVPlayerItem?
var playbackLikelyToKeepUpKeyPathObserver: NSKeyValueObservation?
var playbackBufferEmptyObserver: NSKeyValueObservation?
var playbackBufferFullObserver: NSKeyValueObservation?
private func observeBuffering() {
let playbackBufferEmptyKeyPath = \AVPlayerItem.playbackBufferEmpty
playbackBufferEmptyObserver = playerItem?.observe(playbackBufferEmptyKeyPath, options: [.new]) { [weak self] (_, _) in
// show buffering
}
let playbackLikelyToKeepUpKeyPath = \AVPlayerItem.playbackLikelyToKeepUp
playbackLikelyToKeepUpKeyPathObserver = playerItem?.observe(playbackLikelyToKeepUpKeyPath, options: [.new]) { [weak self] (_, _) in
// hide buffering
}
let playbackBufferFullKeyPath = \AVPlayerItem.playbackBufferFull
playbackBufferFullObserver = playerItem?.observe(playbackBufferFullKeyPath, options: [.new]) { [weak self] (_, _) in
// hide buffering
}
}
Observers need to be removed after we are done observing.
To remove these three observers just set playbackBufferEmptyObserver
, playbackLikelyToKeepUpKeyPathObserver
and playbackBufferFullObserver
to nil
.
No need to remove them manually (this is specific for observe<Value>(_ keyPath:, options:, changeHandler:)
method.

- 392
- 1
- 5
- 12

- 3,785
- 1
- 30
- 44
#Updated in Swift 4 and worked fine
As through i have gone with accepted answer but didn't work in swift 4 for me so after certain research i have found this thinks from apple doc. There are two way to determine AVPlayer states that are,
- addPeriodicTimeObserverForInterval:queue:usingBlock: and
- addBoundaryTimeObserverForTimes:queue:usingBlock:
and using ways is like this
var observer:Any?
var avplayer : AVPlayer?
func preriodicTimeObsever(){
if let observer = self.observer{
//removing time obse
avplayer?.removeTimeObserver(observer)
observer = nil
}
let intervel : CMTime = CMTimeMake(1, 10)
observer = avplayer?.addPeriodicTimeObserver(forInterval: intervel, queue: DispatchQueue.main) { [weak self] time in
guard let `self` = self else { return }
let sliderValue : Float64 = CMTimeGetSeconds(time)
//this is the slider value update if you are using UISlider.
let playbackLikelyToKeepUp = self.avPlayer?.currentItem?.isPlaybackLikelyToKeepUp
if playbackLikelyToKeepUp == false{
//Here start the activity indicator inorder to show buffering
}else{
//stop the activity indicator
}
}
}
And Don't forget to kill time observer to save from memory leak. method for killing instance, add this method according to your need but i have used it in viewWillDisappear method.
if let observer = self.observer{
self.avPlayer?.removeTimeObserver(observer)
observer = nil
}

- 922
- 7
- 21
-
-
in addPeriodicTimeObserver we have to used weak reference so I am wrapping self to used self properties!!! – Amrit Tiwari Sep 06 '18 at 11:09
In Swift 5.3
Vars:
private var playerItemBufferEmptyObserver: NSKeyValueObservation?
private var playerItemBufferKeepUpObserver: NSKeyValueObservation?
private var playerItemBufferFullObserver: NSKeyValueObservation?
AddObservers
playerItemBufferEmptyObserver = player.currentItem?.observe(\AVPlayerItem.isPlaybackBufferEmpty, options: [.new]) { [weak self] (_, _) in
guard let self = self else { return }
self.showLoadingIndicator(over: self)
}
playerItemBufferKeepUpObserver = player.currentItem?.observe(\AVPlayerItem.isPlaybackLikelyToKeepUp, options: [.new]) { [weak self] (_, _) in
guard let self = self else { return }
self.dismissLoadingIndicator()
}
playerItemBufferFullObserver = player.currentItem?.observe(\AVPlayerItem.isPlaybackBufferFull, options: [.new]) { [weak self] (_, _) in
guard let self = self else { return }
self.dismissLoadingIndicator()
}
RemoveObservers
playerItemBufferEmptyObserver?.invalidate()
playerItemBufferEmptyObserver = nil
playerItemBufferKeepUpObserver?.invalidate()
playerItemBufferKeepUpObserver = nil
playerItemBufferFullObserver?.invalidate()
playerItemBufferFullObserver = nil

- 4,278
- 40
- 52
-
-
-
In init of the class that manage the AvPlayer . I call a function that init all observers then I call the call logic to create an AVPlayerItem to stream into the player. The observers (currentItem.status, actionItemAdded and rate) are triggering but not these. – Makaille Mar 14 '22 at 15:38
-
Only solution that worked for me on iOS 16 and AVPlayer wrapped in SwiftUI's VideoPlayer. Without this, could not find a way to show an indicator. Thank you. – jusko Apr 23 '23 at 23:01
Updated for Swift 4.2
var player : AVPlayer? = nil
let videoUrl = URL(string: "https://wolverine.raywenderlich.com/content/ios/tutorials/video_streaming/foxVillage.mp4")
self.player = AVPlayer(url: videoUrl!)
self.player?.addPeriodicTimeObserver(forInterval: CMTimeMake(value: 1, timescale: 600), queue: DispatchQueue.main, using: { time in
if self.player?.currentItem?.status == AVPlayerItem.Status.readyToPlay {
if let isPlaybackLikelyToKeepUp = self.player?.currentItem?.isPlaybackLikelyToKeepUp {
//do what ever you want with isPlaybackLikelyToKeepUp value, for example, show or hide a activity indicator.
//MBProgressHUD.hide(for: self.view, animated: true)
}
}
})

- 1,119
- 11
- 9
-
That worked thanks, but it's recommended to use weak self in the block. I posted my answer. – fullmoon Apr 07 '20 at 14:32
Hmm, the accepted solution didn't work for me and the periodic observer solutions seem heavy handed.
Here's my suggestion, observe timeControlerStatus
on AVPlayer
.
// Add observer
player.addObserver(self,
forKeyPath: #keyPath(AVPlayer.timeControlStatus),
options: [.new],
context: &playerItemContext)
// At some point you'll need to remove yourself as an observer otherwise
// your app will crash
self.player?.removeObserver(self, forKeyPath: #keyPath(AVPlayer.timeControlStatus))
// handle keypath callback
if keyPath == #keyPath(AVPlayer.timeControlStatus) {
guard let player = self.player else { return }
if let isPlaybackLikelyToKeepUp = player.currentItem?.isPlaybackLikelyToKeepUp,
player.timeControlStatus != .playing && !isPlaybackLikelyToKeepUp {
self.playerControls?.loadingStatusChanged(true)
} else {
self.playerControls?.loadingStatusChanged(false)
}
}

- 2,621
- 4
- 34
- 39
We can directly Observe Playback State using the state observer method once is there any playback state changes it will be notified, it's a really easy way and it's tested with swift 5 and iOS 13.0+
var player: AVPlayer!
player.currentItem!.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: .new, context: nil)
func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
contexts: UnsafeMutableRawPointer?) {
if (player.currentItem?.isPlaybackLikelyToKeepUp ?? false) {
// End Buffering
} else {
// Buffering is in progress
}
}
Solution for Xamarin inspired by Marco's answer
// KVO registrations
private void Initialize()
{
playbackBufferEmptyObserver?.Dispose();
playbackBufferEmptyObserver = (NSObject)playerItem.AddObserver("playbackBufferEmpty",
NSKeyValueObservingOptions.New,
AVPlayerItem_BufferUpdated);
playbackLikelyToKeepUpObserver?.Dispose();
playbackLikelyToKeepUpObserver = (NSObject)playerItem.AddObserver("playbackLikelyToKeepUp",
NSKeyValueObservingOptions.New,
AVPlayerItem_BufferUpdated);
playbackBufferFullObserver?.Dispose();
playbackBufferFullObserver = (NSObject)playerItem.AddObserver("playbackBufferFull",
NSKeyValueObservingOptions.New,
AVPlayerItem_BufferUpdated);
}
private void AVPlayerItem_BufferUpdated(NSObservedChange e)
{
ReportVideoBuffering();
}
private void ReportVideoBuffering()
{
// currentPlayerItem is the current AVPlayerItem of AVPlayer
var isBuffering = !currentPlayerItem.PlaybackLikelyToKeepUp;
// NOTE don't make "buffering" as one of your PlayerState.
// Treat it as a separate property instead. Learned this the hard way.
Buffering?.Invoke(this, new BufferingEventArgs(isBuffering));
}

- 3,438
- 3
- 40
- 57
Please note that
Use a weak reference to self in the callback block to prevent creating a retain cycle.
func playRemote(url: URL) {
showSpinner()
let playerItem = AVPlayerItem(url: url)
avPlayer = AVPlayer(playerItem: playerItem)
avPlayer.rate = 1.0
avPlayer.play()
self.avPlayer.addPeriodicTimeObserver(forInterval: CMTimeMake(value: 1,
timescale: 600), queue: DispatchQueue.main, using: { [weak self] time in
if self?.avPlayer.currentItem?.status == AVPlayerItem.Status.readyToPlay {
if let isPlaybackLikelyToKeepUp = self?.avPlayer.currentItem?.isPlaybackLikelyToKeepUp {
self?.removeSpinner()
}
}
})
}
}

- 8,030
- 5
- 43
- 58
Using Combine you can easily subscribe to the publisher for when an AVPlayerItem is buffering or not like so:
// Subscribe to this and update your `View` appropriately
@Published var isBuffering = false
private var observation: AnyCancellable?
observation = avPlayer?.currentItem?.publisher(for: \.isPlaybackBufferEmpty).sink(receiveValue: { [weak self] isBuffering in
self?.isBuffering = isBuffering
})

- 316
- 2
- 12
Here is a simple method, that works with Swift 5.
This will add the loadingIndicator when your player is stalled
NotificationCenter.default.addObserver(self, selector:
#selector(playerStalled(_:)), name: NSNotification.Name.AVPlayerItemPlaybackStalled, object: self.player?.currentItem)
@objc func playerStalled(_ notification: Notification){
self.loadingIndicator.isHidden = false
self.playPauseButton.isHidden = true
}
This will show loader Indicator when buffer is empty:
if let isPlayBackBufferEmpty = self.player?.currentItem?.isPlaybackBufferEmpty{
if isPlayBackBufferEmpty{
self.loadingIndicator.isHidden = false
self.playPauseButton.isHidden = true
}
}
This will hide the loader when player is ready to play:
if self.playerItem?.status == AVPlayerItem.Status.readyToPlay{
if let isPlaybackLikelyToKeepUp = self.player?.currentItem?.isPlaybackLikelyToKeepUp {
if isPlaybackLikelyToKeepUp{
self.loadingIndicator.isHidden = true
self.playPauseButton.isHidden = false
}
}
}

- 683
- 3
- 23
-
for me the above notifcation is not getting called, can u please help – Kedar Sukerkar Oct 06 '19 at 21:30
You can check if the player is buffering/loading like this:
let playerObserver = self.player.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, preferredTimescale: 1), queue: DispatchQueue.main, using: { [weak self] time in
if self?.player.timeControlStatus == .playing {
debugPrint("#player - info: isPlaying")
self?.playButton.isSelected = true
} else if self?.player.timeControlStatus == .paused {
debugPrint("#player - info: isPaused")
self?.playButton.isSelected = false
} else if self?.player.timeControlStatus == .waitingToPlayAtSpecifiedRate {
debugPrint("#player - info: isWaiting") //Buffering
}
})

- 481
- 4
- 11
For RXswift fans, you can check AVPlayer's buffering state by adding an extension to the Reactive
class:
extension Reactive where Base: AVPlayerItem {
public var playbackBufferEmpty: Observable<Bool> {
return self.observe(Bool.self, #keyPath(AVPlayerItem.isPlaybackBufferEmpty))
.map { $0 ?? false }
}
}
And use it as follows:
avPlayerItem.rx.playbackBufferEmpty
.subscribe(onNext: {isLoading in
//Do whatever you want
}).disposed(by: disposeBag)

- 41
- 1
- 2