One AVPlayer's AVPlayerItemDidPlayToEndTime action executed for all Currently playing videos - ios

Problem : In collectionview cell which has player
if I play two Video simultaneously and seek first Video to end then AVPlayerItemDidPlayToEndTime fired for two times and both videos restarted
In collection view cell I have
override func awakeFromNib() {
NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: .main, using: {[weak self] (notification) in
if self?.player != nil {
self?.player?.seek(to: kCMTimeZero)
self?.player?.play()
}
})
}
and one play button action which play the video.
In cell I have slider to seek.
Any Help would be appreciated

Make sure that player and player?.currentItem are not equal to nil when you're registering for notifications. To me, it seems like one of them was nil and you're basically subscribing to all of the .AVPlayerItemDidPlayToEndTime notifications (since object is nil).
To avoid that, subscribe to the notifications right after assigning AVAsset to the player.

Swift 5.1
Pass the item as your object:
// Stored property
let player = AVPlayer(url: videoUrl)
// When you are adding your video layer
let playerLayer = AVPlayerLayer(player: player)
NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying), name: .AVPlayerItemDidPlayToEndTime, object: player.currentItem)
// add layer to view
Then when you get that notification, here's how you can get the currentItem:
// Grab the item from the notification object and ensure its the same item as the current players item
if let item = notification.object as? AVPlayerItem,
let currentItem = player.currentItem,
item == currentItem {
NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: nil)
// remove player from view or do whatever you need to do here
}

Related

AVPlayerItemDidPlayToEndTime clash with applicationDidBecomeActive

I've an AVPlayer embedded inside a UIViewController. I've added AVPlayerItemDidPlayToEndTime notification to the UIViewController so that I can restart my AVPlayer once it finished playing currentItem
NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object:player.currentItem)
#objc private func playerDidFinishPlaying(_ notification: Notification) {
guard let url = URL(string: self.video.alt_content) else { return }
let item = self.getAssetToPlay(url: url)
item.seek(to: .zero, toleranceBefore: .zero, toleranceAfter: .zero)
self.player.replaceCurrentItem(with: item)
player.play()
}
private func getAssetToPlay(url: URL) -> AVPlayerItem {
let asset = AVURLAsset(url: url)
let item = AVPlayerItem(asset: asset)
return item
}
But this notification is also called from AppDelegate's applicationDidBecomeActive, like when my app comes from background. And so rather than playing AVPlayer from current time, code restarts the player in playerDidFinishPlaying.
I need a way so that when app comes from background it starts playing where it was left. And on completely finishing currentItem, it restarts the AVPlayerItem
So the problem was very stupid to be honest. It was due to the fact that I added the Observer before initializing the AVPlayer. Once I corrected the code, it no longer clashes with applicationDidBecomeActive

Swift AVPlayer - The proper way to loop a video with custom rate

So I have an AVPlayer which I would like to continuously loop. This is not an issue as this works fine.
However, the user has the ability to edit the rate of the video. If the rate is changed, it will loop fine for 2-3 times and then the AVPlayer get stuck on the first frame.
The completion block is not called after it gets stuck.
Here is my code for looping the AVPlayer
// Loop video notification
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: .main) { [weak self] _ in
guard let self = self, let player = self.player else { return }
player.seek(to: .zero)
player.play()
player.rate = self.playerRate
}

AVPlayer play continuously

I have an array of object. Each object has a property url. Using this url I am playing audio on AVPlayer.
func playAudio(url: URL)
{
let player = AVPlayer(url: url )
let playerViewController = AVPlayerViewController()
playerViewController.player = player
self.present(playerViewController, animated: true)
{
do
{
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
}
catch _ as NSError {
}
playerViewController.player!.play()
}
NotificationCenter.default.addObserver(self, selector: #selector(CourseDetailViewController.moviePlayBackDidFinish(notification:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
}
I want to play the audios of all objects continuously. For which I have set
NotificationCenter.default.addObserver(self, selector: #selector(CourseDetailViewController.moviePlayBackDidFinish(notification:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
In CourseDetailViewController.moviePlayBackDidFinish I am again calling playAudio. I then took player as a global variable so that it is not initialised again. But there is no overload of AVPlayer where we can supply url to already existing AVPlayer. And if do let player = AVPlayer(url: url ) for next audio, it doesn't play because the previous AVPlayer instance is in the foreground.
This is the challenge I am facing in playing continuous audios. I would appreciate if there a better and neat way to do this.
I did the same in AVPlayer, but playing video continuously. I hope you can solve this issue exactly the same way. Add an observer to check if the player reached at the end of item. When ever each item reached at the end, notification will call.
Adding observer:
NotificationCenter.default.addObserver(self,
selector: #selector(playerItemDidReachEnd(notification:)),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: avPlayer.currentItem)
avPlayer.play()
Notification method:
func playerItemDidReachEnd(notification: Notification){
//TODO: Add your code here..
}
Did you look at AVQueuePlayer. This player is used to play a number of items in sequence. SDK Docs at link

AVPlayer playing even though rate property is 0

This other SO post suggests when the rate property for an AVPlayer is 0, it means the AVPlayer is no longer playing. We use the observer code below to loop a video, but sometimes invoking the pause function on the player fails to break the looping. When debugging, the rate property evaluates to 0.
1) Is the linked SO post wrong, in that a rate value of 0 does not mean the player is paused?
2) Is the continuous looping, even after the pause function is invoked, some kind of race condition where the pause function comes after the playerItemDidReachEnd notification is already issued?
NSNotificationCenter.defaultCenter().addObserver(self, selector: "playerItemDidReachEnd:", name: AVPlayerItemDidPlayToEndTimeNotification, object: playerItem)
private func playVideo() {
player.seekToTime(kCMTimeZero)
player.actionAtItemEnd = .Pause
player.play()
}
when setting up the player:
player.actionAtItemEnd = AVPlayerActionAtItemEnd.None
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "playerItemDidReachEnd:",
name: AVPlayerItemDidPlayToEndTimeNotification,
object: player.currentItem)
this will prevent the player to pause at the end.
in the notification:
func playerItemDidReachEnd(notification: NSNotification) {
let p: AVPlayerItem = notification.object as! AVPlayerItem
p.seekToTime(kCMTimeZero)
}
this will rewind the movie.
Don't forget to unregister the notification when releasing the player.

View unresponsive after AVPlayerViewController dismissed - Swift

I have a parent view with a play button that instantiates an AVPlayerViewController object. If the user presses the player's default "Done" button, the player view disappears and the parent view works as expected. However, when playback finishes by reaching the end of the video, the player disappears but the parent view screen is unresponsive to button clicks. I don't understand because both scenarios are using the same callback to dismiss the player view.
Here is the code where I instantiate the AVPlayer and set up the two notifications for how the playback can be completed:
func playVideoLocally() {
// create player
let playerItem = AVPlayerItem(asset: AVAsset(URL: NSURL(string: media.url)!))
player = AVPlayer(playerItem: playerItem)
// create modal view with callback for done button
// This works fine
playerController = CustomAVPlayerViewController()
playerController.player = player
playerController.modalPresentationStyle = UIModalPresentationStyle.OverFullScreen
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "onLocalPlayCompletion:",
name: CustomAVPlayerViewController.viewWillDisappearNotification,
object: nil)
self.presentViewController(playerController, animated: true, completion: nil)
// create player completion callback
// This leads to unresponsive parent view
NSNotificationCenter.defaultCenter().addObserver(self, selector: "onLocalPlayCompletion:",
name: AVPlayerItemDidPlayToEndTimeNotification,
object: playerItem)
// play it
player.play()
}
Here is my onLocalPlayCompletion callback that both scenarios are calling:
func onLocalPlayCompletion(note: NSNotification) -> Void {
playerController.view.removeFromSuperview()
onCompletion()
}
I found the problem. The only reason why pressing "Done" wasn't affecting the parent view is because it's using a default AVPlayer action to dismiss itself. My attempt to dismiss the player from inside onLocalPlayCompletion(),
playerController.view.removeFromSuperview()
isn't correct, and in my case I replaced it with
self.dismissViewControllerAnimated(true, completion: nil)
which does what I want.

Resources