Remove loop AVPlayer Observer in UIPageViewController - ios

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.

Related

Do I have to remove in built observers in swift?

So I have a login page that has a video looping in the background and I have done this with the following code:
videoPlayer.play()
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: videoPlayer.currentItem, queue: .main) { [weak self] _ in
self?.videoPlayer?.seek(to: CMTime.zero)
self?.videoPlayer?.play()
}
Question is do I have to remove this observer at deinit or something and if so how do I go about removing the .AVPlayerItemDidPlayToEndTime observer. Not sure of the syntax when it comes to removing these built in observers.
Swift automatically deinitializes built in observables when the controller is dismissed - just use the following function.
deinit {
// Release all resources
// perform the deinitialization
}
there is also a similar question asked here.
Swift deinit

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

Player swift crash app

I have add Player-swift in my project to play video but when i pop that controller app getting crash with following log
Terminating app due to uncaught exception 'NSRangeException', reason:
'Cannot remove an observer for the key
path "rate" from because it is not
registered as an observer.'
any one have any idea? i have used this player https://github.com/piemonte/Player
Thanks in advance !
Do not forget to unsubsribe from observing some property. For example if you subscribed for observing rate then to remove observing use
player.removeObserver(observer, forKeyPath: #keyPath(AVPlayer.rate))
First: check KVO basics.
Second: in objective-C you would the removing observers code into try-catch block and live happy. The apple's guide tell the same:
Asking to be removed as an observer if not already registered as one
results in an NSRangeException. You either call
removeObserver:forKeyPath:context: exactly once for the corresponding
call to addObserver:forKeyPath:options:context:, or if that is not
feasible in your app, place the removeObserver:forKeyPath:context:
call inside a try/catch block to process the potential exception.
In swift there’s no KVO API call you can make to ask, “Is X observing key path Y of object Z?” There is some ways to workaround it.
Also check the one of the reasons of the crashes when removing the observer. Here is the quote:
"It" refers to the observer. -removeObserver:forKeyPath: raises this
exception if told to remove an object that isn't currently registered
as an observer. So what's happening is that a table view is trying to
unregister as an observer from one of your objects that it
unfortunately didn't previouly register as an observer for.
The usual cause of this is that you have a property that isn't KVO-
compliant. Something accesses your 'foo' property and registers as an
observer of that property, and also as an observer of the object
that's the property's current value; you change the value of 'foo'
without letting anyone know; the observer then later decides to stop
observing, gets your 'foo' property, and removes itself as an observer
of that object. But it's no longer the same object that it registered
as an observer for...
Take IBOutlet of Your UIView for Example
#IBOutlet var videoView:UIView!
var player:AVPlayer!
func buttonPressed()
{
let videoURL = URL(fileURLWithPath: "your File Path")
player = AVPlayer(url: videoURL)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.videoGravity = .resizeAspect
playerLayer.frame = videoView.bounds
videoView.layer.addSublayer(playerLayer)
player.play()
player.actionAtItemEnd = .none
NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemDidReachEnd(_:));, name: .AVPlayerItemDidPlayToEndTime, object: player.currentItem)
}
#objc func playerItemDidReachEnd(_ notification: Notification?)
{
let p = notification?.object as? AVPlayerItem
p?.seek(to: kCMTimeZero)
}
override func viewWillDisappear(_ animated: Bool)
{
NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: player.currentItem)
}

AVPlayer keeping in memory when the video is in loop

I need to put a player in loop, but why when I add the
NotificationCenter.default.addObserver(forName:NSNotification.Name.AVPlayerItemDidPlayToEndTime,object: nil, queue:nil){
notification in
videoPlayer.seek(to: KCMTimeZero)
videoplayer.play()
}
}
My view stay in memory when i give dismiss in my viewController.
How i reproduce a video my memory goes to increasing all every time that I open the ViewController
Whithout this code it is removed with sucess.
I don't know what I have to do
Can you help me please?
There are three issues with your code:
By default, references are passed as strong into a block. To make sure they are not retained, use weak or unowned:
NotificationCenter.default.addObserver(forName:NSNotification.Name.AVPlayerItemdidPlayToEndTime,object: nil, queue:nil){
[weak videoPlayer] notification in
videoPlayer?.seek(to: KCMTimeZero)
videoplayer?.play()
}
Since iOS 9, observers do not need to be removed from the NotificationCenter unless you are using block observers (which you are). You should store the reference to the observer which is returned from NotificationCenter.addObserver:forName:object:queue:usingBlock::
self.observer = NotificationCenter.default.addObserver(...)
and in viewWillDissappear:
NotificationCenter.default.removeObserver(self.observer)
(alternatively, you could use a selector instead, as Chan Jing Hong pointed out; in that case, removing the observer is no longer necessary but might be needed depending on your app's logic)
The way you are registering for NSNotification.Name.AVPlayerItemdidPlayToEndTime, you will be notified whenever the playback of any AVPlayerItem reaches the end. To avoid potential problems, listen to the notification on the currently played item (by replacing object:nil with object: playerItem)
You should set self as the observer when adding to NotificationCenter.
NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemDidReachedEnd(_:)), name: Notification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
This way, in your viewWillDisappear, you can do removeObserver()
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
}

Resources