Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 1

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

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

-(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) Дождитесь загрузки видео. Если вы продолжаете играть напрямую, у вас не будет достаточно буфера для продолжения воспроизведения без перерыва. Чтобы знать, когда начинать, вы должны наблюдать значение 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];
            }
                    }
    }];
}

И это! проблема решена

Изменить: кстати, в библиотеке используется libextobjc

Ответ 2

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

определенные /init 'd в файле .h или в другом месте:

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

partial.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 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
              }
          }
        });
}

Надеюсь, что это поможет!

Ответ 3

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

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

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

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

При создании AVPlayer добавьте наблюдателя:

[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"]

Ответ 4

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

Ответ 5

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

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

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

Handler:

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

Magic:

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

Ответ 6

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

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

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

Ответ 7

в очень плохой сети playbackLikelyToKeepUp, скорее всего, будет false.

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