Avplayer Can't Resume After Paused + Some Waiting

AVPlayer stops playing and doesn't resume again

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

Resuming AVPlayer after phone call

I solved this but creating a shared VideoPlayer class that contained references to all the screen that had animations.

import Foundation
import UIKit
import AVKit

class VideoPlayer: NSObject {

static var shared: VideoPlayer = VideoPlayer()

var avPlayer: AVPlayer!
var avPlayerLayer: AVPlayerLayer!

weak var vcForConnect:ConnectVC?
weak var vcForList:ListVC?

override init() {
super.init()
guard let path = Bundle.main.path(forResource: "animation", ofType:"mp4") else {
print("video not found")
return
}
avPlayer = AVPlayer(url: URL(fileURLWithPath: path))
avPlayerLayer = AVPlayerLayer(player: avPlayer)
avPlayerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
avPlayer.volume = 0
avPlayer.actionAtItemEnd = .none
loopVideo(videoPlayer: avPlayer)
avPlayer.play()
NotificationCenter.default.addObserver(self, selector: #selector(handleInterruption(notification:)), name: AVAudioSession.interruptionNotification, object: nil)

}

deinit {
avPlayer.pause()
}

@objc func handleInterruption(notification: Notification) {
guard let info = notification.userInfo,
let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
return
}
if type == .began {
// Interruption began, take appropriate actions (save state, update user interface)
self.avPlayer.pause()
} else if type == .ended {
guard let optionsValue =
info[AVAudioSessionInterruptionOptionKey] as? UInt else {
return
}
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
if options.contains(.shouldResume) {
// Interruption Ended - playback should resume
self.avPlayer.play()
}
}
}

func resumeAllAnimations() {
self.avPlayer.play()
if vcForList?.avPlayer != nil {
vcForList?.avPlayer.play()
}
if vcForConnect?.avPlayer != nil {
vcForConnect?.avPlayer.play()
}
if vcForConnect?.avPlayerBG != nil {
vcForConnect?.avPlayerBG.play()
}
}
...
}

I then resume the animations by calling resumeAllAnimations() in applicationDidBecomeActive(_:) in AppDelegate.swift like so:

func applicationDidBecomeActive(_ application: UIApplication) {
VideoPlayer.shared.resumeAllAnimations()
...
}

HLS audio stream fails to resume play after time in background (AVPlayer refuses to buffer)

The code works for me on iOS 13.5.1 and iOS 14.4, but fails on 14.0.1.
It looks like an iOS bug. You could work around this by recreating the AVPlayerItem when returning to the foreground on affected systems.

How to know when AVPlayer is ready to play and sent info to the controller

You can add observers for the AVPlayer properties, e.g. in Swift 3:

player.addObserver(self, forKeyPath: "reasonForWaitingToPlay", options: .new, context: &observerContext)

Or in Swift 2, use .New:

player.addObserver(self, forKeyPath: "reasonForWaitingToPlay", options: .New, context: &observerContext)

Note, that's using a private property to identify the context:

private var observerContext = 0

And then you can add the observer method. In Swift 3:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard context == &observerContext else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}

// look at `change![.newKey]` to see what the status is, e.g.

if keyPath == "reasonForWaitingToPlay" {
NSLog("\(keyPath): \(change![.newKey])")
}
}

Or in Swift 2:

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
guard context == &observerContext else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
return
}

// look at `change![NSKeyValueChangeNewKey]` to see what the status is, e.g.

if keyPath == "reasonForWaitingToPlay" {
NSLog("\(keyPath): \(change![NSKeyValueChangeNewKey])")
}

}


Related Topics



Leave a reply



Submit