web-dev-qa-db-ja.com

AVPlayerオブジェクトの再生準備ができたことを知る

以前のUIView(_MP3_変数に格納されている)からUIViewに渡される_NSURL *fileURL_ファイルを再生しようとしています。

私はAVPlayerを初期化しています:

_player = [AVPlayer playerWithURL:fileURL];

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

NSLogは_Player created:0,_を出力しますが、これはまだプレイする準備ができていないことを意味します。

Play 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)...で置き換えると、音楽は2回クリックしますプレイ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**
_

SECOND TIME、_player.status_は1を保持しているようです。これはAVPlayerReadyToPlayであると推測しています。

最初にplay UIButtonをクリックしたときに、正しく動作するようにするにはどうすればよいですか? (つまり、AVPlayerが作成されるだけでなく、再生する準備ができていることを確認するにはどうすればよいですか?)

69
mvishnu

リモートファイルを再生しています。 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
        }
    }
}
122
Jilouc

AVPlayerのステータスを把握するのに苦労しました。 statusプロパティは常に非常に役立つとは限らなかったため、オーディオセッションの中断を処理しようとしていたとき、これは無限のフラストレーションにつながりました。時々AVPlayerが、実際にはそうではなかったのに(AVPlayerStatusReadyToPlayで)プレイする準備ができていると言っていました。 JiloucのKVOメソッドを使用しましたが、すべての場合に機能しませんでした。

補足として、statusプロパティが役に立たなかったときに、loadedTimeRangesAVPlayercurrentItemプロパティを見て、AVPlayerがロードしたストリームの量を問い合わせました。 (これはAVPlayerItemです)。

少し混乱しますが、次のようになります。

NSValue *val = [[[audioPlayer currentItem] loadedTimeRanges] objectAtIndex:0];
CMTimeRange timeRange;
[val getValue:&timeRange];
CMTime duration = timeRange.duration;
float timeLoaded = (float) duration.value / (float) duration.timescale; 

if (0 == timeLoaded) {
    // AVPlayer not actually ready to play
} else {
    // AVPlayer is ready to play
}
29
Tim Camber

迅速なソリューション

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

15
Josh Bernfeld

多くのことを研究し、多くの方法を試した後、私は通常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、またはregister observerの前)

- (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
    }
}
10
jose920405
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です。

6
Harman

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
    }
}
4
Axel Guilmin

コールバックを取得できないという問題がありました。

ストリームの作成方法に依存することがわかりました。私の場合、playerItemを使用して初期化したため、代わりにオブザーバーをアイテムに追加する必要がありました。

例えば:

- (void) setup
{
    ...
    self.playerItem = [AVPlayerItem playerItemWithAsset:asset];
    self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
    ... 

     // add callback
     [self.player.currentItem addObserver:self forKeyPath:@"status" options:0 context:nil];
}

// the callback method
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
                    change:(NSDictionary *)change context:(void *)context
{
    NSLog(@"[VideoView] player status: %i", self.player.status);

    if (object == self.player.currentItem && [keyPath isEqualToString:@"status"])
    {
        if (self.player.currentItem.status == AVPlayerStatusReadyToPlay)
        {
           //do stuff
        }
    }
}

// cleanup or it will crash
-(void)dealloc
{
    [self.player.currentItem removeObserver:self forKeyPath:@"status"];
}
4
dac2009

スイフト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!!
        }
}
1

プレーヤーのcurrentItemのステータスを確認します。

if (player.currentItem.status == AVPlayerItemStatusReadyToPlay)
1
Kirby Todd