AVPlayer playing even though rate property is 0 - ios

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.

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
}

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

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
}

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

Remove loop AVPlayer Observer in UIPageViewController

Hi guys I am having a problem with UIPageViewController and Notifications.
I have a page UIPageVewController with a array of pages, so in these pages I have a AVplayer playing in loop as bellow:
func loopVideo(videoPlayer:AVPlayer){
NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil, queue: nil){
[weak videoPlayer] notification in
videoPlayer?.seek(to: kCMTimeZero)
videoPlayer?.play()
}
}
The problem is when I change page with scroll the notifications from another pages change my current video playing AVPlayer. I put a print inside the notification and I can see calling the other pages notifications. I don't Know what I have to do?
I tried remove the notification in the viewDidDisappear using NotificationCenter.default.removeObserver(self) but didn't work.
Can you help me?
Thanks
NotificationCenter.default.removeObserver(self) won't work here as you never added yourself as a target.
Instead keep a reference to your notification and remove it. I think it should look something like this:
var notificationObserver:NSObjectProtocol?
func loopVideo(videoPlayer:AVPlayer){
self.notificationObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil, queue: nil){
[weak videoPlayer] notification in
videoPlayer?.seek(to: kCMTimeZero)
videoPlayer?.play()
}
}
func removeObserver() {
NotificationCenter.default.removeObserver(self.notificationObserver)
}
You can simply do a check when your notification is received.
Check if the notification object as AVPlayerItem is the same as the visible views player playerItem, videoPlayer.currentItem
Or simply check if the AVPlayerItem in the notification is the same as the yourCustomView.playerItem
EDIT:
I see your object is nil, it should be the AVPlayerItem. Check this thread.

Resources