43

In my application I have to play audio files stored on a web server. I'm using AVPlayer for it. I have all the play/pause controls and all delegates and observers there which work perfectly fine. On playing small audio files everything works great.

When a long audio file is played it also starts playing fine but after some seconds the AVPlayer pauses the playing (most probably to buffer it). The issue is it doesn't resume on its own again. It keeps in a pause state and if I manually press the play button again it plays smoothly again.

I want to know why AVPlayer doesn't resume automatically and how can I manage to resume the audio again without user pressing the play button again? Thanks.

4

9 に答える 9

18

はい、バッファが空であるため停止するため、さらにビデオをロードするのを待つ必要があります。その後、手動で再起動を要求する必要があります。問題を解決するために、次の手順に従いました。

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) ビデオがロードされるのを待ちます - 直接再生を続けると、中断せずに再生を続けるのに十分なバッファがありません。いつ開始するかを知るには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です

于 2013-11-11T15:00:12.347 に答える
9

私はビデオ ファイルを扱っているので、私のコードには必要以上のものがありますが、次の解決策では、プレーヤーがハングしたときにプレーヤーを一時停止し、0.5 秒ごとにチェックして、追いつくのに十分なバッファリングがあるかどうかを確認します。その場合、プレーヤーを再起動します。プレーヤーが再起動せずに 10 秒以上ハングした場合、プレーヤーを停止し、ユーザーに謝罪します。これは、適切なオブザーバーを配置する必要があることを意味します。以下のコードは、私にとってはかなりうまく機能しています。

.h ファイルまたは他の場所で定義/初期化されたプロパティ:

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

部分的な .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
              }
          }
        });
}

それが役に立てば幸い!

于 2015-02-06T17:35:11.860 に答える
6

同様の問題がありました。再生したいローカル ファイルがいくつかあり、AVPlayer を構成して [player play] を呼び出すと、プレーヤーはフレーム 0 で停止し、手動で play を再度呼び出すまで再生しませんでした。受け入れられた答えは、説明が間違っていたために実装できませんでした。その後、プレイを遅らせてみたところ、魔法のように機能しました

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

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

Webビデオについても問題がありました。ウォレスの回答を使用して解決しました。

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"]
于 2015-07-01T19:05:16.077 に答える
4

最初に、再生の停止を観察します

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

これはおそらく最善の方法ではありませんが、より良い方法が見つかるまで使用しています:)

于 2016-07-25T17:39:13.613 に答える
4

here で説明されているように、この問題にも遭遇しました

以下でこの回答を複数回テストしましたが、これまでのところ毎回機能していました。

@wallaceの回答のSwift 5バージョンで思いついたのは次のとおりです。

1- keyPath"playbackLikelyToKeepUp"を観察する代わりに を使用し、.AVPlayerItemPlaybackStalled Notificationその内部でバッファがいっぱいかどうかを確認しますif !playerItem.isPlaybackLikelyToKeepUp {...}

2-彼を使用する代わりに、PlayerHangingNotificationという名前の関数を使用しますplayerIsHanging()

3-彼を使用する代わりに、PlayerContinueNotification私はという名前の関数を使用しますcheckPlayerTryCount()

4-そして、内部では、何も起こらない場合を除いcheckPlayerTryCount()て、彼の機能と同じことをすべて行います. それを避けるために、ステートメントの上に2行のコードを追加しました(void)playerContinue} else if playerTryCount == 0 {return

5- @wallace のコメントの下で提案された @PranoyC のように、を の代わりにplayerTryCount最大値に設定しました。クラスプロパティとしても設定します2010let playerTryCountMaxLimit = 20

コメントがそうするように示唆している場所で、アクティビティインジケーター/スピナーを追加/削除する必要があります

コード:

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()
}
于 2020-06-22T10:42:22.927 に答える