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
iOS 13 Modals - Calling Swipe Dismissal Programmatically
Loop Over Multiple Uialertcontroller'S
Swiftui Sheet Not Updating Variable
Is Dispatchsemaphore a Good Replacement for Nslock
Typecast Unsafemutablepointer<Void> to Unsafemutablepointer<#Struct Type#>
Swift Create Byte Buffer Holder for Nsstream
Core Data: Rename Attribute Without Having Issues with Users and Their Current Data
Swiftui Hierarchical Picker with Dynamic Data
Type '()' Cannot Conform to 'View'
Not Getting Expected Delegate Calls When Trying to Restore In-App Purchases with Storekit
How to Define an Extension to Collectiontype So That Its Methods Are Available to Dictionaries
Crop Image According to Rectangle in Swiftui
"Use Default Container" Doesn't Show in Icloud Capabilities
What Might Be Causing This Animation Bug with Swiftui and Navigationview
Convert to Latest Swift Syntax' Breaks the Build Even When There Are No Changes