Wiedząc, kiedy obiekt AVPlayer jest gotowy do odtwarzania

Próbuję odtworzyć plik MP3, który zostanie przekazany do UIView z poprzedniej UIView (przechowywanej w zmiennej NSURL *fileURL).

Inicjalizuję AVPlayer z:

player = [AVPlayer playerWithURL:fileURL];

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

Wydruki NSLog Player created:0,, które, jak myślałem, oznaczają, że nie jest jeszcze gotowy do gry.

Kiedy klikam play UIButton, uruchamiam kod:

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

}
Kiedy to uruchamiam, zawsze dostaję Error in player?? w konsoli. Jeśli jednak zastąpię if warunek sprawdzający, czy AVPlayer jest gotowy do gry, prostym if(!isPlaying)..., potem muzyka gra drugi raz klikam na play UIButton.

Dziennik konsoli to:

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**

Widzę, że za drugim razem player.status wydaje się trzymać 1, Co zgaduję jest AVPlayerReadyToPlay.

Co mogę zrobić, aby gra działała poprawnie po pierwszym kliknięciu play UIButton? (czyli jak mogę się upewnić, że AVPlayer jest nie tylko stworzony, ale także gotowy do gry?)

Author: mvishnu, 2011-03-23

9 answers

Odtwarzasz zdalny plik. Może upłynąć trochę czasu, zanim AVPlayer zbuforuje wystarczającą ilość danych i będzie gotowy do odtworzenia pliku (Zobacz Av Foundation Programming Guide)

Ale nie wydaje się czekać, aż gracz będzie gotowy przed naciśnięciem przycisku Odtwórz. Chciałbym wyłączyć ten przycisk i włączyć go tylko wtedy, gdy odtwarzacz jest gotowy.

Za pomocą KVO można być powiadamianym o zmianach statusu gracza:

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

Ta metoda zostanie wywołana, gdy zmiany statusu:

- (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
        }
    }
}
 113
Author: Jilouc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-19 14:00:11

Miałem wiele problemów próbując dowiedzieć się status AVPlayer. Właściwość status nie zawsze wydawała się bardzo pomocna, a to doprowadziło do niekończącej się frustracji, gdy próbowałem poradzić sobie z przerwami w sesji audio. Czasami AVPlayer powiedział mi, że jest gotowy do gry (z AVPlayerStatusReadyToPlay), Kiedy tak naprawdę nie wydaje się być. Użyłem metody KVO, ale nie we wszystkich przypadkach.

Aby uzupełnić, gdy właściwość status nie była użyteczna, zapytałem o ilość strumienia że AVPlayer załadował patrząc na właściwość loadedTimeRanges AVPlayer's currentItem (która jest AVPlayerItem).

To wszystko jest trochę mylące, ale tak to wygląda:

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
}
 27
Author: Tim Camber,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-17 03:19:26

Po zbadaniu wielu i wypróbowaniu wielu sposobów zauważyłem, że normalnie status obserwator nie jest tym lepiej wiedzieć kiedy AVPlayer obiekt jest gotowy do gry, ponieważ obiekt może być gotowy do gry, ale nie oznacza to, że będzie natychmiast odtwarzany.

Lepiej wiedzieć, że to jest z loadedTimeRanges.

Dla obserwatora rejestru

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

Listen the observer

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

For remove observer (dealloc, viewWillDissapear lub przed rejestr observer) its a good places for called

- (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
Author: jose920405,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-10 20:13:50

Swift Solution

var observer: NSKeyValueObservation?

func prepareToPlay() {
    let url = <#Asset URL#>
    // Create asset to be played
    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
    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)
}

Możesz również unieważnić obserwatora w ten sposób

self.observer.invalidate()

Ważne: należy zachować zmienną observer, w przeciwnym razie zostanie ona wyłączona i zmienna changeHandler nie będzie już wywoływana. Więc nie Definiuj obserwatora jako zmiennej funkcji, ale zdefiniuj ją jako zmienną instancji, jak w podanym przykładzie.

Ta składnia obserwatora wartości klucza jest nowa w Swift 4.

Aby uzyskać więcej informacji, zobacz tutaj https://github.com/ole/whats-new-in-swift-4/blob/master/Whats-new-in-Swift-4.playground/Pages/Key%20paths.xcplaygroundpage/Contents.swift

 8
Author: Josh Bernfeld,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-27 03:26:51
private var playbackLikelyToKeepUpContext = 0

Dla obserwatora rejestru

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

Listen the observer

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

Do usunięcia obserwatora

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

Kluczowym punktem w kodzie jest właściwość instancji isplaybacklikelytokeepup.

 5
Author: Harman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-16 13:18:42

Miałem problemy z nie otrzymywaniem żadnych połączeń zwrotnych.

Okazuje się, że to zależy od tego, jak stworzysz strumień. W moim przypadku użyłem playerItem do inicjalizacji, a więc musiałem dodać obserwatora do elementu zamiast.

Na przykład:

- (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
Author: dac2009,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-08 13:29:39

W oparciu o odpowiedź Tima Cambera , Oto funkcja Swift, której używam:

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
}

Lub jako rozszerzenie

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
Author: Axel Guilmin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:03:02

Sprawdź status aktualnego gracza:

if (player.currentItem.status == AVPlayerItemStatusReadyToPlay)
 1
Author: Kirby Todd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-23 06:27:46

Myślę, że lepiej będzie, jeśli gracz użyje initWithUrl. Nie sądzę, że to rozwiąże twój problem, ale jest lepiej.

 -8
Author: visakh7,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-23 06:23:40