AVPlayer stops playing and doesn't resume again

前端 未结 9 1542
离开以前
离开以前 2020-12-02 06:03

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 observ

相关标签:
9条回答
  • 2020-12-02 06:31

    I also ran into this issue as described here

    I tested this answer below multiple times and it worked every time so far.

    Here is what I came up with for the Swift 5 version of @wallace's answer.

    1- instead of observing the keyPath "playbackLikelyToKeepUp" I use the .AVPlayerItemPlaybackStalled Notification and inside there I check to see if the buffer is full or not via if !playerItem.isPlaybackLikelyToKeepUp {...}

    2- instead of using his PlayerHangingNotification I use a function named playerIsHanging()

    3- instead of using his PlayerContinueNotification I use a function named checkPlayerTryCount()

    4- and inside checkPlayerTryCount() I do everything the same as his (void)playerContinue function except when I ran into } else if playerTryCount == 0 { nothing would happen. To avoid that I added 2 lines of code above the return statement

    5- like @PranoyC suggested under @wallace's comments I set the playerTryCount to a max of 20 instead of 10. I also set it as a class property let playerTryCountMaxLimit = 20

    You have to add/remove your activity indicator/spinner where the comment suggests to do so

    code:

    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()
    }
    
    0 讨论(0)
  • 2020-12-02 06:34

    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:

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

    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 playbackLikelytoKeepUp from the playerItem (here I use a library to observe with blocks but I think it makes the point):

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

    0 讨论(0)
  • 2020-12-02 06:35

    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

    [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:

    [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"]
    
    0 讨论(0)
  • 2020-12-02 06:38

    I think use AVPlayerItemPlaybackStalledNotification to detect the stalled is a better way.

    0 讨论(0)
  • 2020-12-02 06:42

    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 :)

    0 讨论(0)
  • 2020-12-02 06:49

    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];
          }
      
    0 讨论(0)
提交回复
热议问题