知道AVPlayer对象何时可以播放

时间:2011-03-23 06:04:58

标签: iphone avfoundation audio-streaming avplayer

我正在尝试播放MP3文件,该文件从之前的UIView传递给UIView(存储在NSURL *fileURL变量中)。

我正在使用:

初始化AVPlayer
player = [AVPlayer playerWithURL:fileURL];

NSLog(@"Player created:%d",player.status);

NSLog打印Player created:0,,我认为这意味着它尚未准备好播放。

当我点击播放UIButton时,我运行的代码是:

-(IBAction)playButtonClicked
{
    NSLog(@"Clicked Play. MP3:%@",[fileURL absoluteString]);

    if(([player status] == AVPlayerStatusReadyToPlay) && !isPlaying)
//  if(!isPlaying)
    {
        [player play];
        NSLog(@"Playing:%@ with %d",[fileURL absoluteString], player.status);
        isPlaying = YES;
    }
    else if(isPlaying)
    {

        [player pause];
        NSLog(@"Pausing:%@",[fileURL absoluteString]);
        isPlaying = NO;
    }
    else {
        NSLog(@"Error in player??");
    }

}

当我运行它时,我总是在控制台中获得Error in player??。 但是,如果我使用简单的if替换AVPlayer条件来检查if(!isPlaying)是否已准备就绪,那么音乐会播放第二次我点击播放{{} 1}}。

控制台日志是:

UIButton

我看到第二次,Clicked Play. MP3:http://www.nimh.nih.gov/audio/neurogenesis.mp3 Playing:http://www.nimh.nih.gov/audio/neurogenesis.mp3 **with 0** Clicked Play. MP3:http://www.nimh.nih.gov/audio/neurogenesis.mp3 Pausing:http://www.nimh.nih.gov/audio/neurogenesis.mp3 Clicked Play. MP3:http://www.nimh.nih.gov/audio/neurogenesis.mp3 2011-03-23 11:06:43.674 Podcasts[2050:207] Playing:http://www.nimh.nih.gov/audio/neurogenesis.mp3 **with 1** 似乎持有1,我猜是player.status

第一次点击播放AVPlayerReadyToPlay,我该怎么做才能让播放正常工作? (即,我如何确保UIButton不仅仅是创建,还准备好发挥?)

9 个答案:

答案 0 :(得分:119)

您正在播放远程文件。 AVPlayer可能需要一些时间来缓冲足够的数据并准备好播放文件(请参阅AV Foundation Programming Guide

但在点击播放按钮之前,您似乎还没等待播放器准备就绪。我想要的是禁用此按钮并仅在播放器准备就绪时启用它。

使用KVO,可以通知玩家状态的变化:

playButton.enabled = NO;
player = [AVPlayer playerWithURL:fileURL];
[player addObserver:self forKeyPath:@"status" options:0 context:nil];   

状态更改时将调用此方法:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
                        change:(NSDictionary *)change context:(void *)context {
    if (object == player && [keyPath isEqualToString:@"status"]) {
        if (player.status == AVPlayerStatusReadyToPlay) {
            playButton.enabled = YES;
        } else if (player.status == AVPlayerStatusFailed) {
            // something went wrong. player.error should contain some information
        }
    }
}

答案 1 :(得分:28)

我在试图找出AVPlayer的状态时遇到了很多麻烦。 status属性似乎并不总是非常有用,当我试图处理音频会话中断时,这导致了无尽的挫败感。有时AVPlayer告诉我它已经准备好了(当AVPlayerStatusReadyToPlay)时它似乎没有。我使用了Jilouc的KVO方法,但它并不适用于所有情况。

为了补充,当状态属性不起作用时,我通过查看loadedTimeRanges的{​​{1}}属性来查询AVPlayer加载的流量{{1 (AVPlayer)。

这有点令人困惑,但这就是它的样子:

currentItem

答案 2 :(得分:13)

Swift Solution

var observer: NSKeyValueObservation?

func prepareToPlay() {
    let url = <#Asset URL#>
    // Create asset to be played
    let asset = AVAsset(url: url)

    let assetKeys = [
        "playable",
        "hasProtectedContent"
    ]
    // Create a new AVPlayerItem with the asset and an
    // array of asset keys to be automatically loaded
    let playerItem = AVPlayerItem(asset: asset,
                              automaticallyLoadedAssetKeys: assetKeys)

    // Register as an observer of the player item's status property
    self.observer = playerItem.observe(\.status, options:  [.new, .old], changeHandler: { (playerItem, change) in
        if playerItem.status == .readyToPlay {
            //Do your work here
        }
    })

    // Associate the player item with the player
    player = AVPlayer(playerItem: playerItem)
}

此外,您可以通过这种方式使观察者无效

self.observer.invalidate()

重要说明:您必须保留观察者变量,否则它将解除分配,并且将不再调用changeHandler。因此,不要将观察者定义为函数变量,而是将其定义为实例变量,如给定示例所示。

此键值观察器语法是Swift 4的新增功能。

有关详细信息,请参阅此处https://github.com/ole/whats-new-in-swift-4/blob/master/Whats-new-in-Swift-4.playground/Pages/Key%20paths.xcplaygroundpage/Contents.swift

答案 3 :(得分:10)

经过多方面的研究并尝试了很多方法,我注意到通常 status观察者在AVPlayer对象准备好发挥时真的知道更好,因为对象可以准备好玩,但这并不意味着它会立即播放。

了解这一点的好主意是使用loadedTimeRanges

  

对于注册观察员

[playerClip addObserver:self forKeyPath:@"currentItem.loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
  

倾听观察者

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (object == playerClip && [keyPath isEqualToString:@"currentItem.loadedTimeRanges"]) {
        NSArray *timeRanges = (NSArray*)[change objectForKey:NSKeyValueChangeNewKey];
        if (timeRanges && [timeRanges count]) {
            CMTimeRange timerange=[[timeRanges objectAtIndex:0]CMTimeRangeValue];
            float currentBufferDuration = CMTimeGetSeconds(CMTimeAdd(timerange.start, timerange.duration));
            CMTime duration = playerClip.currentItem.asset.duration;
            float seconds = CMTimeGetSeconds(duration);

            //I think that 2 seconds is enough to know if you're ready or not
            if (currentBufferDuration > 2 || currentBufferDuration == seconds) {
                // Ready to play. Your logic here
            }
        } else {
            [[[UIAlertView alloc] initWithTitle:@"Alert!" message:@"Error trying to play the clip. Please try again" delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil] show];
        }
    }
}
  

对于删除观察者(dealloc,viewWillDissapear或寄存器观察者之前),它是一个被称为

的好地方
- (void)removeObserverForTimesRanges
{
    @try {
        [playerClip removeObserver:self forKeyPath:@"currentItem.loadedTimeRanges"];
    } @catch(id anException){
        NSLog(@"excepcion remove observer == %@. Remove previously or never added observer.",anException);
        //do nothing, obviously it wasn't attached because an exception was thrown
    }
}

答案 4 :(得分:5)

private var playbackLikelyToKeepUpContext = 0

对于注册观察员

avPlayer.addObserver(self, forKeyPath: "currentItem.playbackLikelyToKeepUp",
        options: .new, context: &playbackLikelyToKeepUpContext)

倾听观察者

 override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if context == &playbackLikelyToKeepUpContext {
        if avPlayer.currentItem!.isPlaybackLikelyToKeepUp {
           // loadingIndicatorView.stopAnimating() or something else
        } else {
           // loadingIndicatorView.startAnimating() or something else
        }
    }
}

删除观察者

deinit {
    avPlayer.removeObserver(self, forKeyPath: "currentItem.playbackLikelyToKeepUp")
}

代码中的关键点是实例属性isPlaybackLikelyToKeepUp。

答案 5 :(得分:4)

基于Tim Camber answer,这是我使用的Swift函数:

private func isPlayerReady(_ player:AVPlayer?) -> Bool {

    guard let player = player else { return false }

    let ready = player.status == .readyToPlay

    let timeRange = player.currentItem?.loadedTimeRanges.first as? CMTimeRange
    guard let duration = timeRange?.duration else { return false } // Fail when loadedTimeRanges is empty
    let timeLoaded = Int(duration.value) / Int(duration.timescale) // value/timescale = seconds
    let loaded = timeLoaded > 0

    return ready && loaded
}

或者,作为扩展名

extension AVPlayer {
    var ready:Bool {
        let timeRange = currentItem?.loadedTimeRanges.first as? CMTimeRange
        guard let duration = timeRange?.duration else { return false }
        let timeLoaded = Int(duration.value) / Int(duration.timescale) // value/timescale = seconds
        let loaded = timeLoaded > 0

        return status == .readyToPlay && loaded
    }
}

答案 6 :(得分:1)

检查播放器currentItem的状态:

if (player.currentItem.status == AVPlayerItemStatusReadyToPlay)

答案 7 :(得分:1)

@JoshBernfeld's answer对我不起作用。不知道为什么。他观察到playerItem.observe(\.status。我不得不观察player?.observe(\.currentItem?.statusplayerItem status属性似乎是同一回事。

var playerStatusObserver: NSKeyValueObservation?

player?.automaticallyWaitsToMinimizeStalling = false // starts faster

playerStatusObserver = player?.observe(\.currentItem?.status, options: [.new, .old]) { (player, change) in
        
    switch (player.status) {
    case .readyToPlay:
            // here is where it's ready to play so play player
            DispatchQueue.main.async { [weak self] in
                self?.player?.play()
            }
    case .failed, .unknown:
            print("Media Failed to Play")
    @unknown default:
         break
    }
}

使用完播放器集playerStatusObserver = nil

后,

答案 8 :(得分:0)

雨燕5:

var player:AVPlayer!

override func viewDidLoad() {
        super.viewDidLoad()
        NotificationCenter.default.addObserver(self, 
               selector: #selector(playerItemDidReadyToPlay(notification:)),
               name: .AVPlayerItemNewAccessLogEntry, 
               object: player?.currentItem)
}

@objc func playerItemDidReadyToPlay(notification: Notification) {
        if let _ = notification.object as? AVPlayerItem {
            // player is ready to play now!!
        }
}