首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >AVPlayer停止播放,不再继续播放

AVPlayer停止播放,不再继续播放
EN

Stack Overflow用户
提问于 2013-10-10 09:13:22
回答 8查看 37.2K关注 0票数 43

在我的应用程序中,我必须播放存储在web服务器上的音频文件。我用的是AVPlayer。我有所有的播放/暂停控制和所有的代表和观察员,他们的工作非常好。在播放小型音频文件时,一切都很好。

当播放一个长音频文件时,它也会很好地开始播放,但是几秒钟后,AVPlayer会暂停播放(很可能是为了缓冲它)。问题是,它不会再单独恢复了。它保持在暂停状态,如果我再次手动按下播放按钮,它将再次平稳地播放。

我想知道为什么AVPlayer不自动恢复,我如何能够设法再次恢复音频而不用用户再次按下播放按钮?谢谢。

EN

回答 8

Stack Overflow用户

回答已采纳

发布于 2013-11-11 15:00:12

是的,它停止是因为缓冲区是空的,所以它必须等待加载更多的视频。在此之后,您必须手动请求重新开始。为了解决这个问题,我遵循了以下步骤:

1)检测:要检测播放机停止时,我使用具有该值的rate属性的KVO:

代码语言:javascript
复制
-(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 (在这里,我使用一个库来观察块,但我认为它说明了问题所在):

代码语言:javascript
复制
-(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。

票数 19
EN

Stack Overflow用户

发布于 2015-02-06 17:35:11

我正在处理视频文件,所以我的代码比您需要的要多,但是下面的解决方案应该在播放机挂起时暂停,然后每0.5秒钟检查一次,看看我们是否有足够的缓冲来跟上。如果是的话,它会重新启动玩家。如果播放机挂起超过10秒而不重新启动,我们将停止该播放器并向用户道歉。这意味着你需要正确的观察者就位。下面的代码对我非常有用。

在.h文件或其他地方定义/输入的属性:

代码语言:javascript
复制
AVPlayer *player;  
int playerTryCount = -1; // this should get set to 0 when the AVPlayer starts playing
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];

部分.m:

代码语言:javascript
复制
- (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
              }
          }
        });
}

希望能帮上忙!

票数 9
EN

Stack Overflow用户

发布于 2016-08-02 17:18:56

接受的答案给出了解决问题的可能方法,但它缺乏灵活性,也很难理解。这里有更灵活的解决方案。

增加观察员:

代码语言:javascript
复制
//_player is instance of AVPlayer
[_player.currentItem addObserver:self forKeyPath:@"status" options:0 context:nil];
[_player addObserver:self forKeyPath:@"rate" options:0 context:nil];

处理程序:

代码语言:javascript
复制
-(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];
        }
    }
}

魔法:

代码语言:javascript
复制
-(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“是可选的,但是您可以根据可用视频的数量手动启动回放。您可以更改代码检查是否有足够的视频被缓冲的频率。如果您决定使用可选部分,下面是方法实现:

代码语言:javascript
复制
- (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;
}

别忘了清理。撤走观察员:

代码语言:javascript
复制
[_player.currentItem removeObserver:self forKeyPath:@"status"];
[_player removeObserver:self forKeyPath:@"rate"];

以及处理被搁置的视频的可能悬而未决的呼叫:

代码语言:javascript
复制
[UIView cancelPreviousPerformRequestsWithTarget:self selector:@selector(handleStalled) object:nil];
票数 9
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/19291636

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档