AVPlayer停止播放并且不再恢复

2020/12/16 13:32 · ios ·  · 0评论

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

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

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

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

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)等待视频加载-如果您继续直接播放,您将没有足够的缓冲区来继续播放而不中断。要知道什么时候开始,您必须观察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];
            }
                    }
    }];
}

就是这样!问题解决了

编辑:通过使用的库是libextobjc

我正在处理视频文件,因此代码中的内容超出了您的需求,但是以下解决方案应在播放器挂起时暂停播放器,然后每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
              }
          }
        });
}

希望能帮助到你!

接受的答案可以解决该问题,但它缺乏灵活性,也很难阅读。这是更灵活的解决方案。

添加观察者:

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

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

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

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

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

创建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"]

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

首先,我观察到播放停止

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方法继续回放。

我也遇到了这个问题,如下所述

我多次在下面测试了此答案,并且到目前为止每次都有效。

这是我为@wallace的答案的Swift 5版本想到

1-而不是观察keyPath"playbackLikelyToKeepUp"我使用.AVPlayerItemPlaybackStalled Notification和在里面检查缓冲区是否已满if !playerItem.isPlaybackLikelyToKeepUp {...}

2-而不是使用他,PlayerHangingNotification我使用一个名为playerIsHanging()

3-代替使用他,PlayerContinueNotification我使用了一个名为checkPlayerTryCount()

4-在内部,checkPlayerTryCount()我所做的一切与他的(void)playerContinue功能相同,除了遇到} else if playerTryCount == 0 {任何问题时。为了避免这种情况,我在return语句上方添加了两行代码

5-像@PranoyC在@wallace的注释下建议的那样,我将设置playerTryCount20而不是的最大值10我也将其设置为类属性let 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()
}

就我而言

  • 我试图使用imagePickerController录制视频并使用AVPlayerController回放录制的视频。但是它会开始播放视频并在1秒钟后停止播放。这样一来,它就有时间保存视频,并且如果立即播放,它将无法播放。

    所以解决办法是
  • 在0.5秒(延迟)后致电播放视频。像下面

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

    -(无效)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];
        }
    
本文地址:http://ios.askforanswer.com/avplayertingzhibofangbingqiebuzaihuifu.html
文章标签: ,   ,   ,  
版权声明:本文为原创文章,版权归 admin 所有,欢迎分享本文,转载请保留出处!

文件下载

老薛主机终身7折优惠码boke112

上一篇:
下一篇:

评论已关闭!