Player swift crash app - ios

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)
}

Related

iOS MPMusicPlayerController song changed notification does not have a song object with it

I am developing an app that listens to song changes of the MPMusicPlayerController.
For that, I am adding the following observer:
NotificationCenter.default
.addObserver(self,
selector: #selector(systemSongDidChange(_:)),
name: .MPMusicPlayerControllerNowPlayingItemDidChange,
object: nil)
The problem is that, when the notification is fired, the nowPlayingItem that can be found at (notification?.object as? MPMusicPlayerController)!.nowPlayingItem is always nil.
Am I doing anything wrong or is there some special trick that must be done to retrieve the actual nowPlayingItem?
Here is a more complete code:
// ...
init() {
let systemPlayer = MPMusicPlayerController.systemMusicPlayer
NotificationCenter.default.addObserver(self,
selector: #selector(systemSongDidChange(_:)),
name: .MPMusicPlayerControllerNowPlayingItemDidChange,
object: systemPlayer)
player.beginGeneratingPlaybackNotifications()
}
private func systemSongDidChange(notification: Notification) {
let currentSong = (notification.object as? MPMusicPlayerController)?.nowPlayingItem
// `currentSong` is always `nil` =/
}
// ...
The player I am using is the Apple's Music Player. I am not playing songs from the cloud.
You need to set the object within your notification, to be able to get the nowPlayingItem.
The code should look something like this:
private let playerController = MPMusicPlayerController.applicationMusicPlayer
NotificationCenter.default.addObserver(
self,
selector: #selector(systemSongDidChange(_:)),
name: .MPMusicPlayerControllerNowPlayingItemDidChange,
object: playerController
)
Then you should be able to access the nowPlayingItem like this in your systemSongDidChange function:
func systemSongDidChange(_ notification: Notification) {
guard let playerController = notification?.object as? MPMusicPlayerController else {
return
}
let item = playerController.nowPlayingItem
}
I have just found out why the nowPlayingItem is always being nil.
It seems that the user must have allowed the app to access the "Media & Apple Music". If this access has not been granted, the app will not have permission to know what is currently playing on the system's player.
This authorization can be requested as follows:
// if not yet given or requested
MPMediaLibrary.requestAuthorization { authorizationStatus in }
or
// if already requested and denied (will take user to the App Settings Page)
UIApplication.shared.openURL(URL(string:UIApplicationOpenSettingsURLString)!)

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
}

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.

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)
}

Swift: Detect video end

I have to play some videos in my swift application. The video is working perfectly but I want to detect when the video is ended. I searched about that then I found that the NotificationCenter is the solution for that. I used this code but my application crash at the end of the video.
This is my code:
func playVideo(url: NSURL){
let player = AVPlayer(url: url as URL)
NotificationCenter.default.addObserver(self, selector: Selector(("playerDidFinishPlaying")), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem)
player.play()
}
func playerDidFinishPlaying(note: NSNotification) {
print("Video Finished")
}
The error is:
[myApp.myViewController playerDidFinishPlaying]: unrecognized selector sent to instance 0x79669740
Any help please?
Because your selector is wrong, obviously. You are saying:
Selector(("playerDidFinishPlaying"))
But that is not the Objective-C name of your method.
Clearly, you don't know how to make the Objective-C name of your method. And you don't have to! This is exactly what #selector syntax solves. Just use it:
#selector(playerDidFinishPlaying)
And now it will work, because Swift will solve the problem you don't know how to solve.

Resources