86

以前のファイル(変数に保存されているファイル)からMP3渡されたファイルを再生しようとしています。UIViewUIViewNSURL *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)...に置き換えると、音楽は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**

2番目の時間はplayer.status1を保持しているように見えますが、これは推測ですAVPlayerReadyToPlay

初めてプレイをクリックしたときにプレイを正しく機能させるにはどうすればよいUIButtonですか?AVPlayer(つまり、が作成されただけでなく、プレイする準備ができていることを確認するにはどうすればよいですか?)

4

10 に答える 10

130

リモートファイルを再生しています。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
        }
    }
}
于 2011-03-23T09:33:19.100 に答える
31

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

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

ちょっとややこしいですが、こんな感じです。

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
}
于 2012-03-17T03:19:26.857 に答える
11

多くの調査を行い、多くの方法を試した結果、通常、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
    }
}
于 2016-03-10T20:13:50.117 に答える
7

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
    }
}
于 2017-04-27T10:43:48.257 に答える
5

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

ストリームの作成方法に依存することがわかりました。私の場合、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"];
}
于 2015-08-26T09:10:22.863 に答える
3

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

if (player.currentItem.status == AVPlayerItemStatusReadyToPlay)
于 2011-03-23T06:27:46.367 に答える