检查AVPlayer的播放状态

122

有没有办法知道 AVPlayer 的播放是否停顿或已经结束?

13个回答

360

您可以使用以下方法判断是否正在播放:

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

Swift 3扩展:

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

34
并不是一定的,它不能处理因文件错误而导致玩家停滞的情况。这是我吃过亏才发现的...... - Dermot
7
如Dermot所说,如果您在飞行模式下尝试播放某个内容,AVPlayer的速率仍将设置为1.0,因为这意味着您有播放意图。 - phi
4
AVPlayer有一个错误属性,只需检查它不为nil以及检查速率不为0即可 :) - James Campbell
26
请注意,答案已更新以防止@Dermot情景发生。因此这个答案确实有效。 - jomafer
2
当播放视频素材倒放(- [AVPlayer setRate:-1.0])时,将无法工作,因为-1.0小于0。 - Julian F. Weinert
显示剩余6条评论

78
在iOS10中,现在有一个内置属性:timeControlStatus
例如,这个函数根据avPlayer的状态播放或暂停,并适当更新播放/暂停按钮。
@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)
    }
}

关于您的第二个问题,要知道avPlayer是否已到达结尾,最简单的方法是设置通知。

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

例如,当视频播放到结尾时,您可以让其倒回到视频开头并将暂停按钮重置为播放。

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

如果您正在创建自己的控件,这些示例很有用,但是如果您使用AVPlayerViewController,则内置了控件。


易于使用,适用于iOS 10+。 - technerd
2
@objc func didPlayToEnd() 对于 Swift 4.2。 - Sean Stayns

59

要接收在项目结束时的通知(来自Apple):

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

要跟踪播放进度,您可以使用 addPeriodicTimeObserverForInterval:queue:usingBlock:addBoundaryTimeObserverForTimes:queue:usingBlock: 监听 AVPlayer 对象中播放头位置的更改。

以下是苹果提供的示例:

// 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];
}];

你可以加入一个布尔标志来检查播放状态。 - user749127
如果您使用-replaceCurrentItemWithPlayerItem:更改播放器的项目,但未正确处理通知,则可能会导致问题。对我而言,一种更可靠的方法是使用KVO跟踪AVPlayer的状态。有关更多详细信息,请参见下面的答案:https://dev59.com/c2035IYBdhLWcg3wC7if#34321993 - maxkonovalov
2
还需要错误通知吗?AVPlayerItemFailedToPlayToEndTimeNotification - ToolmakerSteve

25

rate 不是检测视频是否正在播放的方法(它可能会停顿)。从 rate 的文档中可以看出:

指示所需的播放速率;0.0 表示“暂停”,1.0 表示希望以当前项目的自然速率播放。

关键词“希望播放”- 1.0 的速率并不意味着视频正在播放。

iOS 10.0 及以上版本的解决方案是使用 AVPlayerTimeControlStatus,它可以在 AVPlayertimeControlStatus 属性上观察。

iOS 10.0 之前(9.0、8.0 等)的解决方案是编写自己的解决方案。0.0 的速率意味着视频已暂停。当 rate != 0.0 时,表示视频正在播放或者停顿了。

您可以通过观察播放器时间来了解区别,具体方法为:使用 func addPeriodicTimeObserver(forInterval interval: CMTime, queue: DispatchQueue?, using block: @escaping (CMTime) -> Void) -> Any

该块返回当前播放器时间 CMTime,因此比较上一个从块获取的时间 lastTime 和刚报告的时间 currentTime 将告诉播放器是正在播放还是停顿。例如,如果 lastTime == currentTime 并且 rate != 0.0,那么播放器就停顿了。

正如其他人所指出的,确定播放是否已完成由 AVPlayerItemDidPlayToEndTimeNotification 表示。


18
更可靠的替代方法是将自己作为观察者添加到播放器的“rate”属性中,而不是使用NSNotification
[self.player addObserver:self
              forKeyPath:@"rate"
                 options:NSKeyValueObservingOptionNew
                 context:NULL];

然后检查观察速率的新值是否为零,这意味着播放因某些原因停止,如到达结尾或由于空缓冲而停顿。

- (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
        }
    }
}

针对 rate == 0.0 的情况,为了了解播放停止的确切原因,您可以进行以下检查:

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
}

不要忘记在播放器到达结尾时停止播放:

self.player.actionAtItemEnd = AVPlayerActionAtItemEndPause;


我认为还需要通知无法到达结尾的失败 - AVPlayerItemFailedToPlayToEndTimeNotification。如果发生此错误,将永远无法到达结束时间。或者根据maz的答案,将检查player.error != nil添加到您的代码中,在这种情况下,由于错误而“结束”播放。 - ToolmakerSteve
顺便问一下,你觉得使用AVPlayerItemDidPlayToEndTimeNotification的NSNotification不可靠在哪里? - ToolmakerSteve
ToolmakerSteve,感谢您的留言,我已经在我的答案中添加了错误检查。对于不可靠的通知 - 我自己在实现集合视图中的重复和可重用播放器视图时遇到了这个问题,而KVO使事情更加清晰,因为它允许将所有内容保持更本地化,而不会干扰不同播放器的通知。 - maxkonovalov

18

对于Swift

AVPlayer

let player = AVPlayer(URL: NSURL(string: "http://www.sample.com/movie.mov"))
if (player.rate != 0 && player.error == nil) {
   println("playing")
}
更新:
player.rate > 0 条件更改为 player.rate != 0,因为视频正在倒播时可以为负数,感谢 Julian 指出。
注意: 这可能与上面(Maz的)答案看起来相同,但在 Swift 中 ' ! player.error ' 给了我一个编译器错误,所以你必须使用 ' player.error == nil ' 在 Swift 中检查错误。(因为 error 属性不是 'Bool' 类型)

AVAudioPlayer:

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

AVQueuePlayer:

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

3
AVPlayer和AVAudioPlayer不相同。 - quemeful
2
注意事项:以上提到的所有内容都是在此回答出现两年前提到的。 - Dan Rosenstark
1
@Yar 我知道我也已经点赞了上面的答案,但这是针对Swift的,而在Swift中'!player.error'对我不起作用,它只允许布尔类型,所以我添加了答案。哎呀,我误点了你的评论,打算使用这个Stack Overflow应用程序回复 :) - Aks
我的担忧是我不知道 player.error 不为 nil 究竟有多好的效果。 - Dan Rosenstark
1
当播放视频镜头倒放(player.rate = -1.0)时,将无法工作,因为-1.0小于0。 - Julian F. Weinert

15

目前使用 Swift 5,最简单的检查播放器是否正在播放或暂停的方法是检查 .timeControlStatus 变量。

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

1
这不是正确的做法。问题想要知道玩家何时到达了终点。用户暂停播放也会激活此功能,从而导致意外效果。 - FontFamily

10

基于maz的答案的Swift扩展

extension AVPlayer {

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

7

用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
}

6

maxkonovalov的答案的Swift版本如下:

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

并且

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

感谢 maxkonovalov!


您好,Stanev先生,感谢您的回答。这对我不起作用(也就是说,在控制台中没有打印任何内容?) - Cesare
没事了,它确实可以工作 - 我的播放器是 nil!但我需要知道视图何时开始(而不是结束)。有什么办法吗? - Cesare

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接