[ios] Check play state of AVPlayer

Is there a way to know whether an AVPlayer playback has stalled or reached the end?

This question is related to ios objective-c iphone avfoundation avplayer

The answer is


rate is NOT the way to check whether a video is playing (it could stalled). From documentation of rate:

Indicates the desired rate of playback; 0.0 means "paused", 1.0 indicates a desire to play at the natural rate of the current item.

Key words "desire to play" - a rate of 1.0 does not mean the video is playing.

The solution since iOS 10.0 is to use AVPlayerTimeControlStatus which can be observed on AVPlayer timeControlStatus property.

The solution prior to iOS 10.0 (9.0, 8.0 etc.) is to roll your own solution. A rate of 0.0 means that the video is paused. When rate != 0.0 it means that the video is either playing or is stalled.

You can find out the difference by observing player time via: func addPeriodicTimeObserver(forInterval interval: CMTime, queue: DispatchQueue?, using block: @escaping (CMTime) -> Void) -> Any

The block returns the current player time in CMTime, so a comparison of lastTime (the time that was last received from the block) and currentTime (the time that the block just reported) will tell whether the player is playing or is stalled. For example, if lastTime == currentTime and rate != 0.0, then the player has stalled.

As noted by others, figuring out whether playback has finished is indicated by AVPlayerItemDidPlayToEndTimeNotification.


For Swift:

AVPlayer:

let player = AVPlayer(URL: NSURL(string: "http://www.sample.com/movie.mov"))
if (player.rate != 0 && player.error == nil) {
   println("playing")
}

Update:
player.rate > 0 condition changed to player.rate != 0 because if video is playing in reverse it can be negative thanks to Julian for pointing out.
Note: This might look same as above(Maz's) answer but in Swift '!player.error' was giving me a compiler error so you have to check for error using 'player.error == nil' in Swift.(because error property is not of 'Bool' type)

AVAudioPlayer:

if let theAudioPlayer =  appDelegate.audioPlayer {
   if (theAudioPlayer.playing) {
       // playing
   }
}

AVQueuePlayer:

if let theAudioQueuePlayer =  appDelegate.audioPlayerQueue {
   if (theAudioQueuePlayer.rate != 0 && theAudioQueuePlayer.error == nil) {
       // playing
   }
}

Answer is Objective C

if (player.timeControlStatus == AVPlayerTimeControlStatusPlaying) {
    //player is playing
}
else if (player.timeControlStatus == AVPlayerTimeControlStatusPaused) {
    //player is pause
}
else if (player.timeControlStatus == AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate) {
    //player is waiting to play
}

Swift extension based on the answer by maz

extension AVPlayer {

    var isPlaying: Bool {
        return ((rate != 0) && (error == nil))
    }
}

Currently with swift 5 the easiest way to check if the player is playing or paused is to check the .timeControlStatus variable.

player.timeControlStatus == .paused
player.timeControlStatus == .playing

player.timeControlStatus == AVPlayer.TimeControlStatus.playing

A more reliable alternative to NSNotification is to add yourself as observer to player's rate property.

[self.player addObserver:self
              forKeyPath:@"rate"
                 options:NSKeyValueObservingOptionNew
                 context:NULL];

Then check if the new value for observed rate is zero, which means that playback has stopped for some reason, like reaching the end or stalling because of empty buffer.

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<NSString *,id> *)change
                       context:(void *)context {
    if ([keyPath isEqualToString:@"rate"]) {
        float rate = [change[NSKeyValueChangeNewKey] floatValue];
        if (rate == 0.0) {
            // Playback stopped
        } else if (rate == 1.0) {
            // Normal playback
        } else if (rate == -1.0) {
            // Reverse playback
        }
    }
}

For rate == 0.0 case, to know what exactly caused the playback to stop, you can do the following checks:

if (self.player.error != nil) {
    // Playback failed
}
if (CMTimeGetSeconds(self.player.currentTime) >=
    CMTimeGetSeconds(self.player.currentItem.duration)) {
    // Playback reached end
} else if (!self.player.currentItem.playbackLikelyToKeepUp) {
    // Not ready to play, wait until enough data is loaded
}

And don't forget to make your player stop when it reaches the end:

self.player.actionAtItemEnd = AVPlayerActionAtItemEndPause;


You can tell it's playing using:

AVPlayer *player = ...
if ((player.rate != 0) && (player.error == nil)) {
    // player is playing
}

Swift 3 extension:

extension AVPlayer {
    var isPlaying: Bool {
        return rate != 0 && error == nil
    }
}

The Swift version of maxkonovalov's answer is this:

player.addObserver(self, forKeyPath: "rate", options: NSKeyValueObservingOptions.New, context: nil)

and

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
    if keyPath == "rate" {
        if let rate = change?[NSKeyValueChangeNewKey] as? Float {
            if rate == 0.0 {
                print("playback stopped")
            }
            if rate == 1.0 {
                print("normal playback")
            }
            if rate == -1.0 {
                print("reverse playback")
            }
        }
    }
}

Thank you maxkonovalov!


In iOS10, there's a built in property for this now: timeControlStatus

For example, this function plays or pauses the avPlayer based on it's status and updates the play/pause button appropriately.

@IBAction func btnPlayPauseTap(_ sender: Any) {
    if aPlayer.timeControlStatus == .playing {
        aPlayer.pause()
        btnPlay.setImage(UIImage(named: "control-play"), for: .normal)
    } else if aPlayer.timeControlStatus == .paused {
        aPlayer.play()
        btnPlay.setImage(UIImage(named: "control-pause"), for: .normal)
    }
}

As for your second question, to know if the avPlayer reached the end, the easiest thing to do would be to set up a notification.

NotificationCenter.default.addObserver(self, selector: #selector(self.didPlayToEnd), name: .AVPlayerItemDidPlayToEndTime, object: nil)

When it gets to the end, for example, you can have it rewind to the beginning of the video and reset the Pause button to Play.

@objc func didPlayToEnd() {
    aPlayer.seek(to: CMTimeMakeWithSeconds(0, 1))
    btnPlay.setImage(UIImage(named: "control-play"), for: .normal)
}

These examples are useful if you're creating your own controls, but if you use a AVPlayerViewController, then the controls come built in.


Examples related to ios

Adding a UISegmentedControl to UITableView Crop image to specified size and picture location Undefined Symbols error when integrating Apptentive iOS SDK via Cocoapods Keep placeholder text in UITextField on input in IOS Accessing AppDelegate from framework? Autoresize View When SubViews are Added Warp \ bend effect on a UIView? Speech input for visually impaired users without the need to tap the screen make UITableViewCell selectable only while editing Xcode 12, building for iOS Simulator, but linking in object file built for iOS, for architecture arm64

Examples related to objective-c

Adding a UISegmentedControl to UITableView Keep placeholder text in UITextField on input in IOS Accessing AppDelegate from framework? Warp \ bend effect on a UIView? Use NSInteger as array index Detect if the device is iPhone X Linker Command failed with exit code 1 (use -v to see invocation), Xcode 8, Swift 3 ITSAppUsesNonExemptEncryption export compliance while internal testing? How to enable back/left swipe gesture in UINavigationController after setting leftBarButtonItem? Change status bar text color to light in iOS 9 with Objective-C

Examples related to iphone

Detect if the device is iPhone X Xcode 8 shows error that provisioning profile doesn't include signing certificate Access files in /var/mobile/Containers/Data/Application without jailbreaking iPhone Certificate has either expired or has been revoked Missing Compliance in Status when I add built for internal testing in Test Flight.How to solve? cordova run with ios error .. Error code 65 for command: xcodebuild with args: "Could not find Developer Disk Image" Reason: no suitable image found iPad Multitasking support requires these orientations How to insert new cell into UITableView in Swift

Examples related to avfoundation

How to play a sound using Swift? How to play video with AVPlayerViewController (AVKit) in Swift Check play state of AVPlayer Making the iPhone vibrate

Examples related to avplayer

How to play a local video with Swift? Check play state of AVPlayer