AVPlayer przestaje grać i nie wznawia ponownie

W mojej aplikacji muszę odtwarzać pliki audio przechowywane na serwerze WWW. Używam do tego AVPlayer. Mam wszystkie kontrolki play/pause i wszystkich delegatów i obserwatorów, które działają idealnie. Przy odtwarzaniu małych plików audio Wszystko działa świetnie.

Gdy odtwarzany jest długi plik audio, również zaczyna grać dobrze, ale po kilku sekundach AVPlayer wstrzymuje odtwarzanie (najprawdopodobniej w celu jego buforowania). Problem w tym, że nie wraca ponownie Sam. Zatrzymuje się w Pauzie stan i jeśli ręcznie nacisnąć przycisk odtwarzania ponownie gra płynnie ponownie.

Chcę wiedzieć, dlaczego AVPlayer nie wznawia się automatycznie i jak mogę przywrócić dźwięk bez ponownego naciśnięcia przycisku odtwarzania? Dzięki.

Author: mhatch, 2013-10-10

9 answers

Tak, zatrzymuje się, ponieważ bufor jest pusty, więc musi poczekać, aby załadować więcej wideo. Następnie musisz ręcznie poprosić o ponowne uruchomienie. Aby rozwiązać problem, wykonałem następujące kroki:

1) Detekcja: aby wykryć kiedy gracz się zatrzymał używam KVO z właściwością rate o wartości:

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"rate"] )
    {

        if (self.player.rate == 0 && CMTimeGetSeconds(self.playerItem.duration) != CMTimeGetSeconds(self.playerItem.currentTime) && self.videoPlaying)
        {
            [self continuePlaying];
        }
      }
    }

Ten warunek: {[2] } polega na wykryciu różnicy między przybyciem na koniec filmu, a zatrzymaniem się w środku

2) Poczekaj, aż film się załaduje - jeśli kontynuujesz grając bezpośrednio nie będziesz miał wystarczająco dużo bufora, aby kontynuować grę bez przerwy. Aby wiedzieć, kiedy zacząć, należy obserwować wartość playbackLikelytoKeepUp z playerItem (tutaj używam biblioteki do obserwacji z blokami, ale myślę, że o to chodzi):

-(void)continuePlaying
 {

if (!self.playerItem.playbackLikelyToKeepUp)
{
    self.loadingView.hidden = NO;
    __weak typeof(self) wSelf = self;
    self.playbackLikelyToKeepUpKVOToken = [self.playerItem addObserverForKeyPath:@keypath(_playerItem.playbackLikelyToKeepUp) block:^(id obj, NSDictionary *change) {
        __strong typeof(self) sSelf = wSelf;
        if(sSelf)
        {
            if (sSelf.playerItem.playbackLikelyToKeepUp)
            {
                [sSelf.playerItem removeObserverForKeyPath:@keypath(_playerItem.playbackLikelyToKeepUp) token:self.playbackLikelyToKeepUpKVOToken];
                sSelf.playbackLikelyToKeepUpKVOToken = nil;
                [sSelf continuePlaying];
            }
                    }
    }];
}
I to wszystko! problem rozwiązany

Edit: przy okazji biblioteka używana jest libextobjc

 17
Author: Jpellat,
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-08-03 16:11:11

Pracuję z plikami wideo, więc mój kod jest więcej niż potrzebujesz, ale poniższe rozwiązanie powinno wstrzymać odtwarzacz, gdy się zawiesi, a następnie sprawdzać co 0,5 sekundy, aby sprawdzić, czy mamy wystarczająco buforowane, aby nadążyć. Jeśli tak, uruchamia ponownie odtwarzacz. Jeśli gracz zawiesza się na dłużej niż 10 sekund bez ponownego uruchomienia, zatrzymujemy gracza i przepraszamy użytkownika. Oznacza to, że potrzebujesz odpowiednich obserwatorów na miejscu. Poniższy kod działa dla mnie całkiem dobrze.

zdefiniowane właściwości / init ' D in A.plik h lub gdzie indziej:

AVPlayer *player;  
int playerTryCount = -1; // this should get set to 0 when the AVPlayer starts playing
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];

częściowe .m:

- (AVPlayer *)initializePlayerFromURL:(NSURL *)movieURL {
  // create AVPlayer
  AVPlayerItem *videoItem = [AVPlayerItem playerItemWithURL:movieURL];
  AVPlayer *videoPlayer = [AVPlayer playerWithPlayerItem:videoItem];

  // add Observers
  [videoItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:nil];
  [self startNotificationObservers]; // see method below
  // I observe a bunch of other stuff, but this is all you need for this to work

  return videoPlayer;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  // check that all conditions for a stuck player have been met
  if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) {
      if (self.player.currentItem.playbackLikelyToKeepUp == NO &&
          CMTIME_COMPARE_INLINE(self.player.currentTime, >, kCMTimeZero) && 
          CMTIME_COMPARE_INLINE(self.player.currentTime, !=, self.player.currentItem.duration)) {

              // if so, post the playerHanging notification
              [self.notificationCenter postNotificationName:PlayerHangingNotification object:self.videoPlayer];
      }
  }
}

- (void)startNotificationObservers {
    [self.notificationCenter addObserver:self 
                                selector:@selector(playerContinue)
                                   name:PlayerContinueNotification
                                 object:nil];    

    [self.notificationCenter addObserver:self 
                                selector:@selector(playerHanging)
                                   name:PlayerHangingNotification
                                 object:nil];    
}

// playerHanging simply decides whether to wait 0.5 seconds or not
// if so, it pauses the player and sends a playerContinue notification
// if not, it puts us out of our misery
- (void)playerHanging {
    if (playerTryCount <= 10) {

      playerTryCount += 1;
      [self.player pause];
      // start an activity indicator / busy view
      [self.notificationCenter postNotificationName:PlayerContinueNotification object:self.player];

    } else { // this code shouldn't actually execute, but I include it as dummyproofing

      [self stopPlaying]; // a method where I clean up the AVPlayer,
                          // which is already paused

      // Here's where I'd put up an alertController or alertView
      // to say we're sorry but we just can't go on like this anymore
    }
}

// playerContinue does the actual waiting and restarting
- (void)playerContinue {
    if (CMTIME_COMPARE_INLINE(self.player.currentTime, ==, self.player.currentItem.duration)) { // we've reached the end

      [self stopPlaying];

    } else if (playerTryCount  > 10) // stop trying

      [self stopPlaying];
      // put up "sorry" alert

    } else if (playerTryCount == 0) {

      return; // protects against a race condition

    } else if (self.player.currentItem.playbackLikelyToKeepUp == YES) {

      // Here I stop/remove the activity indicator I put up in playerHanging
      playerTryCount = 0;
      [self.player play]; // continue from where we left off

    } else { // still hanging, not at end

        // create a 0.5-second delay to see if buffering catches up
        // then post another playerContinue notification to call this method again
        // in a manner that attempts to avoid any recursion or threading nightmares 
        playerTryCount += 1;
        double delayInSeconds = 0.5;
        dispatch_time_t executeTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
        dispatch_after(executeTime, dispatch_get_main_queue(), ^{

          // test playerTryCount again to protect against changes that might have happened during the 0.5 second delay
          if (playerTryCount > 0) {
              if (playerTryCount <= 10) {
                [self.notificationCenter postNotificationName:PlayerContinueNotification object:self.videoPlayer];
              } else {
                [self stopPlaying];
                // put up "sorry" alert
              }
          }
        });
}
Mam nadzieję, że to pomoże!
 9
Author: wallace,
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-02-09 06:59:58

Przyjęta odpowiedź daje możliwe rozwiązanie problemu, ale brakuje jej elastyczności, a także trudno ją odczytać. Oto bardziej elastyczne rozwiązanie.

Dodaj obserwatorów:

//_player is instance of AVPlayer
[_player.currentItem addObserver:self forKeyPath:@"status" options:0 context:nil];
[_player addObserver:self forKeyPath:@"rate" options:0 context:nil];

:

-(void)observeValueForKeyPath:(NSString*)keyPath
                     ofObject:(id)object
                       change:(NSDictionary*)change
                      context:(void*)context {

    if ([keyPath isEqualToString:@"status"]) {
        if (_player.status == AVPlayerStatusFailed) {
            //Possibly show error message or attempt replay from tart
            //Description from the docs:
            //  Indicates that the player can no longer play AVPlayerItem instances because of an error. The error is described by
            //  the value of the player's error property.
        }
    }else if ([keyPath isEqualToString:@"rate"]) {
        if (_player.rate == 0 && //if player rate dropped to 0
                CMTIME_COMPARE_INLINE(_player.currentItem.currentTime, >, kCMTimeZero) && //if video was started
                CMTIME_COMPARE_INLINE(_player.currentItem.currentTime, <, _player.currentItem.duration) && //but not yet finished
                _isPlaying) { //instance variable to handle overall state (changed to YES when user triggers playback)
            [self handleStalled];
        }
    }
}

Magia:

-(void)handleStalled {
    NSLog(@"Handle stalled. Available: %lf", [self availableDuration]);

    if (_player.currentItem.playbackLikelyToKeepUp || //
            [self availableDuration] - CMTimeGetSeconds(_player.currentItem.currentTime) > 10.0) {
        [_player play];
    } else {
        [self performSelector:@selector(handleStalled) withObject:nil afterDelay:0.5]; //try again
    }
}

Opcja "[self available] " jest opcjonalna, ale można ręcznie uruchomić odtwarzanie w zależności od ilości dostępnego wideo. Możesz zmienić, jak często kod sprawdza, czy wystarczająca ilość wideo jest buforowana. Jeśli zdecydujesz się użyć części opcjonalnej, oto metoda realizacja:

- (NSTimeInterval) availableDuration
{
    NSArray *loadedTimeRanges = [[_player currentItem] loadedTimeRanges];
    CMTimeRange timeRange = [[loadedTimeRanges objectAtIndex:0] CMTimeRangeValue];
    Float64 startSeconds = CMTimeGetSeconds(timeRange.start);
    Float64 durationSeconds = CMTimeGetSeconds(timeRange.duration);
    NSTimeInterval result = startSeconds + durationSeconds;
    return result;
}
Nie zapomnij o sprzątaniu. Usuń obserwatorów:
[_player.currentItem removeObserver:self forKeyPath:@"status"];
[_player removeObserver:self forKeyPath:@"rate"];

I możliwe połączenia oczekujące na obsługę wstrzymanego wideo:

[UIView cancelPreviousPerformRequestsWithTarget:self selector:@selector(handleStalled) object:nil];
 7
Author: Raimundas Sakalauskas,
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-08-02 17:18:56

Miałem podobny problem. Miałem kilka plików lokalnych, które chciałem odtworzyć, skonfigurowałem Avplayera i wywołałem [player play], odtwarzacz zatrzymuje się na ramce 0 i nie chce grać, dopóki nie wywołałem ponownie play ręcznie. Zaakceptowana odpowiedź była dla mnie niemożliwa do wdrożenia z powodu błędnego wyjaśnienia, potem po prostu próbowałem opóźnić grę i magicznie zadziałał

[self performSelector:@selector(startVideo) withObject:nil afterDelay:0.2];

-(void)startVideo{
    [self.videoPlayer play];
}

W przypadku filmów internetowych miałem również problem, rozwiązuję go za pomocą odpowiedzi wallace ' a.

Podczas tworzenia Avplayera dodaj obserwator:

[self.videoItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:nil];

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
// check that all conditions for a stuck player have been met
if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) {
    if (self.videoPlayer.currentItem.playbackLikelyToKeepUp == NO &&
        CMTIME_COMPARE_INLINE(self.videoPlayer.currentTime, >, kCMTimeZero) &&
        CMTIME_COMPARE_INLINE(self.videoPlayer.currentTime, !=, self.videoPlayer.currentItem.duration)) {
        NSLog(@"hanged");
        [self performSelector:@selector(startVideo) withObject:nil afterDelay:0.2];
    }
}

}

Pamiętaj, aby usunąć obserwatora przed odrzuceniem widoku

[self.videoItem removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"]
 6
Author: gomezluisj,
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-07-02 16:09:26

Myślę, że użycie AVPlayerItemPlaybackStalledNotification do wykrycia zablokowanego jest lepszym sposobem.

 5
Author: xzysun,
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
2014-12-29 03:14:22

Najpierw obserwuję odtwarzanie na zwłokę

NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(playerStalled),
    name: AVPlayerItemPlaybackStalledNotification, object: videoPlayer.currentItem)

Potem wymuszam kontynuację odtwarzania

func playerStalled(note: NSNotification) {
  let playerItem = note.object as! AVPlayerItem
  if let player = playerItem.valueForKey("player") as? AVPlayer{
    player.play()
  }
}

To chyba nie jest najlepszy sposób, ale używam go, dopóki nie znajdę czegoś lepszego:)

 4
Author: budiDino,
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-07-25 17:39:13

Wpadłem również na ten problem jak opisane tutaj

Testowałem tę odpowiedź poniżej wiele razy i do tej pory działało za każdym razem.

Oto, co wymyśliłem dla wersji Swift 5 @wallace ' s answer.

1 - zamiast obserwować klawiaturę "playbackLikelyToKeepUp" używam .AVPlayerItemPlaybackStalled Notification i w środku sprawdzam czy bufor jest pełny czy nie przez if !playerItem.isPlaybackLikelyToKeepUp {...}

2-zamiast używać jego PlayerHangingNotification używam funkcji o nazwie playerIsHanging()

3 - zamiast używając jego PlayerContinueNotification używam funkcji o nazwie checkPlayerTryCount()

4 - a w środku checkPlayerTryCount() robię wszystko tak samo jak jego (void)playerContinue funkcja, chyba że wpadłem na } else if playerTryCount == 0 { nic by się nie stało. Aby tego uniknąć dodałem 2 linijki kodu nad return wypowiedzią

5 - Jak @PranoyC zasugerował pod komentarzami @ wallace ustawiłem playerTryCount na max 20 zamiast 10. Ustawiam go również jako właściwość klasy let playerTryCountMaxLimit = 20

Musisz dodać / usunąć wskaźnik aktywności / spinner gdzie komentarz proponuje to zrobić

Kod:

NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemPlaybackStalled(_:)),
                                       name: NSNotification.Name.AVPlayerItemPlaybackStalled,
                                       object: playerItem)

@objc func playerItemPlaybackStalled(_ notification: Notification) {
// The system may post this notification on a thread other than the one used to registered the observer: https://developer.apple.com/documentation/foundation/nsnotification/name/1387661-avplayeritemplaybackstalled

    guard let playerItem = notification.object as? AVPlayerItem else { return }
    
    // playerItem.isPlaybackLikelyToKeepUp == false && if the player's current time is greater than zero && the player's current time is not equal to the player's duration
    if (!playerItem.isPlaybackLikelyToKeepUp) && (CMTimeCompare(playerItem.currentTime(), .zero) == 1) && (CMTimeCompare(playerItem.currentTime(), playerItem.duration) != 0) {
        
        DispatchQueue.main.async { [weak self] in
            self?.playerIsHanging()
        }
    }
}

var playerTryCount = -1 // this should get set to 0 when the AVPlayer starts playing
let playerTryCountMaxLimit = 20

func playerIsHanging() {
    
    if playerTryCount <= playerTryCountMaxLimit {
        
        playerTryCount += 1

        // show spinner

        checkPlayerTryCount()

    } else {
        // show spinner, show alert, or possibly use player?.replaceCurrentItem(with: playerItem) to start over ***BE SURE TO RESET playerTryCount = 0 ***
        print("1.-----> PROBLEM")
    }
}

func checkPlayerTryCount() {
    
    guard let player = player, let playerItem = player.currentItem else { return }
    
    // if the player's current time is equal to the player's duration
    if CMTimeCompare(playerItem.currentTime(), playerItem.duration) == 0 {
        
        // show spinner or better yet remove spinner and show a replayButton or auto rewind to the beginning ***BE SURE TO RESET playerTryCount = 0 ***
        
    } else if playerTryCount > playerTryCountMaxLimit {
        
        // show spinner, show alert, or possibly use player?.replaceCurrentItem(with: playerItem) to start over ***BE SURE TO RESET playerTryCount = 0 ***
        print("2.-----> PROBLEM")

    } else if playerTryCount == 0 {

        // *** in his answer he has nothing but a return statement here but when it would hit this condition nothing would happen. I had to add these 2 lines of code for it to continue ***
        playerTryCount += 1
        retryCheckPlayerTryCountAgain()
        return // protects against a race condition
        
    } else if playerItem.isPlaybackLikelyToKeepUp {
        
        // remove spinner and reset playerTryCount to zero
        playerTryCount = 0
        player?.play()

    } else { // still hanging, not at end
        
        playerTryCount += 1
        
        /*
          create a 0.5-second delay using .asyncAfter to see if buffering catches up
          then call retryCheckPlayerTryCountAgain() in a manner that attempts to avoid any recursion or threading nightmares
        */

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            DispatchQueue.main.async { [weak self] in

                // test playerTryCount again to protect against changes that might have happened during the 0.5 second delay
                if self!.playerTryCount > 0 {
                    
                    if self!.playerTryCount <= self!.playerTryCountMaxLimit {
                        
                      self!.retryCheckPlayerTryCountAgain()
                        
                    } else {
                        
                      // show spinner, show alert, or possibly use player?.replaceCurrentItem(with: playerItem) to start over ***BE SURE TO RESET playerTryCount = 0 ***
                      print("3.-----> PROBLEM")
                    }
                }
            }
        }
    }
}

func retryCheckPlayerTryCountAgain() {
    checkPlayerTryCount()
}
 2
Author: Lance Samaria,
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
2020-06-24 00:00:58

W bardzo złej sieci playbackLikelyToKeepUp najprawdopodobniej jest to false.

Użycie {[1] } do obserwacji {[2] } jest lepsze, bardziej wrażliwe na istniejące dane bufora, które można wykorzystać do odtwarzania .jeśli wartość zmieni się na true, możesz wywołać metodę odtwarzania, aby kontynuować odtwarzanie.

 1
Author: DumplingKid,
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-12-14 06:15:30

W moim przypadku,

  • próbowałem nagrać wideo za pomocą imagePickerController i odtworzyć nagrane wideo za pomocą AVPlayerController. Ale zaczyna odtwarzać wideo i zatrzymuje się po 1 sekundzie.W jakiś sposób dostaje czas na zapisanie wideo, a jeśli odtworzysz go natychmiast, nie będzie odtwarzany.
    Więc rozwiązaniem jest,
  • Wywołaj odtwarzanie wideo po 0,5 sekundy (opóźnienie). jak poniżej

       -(void)imagePickerController:(UIImagePickerController *)picker 
                didFinishPickingMediaWithInfo:(NSDictionary *)info {
               [self performSelector:@selector(playVideo) withObject:self 
               afterDelay:0.5];
         }
    

    -(void) playVideo {

      self.avPlayerViewController = [[AVPlayerViewController alloc] init];
         if(self.avPlayerViewController != nil)
       {
        AVPlayerItem* playerItem = [AVPlayerItem playerItemWithURL:Vpath];
        AVPlayer* player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
        self.avPlayerViewController.player = player;
        self.avPlayerViewController.showsPlaybackControls = NO;
        [self.avPlayerViewController setVideoGravity:AVLayerVideoGravityResizeAspectFill];
        [self.avPlayerViewController.view setFrame:[[UIScreen mainScreen] bounds]];
        self.avPlayerViewController.view.clipsToBounds = YES;
        self.avPlayerViewController.delegate = self;
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerDidFinishPlaying) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];
        [self.viewVideoHolder addSubview:self.avPlayerViewController.view];
        [self.avPlayerViewController.player play];
    
    }
    

    }

     -(void) playerDidFinishPlaying
       {
        [avPlayer pause];
        }
    
 0
Author: Shrikant Phadke,
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
2020-02-19 13:20:44