AVPlayer перестает играть и не возобновляется снова

в моем приложении я должен воспроизводить аудиофайлы, хранящиеся на веб-сервере. Я использую AVPlayer для него. У меня есть все элементы управления play/pause и все делегаты и наблюдатели, которые работают отлично. При воспроизведении небольших аудиофайлов все работает отлично.

когда долго проигрывания аудио файлов также начинает играть нормально, но через несколько секунд AVPlayer приостановка игры (скорее всего в буфер). Вопрос он снова не возобновится самостоятельно. Он держит паузу состояние, и если я вручную снова нажму кнопку воспроизведения, он снова будет воспроизводиться плавно.

Я хочу знать, почему AVPlayer не возобновляется автоматически и как мне удается возобновить звук снова без нажатия пользователем кнопки воспроизведения снова? Спасибо.

7 ответов


Да, он останавливается, потому что буфер пуст, поэтому он должен ждать, чтобы загрузить больше видео. После этого вы должны вручную попросить начать снова. Чтобы решить проблему, я выполнил следующие шаги:

1) обнаружение: чтобы определить, когда игрок остановился, я использую KVO со свойством rate значения:

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

это условие: CMTimeGetSeconds(self.playerItem.duration) != CMTimeGetSeconds(self.playerItem.currentTime) заключается в обнаружении разницы между прибытием в конце видео или остановкой в середине

2) подождите, пока видео load-если вы продолжаете играть напрямую, у вас не будет достаточного буфера для продолжения игры без перерыва. Чтобы знать, когда начать, вы должны соблюдать значение playbackLikelytoKeepUp из playerItem (здесь я использую библиотеку, чтобы наблюдать с блоками, но я думаю, что это делает точку):

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

и это все! проблема решена

Edit: кстати, используемая библиотека-libextobjc


Я работаю с видеофайлами, поэтому в моем коде больше, чем вам нужно, но следующее решение должно приостановить плеер, когда он зависает, а затем проверять каждые 0,5 секунды, чтобы увидеть, достаточно ли мы буферизованы, чтобы идти в ногу. Если это так, он перезапускает плеер. Если игрок висит более 10 секунд без перезагрузки, мы останавливаем игрока и приносим извинения пользователю. Это означает, что вам нужны правильные наблюдатели на месте. Код ниже работает довольно хорошо для мне.

свойства, определенные / init В бы .H файл или другое место:

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

частичное .м:

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

надеюсь, что это помогает!


у меня была аналогичная проблема. У меня были некоторые локальные файлы, которые я хотел воспроизвести, настроил AVPlayer и вызвал [player play], игрок останавливается на кадре 0 и больше не будет играть, пока я не вызову play снова вручную. Принятый ответ был невозможен для меня из-за неправильного объяснения, тогда я просто попытался отложить игру и волшебно работал

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

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

для веб-видео у меня также была проблема, я решаю ее с помощью ответа Уоллеса.

при создании "плеере АВ" добавить наблюдателя:

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

}

не забудьте удалить наблюдателя, прежде чем отклонить представление

[self.videoItem removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"]

Я думаю, что использовать AVPlayerItemPlaybackStalledNotification обнаружить заглохший лучший путь.


принятый ответ дает возможное решение проблемы, но ему не хватает гибкости, также его трудно прочитать. Вот более гибкое решение.

добавить наблюдателей:

//_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];
        }
    }
}

магия:

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

"[self availableDuration] " является необязательным, но вы можете вручную запустить воспроизведение на основе количества доступных видео. Вы можете изменить, как часто код проверяет, достаточно ли видео буферизовано. Если вы решите использовать необязательная часть, вот реализация метода:

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

не забудьте очистить. Удалить наблюдателей:

[_player.currentItem removeObserver:self forKeyPath:@"status"];
[_player removeObserver:self forKeyPath:@"rate"];

и возможные отложенные вызовы для обработки остановленного видео:

[UIView cancelPreviousPerformRequestsWithTarget:self selector:@selector(handleStalled) object:nil];

сначала я наблюдаю за остановкой воспроизведения

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

затем я заставляю продолжение воспроизведения

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

Это, наверное, не лучший способ сделать это, но я использую его, пока не найду что-то лучше :)


в очень плохой сети playbackLikelyToKeepUp скорее всего, это ложь.

использовать kvo наблюдать playbackBufferEmpty лучше, более чувствительны к существующим буферным данным, которые могут использоваться для воспроизведения .при изменении значения на true можно вызвать метод play для продолжения воспроизведения.