Check Play State of Avplayer

Check play state of AVPlayer

To get notification for reaching the end of an item (via Apple):

[[NSNotificationCenter defaultCenter] 
addObserver:<self>
selector:@selector(<#The selector name#>)
name:AVPlayerItemDidPlayToEndTimeNotification
object:<#A player item#>];

And to track playing you can:

"track changes in the position of the playhead in an AVPlayer object" by using addPeriodicTimeObserverForInterval:queue:usingBlock: or addBoundaryTimeObserverForTimes:queue:usingBlock:.

Example is from Apple:

// Assume a property: @property (retain) id playerObserver;

Float64 durationSeconds = CMTimeGetSeconds([<#An asset#> duration]);
CMTime firstThird = CMTimeMakeWithSeconds(durationSeconds/3.0, 1);
CMTime secondThird = CMTimeMakeWithSeconds(durationSeconds*2.0/3.0, 1);
NSArray *times = [NSArray arrayWithObjects:[NSValue valueWithCMTime:firstThird], [NSValue valueWithCMTime:secondThird], nil];

self.playerObserver = [<#A player#> addBoundaryTimeObserverForTimes:times queue:NULL usingBlock:^{
// Passing NULL for the queue specifies the main queue.

NSString *timeDescription = (NSString *)CMTimeCopyDescription(NULL, [self.player currentTime]);
NSLog(@"Passed a boundary at %@", timeDescription);
[timeDescription release];
}];

Detect when AVPlayer is playing

As far as I know, I agree with you that there is a slight delay between when the play() function is called and the video actually plays (In another word, the time that the first frame of the video has been rendered). The delay depends on some criteria such as video types (VOD or live streaming), the network condition, ... However, fortunately, we are able to know whenever the first frame of the video rendered, I mean exactly when the video actually plays.

By observing the status of the current AVPlayerItem and whenever it is AVPlayerItemStatusReadyToPlay, that should be the first frame has been rendered.

[self.playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL];

-(void)observeValueForKeyPath:(NSString*)path ofObject:(id)object change:(NSDictionary*)change context:(void*) context {

if([self.playerItem status] == AVPlayerStatusReadyToPlay){
NSLog(@"The video actually plays")
}
}

By the way, there is another solution where we observe readyForDisplay status of AVPlayerLayer, it also indicates whenever the video rendered. However, this solution has a drawback as mentioned in Apple document

/*!
@property readyForDisplay
@abstract Boolean indicating that the first video frame has been made ready for display for the current item of the associated AVPlayer.
@discusssion Use this property as an indicator of when best to show or animate-in an AVPlayerLayer into view.
An AVPlayerLayer may be displayed, or made visible, while this propoerty is NO, however the layer will not have any
user-visible content until the value becomes YES.
This property remains NO for an AVPlayer currentItem whose AVAsset contains no enabled video tracks.
*/
@property(nonatomic, readonly, getter=isReadyForDisplay) BOOL readyForDisplay;

Here is the sample code

self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player]; 
[self.playerLayer addObserver:self forKeyPath:@"readyForDisplay" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL];

-(void)observeValueForKeyPath:(NSString*)path ofObject:(id)object change:(NSDictionary*)change context:(void*) context {
if([self.playerLayer isReadyForDisplay]){
NSLog(@"Ready to display");
}
}

Thereotically, [self.playerLayer isReadyForDisplay] should return YES, however, as the document, it is not guaranted.

I hope this would be helpful.

How to recognize AVPlayer is in paused state and user doing scrubbing

Observing rate and status should work - the observing callbacks are called in my code, therefore I assume they work for you as well. I guess the problem is that they get called only when the video is paused, not when the users scrubs it. So I will answer to that.

What you would probably want to observe would be currentTime. I tried it myself, and the KVO are not called for it neither when I try to observe AVPlayer or its currentItem. I have done a bit of research, and it seems that this simply won't work (see this SO question). You have to find another way to do it. One way is to add a timer and periodically check the state of it. Combined with observing rate or timeControlStatus (see this answer), you should be able to tell that the currentTime is being changed by the user even when the video is paused.

I hope you don't mind reading Swift code, this would be the first approach:

class ViewController: UIViewController {

let player = AVPlayerViewController()
var timer: Timer? = nil
var previousTime: Double = 0
var stopped = false

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

present(player, animated: true, completion: {
self.player.player = AVPlayer(url: URL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")!)
self.player.player?.play()
self.player.player?.addObserver(self, forKeyPath: "rate", options: .new, context: nil)
self.timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { (timer) in
let currentTime = self.player.player?.currentTime().seconds ?? 0
if self.previousTime != currentTime && self.stopped {
print(">>> User scrubbing")
}
self.previousTime = currentTime
})
})
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "rate" && (change?[NSKeyValueChangeKey.newKey] as? Float) == 0 {
print(">>> stopped")
stopped = true
} else if keyPath == "rate" && (change?[NSKeyValueChangeKey.newKey] as? Float) == 1 {
print(">>> played again")
stopped = false
}
}
}

Second approach, one that I would prefer, is subclassing AVPlayer and overriding its seek(to:toleranceBefore:toleranceAfter:completionHandler:) method. This method is called by the AVPlayerViewController when user scrubs the video - thus at this point you can tell that the user is scrubbing (although it is called also when user scrubs it while the video is played, so might need to test also if it is playing).

class CustomAVPlayer: AVPlayer {
override func seek(to time: CMTime, toleranceBefore: CMTime, toleranceAfter: CMTime, completionHandler: @escaping (Bool) -> Void) {
super.seek(to: time, toleranceBefore: toleranceBefore, toleranceAfter: toleranceAfter, completionHandler: completionHandler)

// if you don't call this method directly, it's called only by the
// AVPlayerViewController's slider during scrubbing
print(">> user scrubbing")
}
}

Check AVPlayer buffering and playing state

This seems similar to a question I answered here: https://stackoverflow.com/a/42148482/5100819

The approach we used to update the UI when the AVPlayer finishes buffering and starts actual audible playback would likely do the job. We used a boundary time to execute a block at the start of playback – specifically after 1/3 of a second of playback – to update the UI at roughly the same time as audio is heard:

let times = [NSValue(time:CMTimeMake(1,3))]
_ = self.player.addBoundaryTimeObserver(forTimes: times, queue: DispatchQueue.main, using: {
[weak self] time in
// Code to update the UI goes here, e.g.
activityIndicator.stopAnimating()
})

I have a little more detail on the approach (and a sample Xcode project) on our company blog: http://www.artermobilize.com/blog/2017/02/09/detect-when-ios-avplayer-finishes-buffering-using-swift/

AVPlayer, notification for play/pause state?

For iOS 10 onwards You can check new property of AVPlayer timeControlStatus.

if(avPlayerObject.timeControlStatus==AVPlayerTimeControlStatusPaused)
{
//Paused mode
}
else if(avPlayerObject.timeControlStatus==AVPlayerTimeControlStatusPlaying)
{
//Play mode
}

How to check status of AVPlayer?

I could not make it work with adding an observer on the currentItem as user @gabbler suggested.

However it helped using the notification center like this:

NSNotificationCenter.defaultCenter().addObserverForName(
AVPlayerItemFailedToPlayToEndTimeNotification,
object: nil,
queue: nil,
usingBlock: { notification in
self.stop()
})

Note that stop() is a method in the same class which stops the stream as if a stop button were clicked.



Related Topics



Leave a reply



Submit