检测 AVPlayer 何时播放

Detect when AVPlayer is playing

如何检测 AVPlayer 正在播放?。在调用 play() 函数和实际播放视频之间似乎有轻微的延迟。

视频:

AVPlayer 有一个参数叫做 rate (Float),如果 rate 大于 0.0,则有一个正在播放的视频。

您可以检查 rate 是否为 !=0:(如果玩家向后移动,则速率可能为负数)

if vidPlayer != nil && vidPlayer.rate != 0 {
  println("playing")
}

AVPlayer class reference

据我所知,我同意你的看法,在调用 play() 函数和实际播放视频之间存在轻微的延迟(换句话说,第一帧的时间视频已渲染)。延迟取决于一些标准,例如视频类型(VOD 或实时流媒体)、网络状况……但是,幸运的是,我们能够知道视频的第一帧何时呈现,我的意思是视频实际播放的确切时间.

通过观察当前AVPlayerItemstatus,只要是AVPlayerItemStatusReadyToPlay,就应该是第一帧已经渲染了。

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

顺便说一句,还有另一种解决方案,我们观察 AVPlayerLayerreadyForDisplay 状态,它还指示视频何时呈现。但是,此解决方案有一个缺点,如 Apple 文档

中所述
/*!
     @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;

这里是示例代码

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

从理论上讲,[self.playerLayer isReadyForDisplay] 应该 return YES,但是,作为文档,不能保证。

希望对您有所帮助。

Swift 4

方法一

var rateObserver: NSKeyValueObservation?

self.rateObserver = myPlayer.observe(\.rate, options:  [.new, .old], changeHandler: { (player, change) in
     if player.rate == 1  {
          print("Playing")
      }else{
           print("Stop")
      }
 })

 // Later You Can Remove Observer      
 self.rateObserver?.invalidate()

方法二

myPlayer.addObserver(self, forKeyPath: "rate", options: NSKeyValueObservingOptions(rawValue: NSKeyValueObservingOptions.new.rawValue | NSKeyValueObservingOptions.old.rawValue), context: nil)

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
      if keyPath == "rate" { 
          if player.rate == 1  {
              print("Playing")
          }else{
               print("Stop")
          }
      }
 }

您可能想要检测两件事:

  • AVPlayerLayerisReadyForDisplay是接收到第一帧的时间。
  • AVPlayerItemreadyToPlay 是视频真正开始播放的时间。

为了检查这两个状态,您可以使用可观察性。

您将在 class 中定义三个对象:

var player: AVPlayer?
var playerItem: AVPlayerItem?
var playerLayer: AVPlayerLayer?

播放器实现如下所示:

guard let videoURL = URL(string: "<videoPath>") else {
   return
}
asset = AVAsset(url: videoURL)
guard let asset = asset else {
   return
}
playerItem = AVPlayerItem(asset: asset, automaticallyLoadedAssetKeys: requiredAssetKeys)
guard let playerItem = playerItem else {
   return
}
playerItem.addObserver(self,
                       forKeyPath: #keyPath(AVPlayerItem.status),
                       options: [.old, .new],
                       context: &playerItemContext)
player = AVPlayer(playerItem: playerItem)
playerLayer = AVPlayerLayer(player: player)
playerLayer?.addObserver(self, 
                         forKeyPath: #keyPath(AVPlayerLayer.isReadyForDisplay), 
                         options: [.old, .new], 
                         context: &playerLayerContext)
avpController.player = player 
avpController.view.frame.size.height = playerView.frame.size.height
avpController.view.frame.size.width = playerView.frame.size.width
playerView.addSubview(avpController.view)
avpController.player?.play()

此处,上下文是简单整数或枚举。

您可以通过覆盖 observeValue 方法来处理事件。

override func observeValue(forKeyPath keyPath: String?,
                           of object: Any?,
                           change: [NSKeyValueChangeKey : Any]?,
                           context: UnsafeMutableRawPointer?) {
    guard (context == &playerItemContext) ||
        (context == &playerLayerContext) else {
        super.observeValue(forKeyPath: keyPath,
                           of: object,
                           change: change,
                           context: context)
        return
    }
    if keyPath == #keyPath(AVPlayerItem.status) {
        let status: AVPlayerItem.Status
        if let statusNumber = change?[.newKey] as? NSNumber {
            status = AVPlayerItem.Status(rawValue: statusNumber.intValue)!
        } else {
            status = .unknown
        }
        switch status {
        case .readyToPlay:
            print("Ready to play")
        case .failed:
            navigationController?.popViewController(animated: true)
        case .unknown:
            print("Unknown")
        @unknown default:
            print("Unknown default")
        }
    } else if keyPath == #keyPath(AVPlayerLayer.isReadyForDisplay) {
        if playerLayer?.isReadyForDisplay ?? false {
            print("Ready to display")
        }
    }
}

别忘了移除观察者。

deinit {
    playerItem?.removeObserver(self, forKeyPath: #keyPath(AVPlayerItem.status))
    playerLayer?.removeObserver(self, forKeyPath: #keyPath(AVPlayerLayer.isReadyForDisplay))
}