ios AVPlayer 停止播放并且不再恢复

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19291636/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 02:15:26  来源:igfitidea点击:

AVPlayer stops playing and doesn't resume again

iosaudioavfoundationavplayer

提问by iAmd

In my application I have to play audio files stored on a web server. I'm using AVPlayerfor 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.

在我的应用程序中,我必须播放存储在 Web 服务器上的音频文件。我正在使用AVPlayer它。我拥有所有播放/暂停控件以及所有代表和观察者,它们都可以正常工作。在播放小音频文件时一切正常。

When a long audio file is played it also starts playing fine but after some seconds the AVPlayerpauses 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.

当播放长音频文件时,它也开始正常播放,但几秒钟后AVPlayer暂停播放(很可能是为了缓冲它)。问题是它不会再次自行恢复。它保持暂停状态,如果我再次手动按下播放按钮,它会再次流畅播放。

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

我想知道为什么AVPlayer不自动恢复以及如何在没有用户再次按下播放按钮的情况下设法再次恢复音频?谢谢。

采纳答案by Jpellat

Yes, it stops because the buffer is empty so it has to wait to load more video. After that you have to manually ask for start again. To solve the problem I followed these steps:

是的,它停止是因为缓冲区是空的,所以它必须等待加载更多视频。之后,您必须手动要求再次启动。为了解决这个问题,我按照以下步骤操作:

1) Detection: To detect when the player has stopped I use the KVO with the rate property of the value:

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

This condition: CMTimeGetSeconds(self.playerItem.duration) != CMTimeGetSeconds(self.playerItem.currentTime)is to detect the difference between arriving at the end of the video or stopping in the middle

这个条件:CMTimeGetSeconds(self.playerItem.duration) != CMTimeGetSeconds(self.playerItem.currentTime)是检测到达视频结尾或中途停止的区别

2) Wait for the video to load - If you continue playing directly you will not have enough buffer to continue playing without interruption. To know when to start you have to observe the value playbackLikelytoKeepUpfrom the playerItem (here I use a library to observe with blocks but I think it makes the point):

2) 等待视频加载 - 如果您继续直接播放,您将没有足够的缓冲区来不间断地继续播放。要知道何时开始,您必须观察playbackLikelytoKeepUpplayerItem的值(这里我使用一个库来观察块,但我认为这很重要):

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

And that's it! problem solved

就是这样!问题解决了

Edit: By the way the library used is libextobjc

编辑:顺便说一下,使用的库是 libextobjc

回答by wallace

I am working with video files, so there's more to my code than you need, but the following solution should pause the player when it hangs, then check every 0.5 second to see whether we've buffered enough to keep up. If so, it restarts the player. If the player hangs for more than 10 seconds without restarting, we stop the player and apologize to the user. This means you need the right observers in place. The code below is working pretty well for me.

我正在处理视频文件,所以我的代码比您需要的要多,但是以下解决方案应该在播放器挂起时暂停播放器,然后每 0.5 秒检查一次,看看我们是否已经缓冲到足以跟上。如果是这样,它会重新启动播放器。如果播放器挂机超过 10 秒没有重启,我们将停止播放器并向用户道歉。这意味着您需要合适的观察员。下面的代码对我来说效果很好。

properties defined / init'd in a .h file or elsewhere:

在 .h 文件或其他地方定义/初始化的属性:

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

partial .m:

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

Hope it helps!

希望能帮助到你!

回答by gomezluisj

I had a similar issue. I had some local files i wanted to play, configured the AVPlayer and called [player play], the player stops at frame 0 and wouldn't play anymore until i called play again manually. The accepted answer was impossible for me to implement due to faulty explanation, then i just tried delaying the play and magically worked

我有一个类似的问题。我有一些我想播放的本地文件,配置了 AVPlayer 并调用了 [播放器播放],播放器在第 0 帧停止并且不会再播放,直到我再次手动调用播放。由于错误的解释,我无法实施接受的答案,然后我只是尝试延迟播放并神奇地工作

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

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

For web videos i also had the problem, i solve it using wallace's answer.

对于网络视频,我也遇到了问题,我使用华莱士的回答解决了这个问题。

When creating the AVPlayer add an observer:

创建 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];
    }
}

}

}

Remember to remove observer before dismissing the view

记得在关闭视图之前移除观察者

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

回答by xzysun

I think use AVPlayerItemPlaybackStalledNotificationto detect the stalled is a better way.

我认为使用AVPlayerItemPlaybackStalledNotification检测停滞是更好的方法。

回答by Raimundas Sakalauskas

Accepted answer gives a possible solution to the problem, but it lacks flexibility, also it's hard to read. Here's more flexible solution.

接受的答案为问题提供了可能的解决方案,但缺乏灵活性,也很难阅读。这是更灵活的解决方案。

Add observers:

添加观察者:

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

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

The "[self availableDuration]" is optional, but you can manually launch playback based on amount of video available. You can change how often the code checks whether enough video is buffered. If you decide to use the optional part, here's the method implementation:

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

Don't forget the cleanup. Remove observers:

不要忘记清理。移除观察者:

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

And possible pending calls to handle stalled video:

以及处理停滞视频的可能未决呼叫:

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

回答by budidino

First I observe for playback stalling

首先我观察播放停顿

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

Then I force playback continuation

然后我强制播放继续

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

This is probably not the best way of doing it, but I'm using it until I find something better :)

这可能不是最好的方法,但我一直在使用它,直到找到更好的东西:)

回答by DumplingKid

in very bad network playbackLikelyToKeepUpmost probably to be false.

在非常糟糕的网络中很playbackLikelyToKeepUp可能是假的。

use kvoto observe playbackBufferEmptyis better, more sensitive to if existing buffer data that can be used for playback .if the value change to true you can call play method to continue playback.

用于kvo观察playbackBufferEmpty更好,对是否存在可用于播放的缓冲区数据更敏感。如果值更改为true,则可以调用play方法继续播放。

回答by Shrikant Phadke

In my case ,

就我而言,

  • I was trying to record video with imagePickerController and playback recorded video with AVPlayerController. But it starts playing video and gets stops after 1 second.Somehow it gets time to save video and if you playback it immediately, it wont play.
    So solution is ,
  • call play video after 0.5 seconds (delay). like below

       -(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];
        }
    
  • 我试图用 imagePickerController 录制视频并用 AVPlayerController 播放录制的视频。但它开始播放视频并在 1 秒后停止。不知何故,它有时间保存视频,如果您立即播放,它将无法播放。
    所以解决方案是,
  • 0.5 秒后调用播放视频(延迟)。像下面

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

    -(无效)播放视频{

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