Why does observer for status change of AVAsset not work? - ios

I implemented the following code from Apple. It is meant to observe for a change in the status of a playerItem. The problem is that for some reason it does not work. The observe function does not run when ready.
All relevant code is below:
func preloadVideo(media: Media) {
//setup code, and then:
media.playerItem1!.addObserver(self,
forKeyPath: #keyPath(AVPlayerItem.status),
options: [.old, .new],
context: &playerItemContext)
}
Observe method:
private var playerItemContext = 0
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
// Only handle observations for the playerItemContext
print("jdslfhjkfdhaldfahjkflhajfldashkjfdshkjlas")
guard context == &playerItemContext else {
super.observeValue(forKeyPath: keyPath, of: object,change: change, context: context)
return
}
if keyPath == #keyPath(AVPlayerItem.status) {
let status: AVPlayerItem.Status
// Get the status change from the change dictionary
if let statusNumber = change?[.newKey] as? NSNumber {
status = AVPlayerItem.Status(rawValue: statusNumber.intValue)!
} else {
status = .unknown
}
print("dsfdgddgdggdgdgdgddsafdsaf434fdfdg433444343")
// Switch over the status
switch status {
case .readyToPlay:
//Stop animation, and go to p3
print("dsfdsafdsaf434433444343")
if goToVideo {
print("Runinfdsh sahkbdsfhbsadfbadsfl")
goToP3()
}
break
// Player item is ready to play.
case .failed: break
// Player item failed. See error.
case .unknown: break
// Player item is not yet ready.
#unknown default: break
//fatal error
}
}
}
This method will run first:
#objc func viewHandleTap(_ sender: UITapGestureRecognizer) {
if selectedPost?.interimMedia.first?.imageURLString != nil || selectedPost?.interimMedia.first!.playerQueue?.status.rawValue == 1 {
//do something
} else {
//status is 0 (video has not loaded yet)//load animation
//this runs...
goToVideo = true
}
}
How can I make it work? Currently nothing in the observe method is printing.
If there is another method to accomplish this go ahead and post as answer.

Try by updating addObserver options to [.old, .new, .initial, .prior].

Related

Thread 5: Simultaneous accesses to 0x10b883638, but modification requires exclusive access

I had asked this question regarding some Apple code and making it work.
I have looked here but the answer there does not solve my problem
Although I solved that problem I am not getting the bellow error on the line shown.
Thread 5: Simultaneous accesses to 0x10b883638, but modification requires exclusive access
private var playerItemContext = 0
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
// Only handle observations for the playerItemContext
print("jdslfhjkfdhaldfahjkflhajfldashkjfdshkjlas")
guard context == &playerItemContext else {
super.observeValue(forKeyPath: keyPath, of: object,change: change, context: context)
return
}
...
Why is this and how can I fix this?
Try this code:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
// Only handle observations for the playerItemContext
guard context == &P2SheetViewController.playerStatusContext else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
if keyPath == #keyPath(AVPlayer.status) {
let status: AVPlayer.Status
if let statusNumber = change?[.newKey] as? NSNumber {
status = AVPlayer.Status(rawValue: statusNumber.intValue)!
} else {
status = .unknown
}
//Switch over status value
switch status {
case .readyToPlay:
break
// Player item is ready to play
case .failed:
print(".UKNOWN")
break
// Player item failed. See error.
case .unknown:
print(".UKNOWN")
break
// Player item is not yet ready.
#unknown default: //new jul 17
print(".UKNOWN")
break
}
}
}
Hope it helps

Swift iOS -How to Access Error from AVPlayerItemStatus?

I'm using an AVPlayer to play a video url. I followed Apple's code to play the video and handle errors:
Inside the section that says Respond to A State Change there is a switch statement that has a .failed case and the commented out code says: // Player item failed. See error. I've had this run a couple of times by putting a break point there.
The problem is I don't see anything like a variable with a NSError type that would give me an option to actually print out what the error is. How can I find out what the .failed the error is?
Observe the Player's State:
let url: URL = // Asset URL
var asset: AVAsset!
var player: AVPlayer!
var playerItem: AVPlayerItem!
// Key-value observing context
private var playerItemContext = 0
let requiredAssetKeys = [
"playable",
"hasProtectedContent"
]
func prepareToPlay() {
// Create the asset to play
asset = AVAsset(url: url)
// Create a new AVPlayerItem with the asset and an
// array of asset keys to be automatically loaded
playerItem = AVPlayerItem(asset: asset,
automaticallyLoadedAssetKeys: requiredAssetKeys)
// Register as an observer of the player item's status property
playerItem.addObserver(self,
forKeyPath: #keyPath(AVPlayerItem.status),
options: [.old, .new],
context: &playerItemContext)
// Associate the player item with the player
player = AVPlayer(playerItem: playerItem)
}
Respond to a State Change
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
// Only handle observations for the playerItemContext
guard context == &playerItemContext else {
super.observeValue(forKeyPath: keyPath,
of: object,
change: change,
context: context)
return
}
if keyPath == #keyPath(AVPlayerItem.status) {
let status: AVPlayerItemStatus
if let statusNumber = change?[.newKey] as? NSNumber {
status = AVPlayerItemStatus(rawValue: statusNumber.intValue)!
} else {
status = .unknown
}
// Switch over status value
switch status {
case .readyToPlay:
// Player item is ready to play.
case .failed:
// Player item failed. See error.
case .unknown:
// Player item is not yet ready.
}
}
}
if status is .failed then you can access the error from AVPlayer itself like this.
switch status {
case .readyToPlay:
// Player item is ready to play.
case .failed:
handleErrorWithMessage(player.currentItem?.error?.localizedDescription, error:player.currentItem?.error)
case .unknown:
// Player item is not yet ready.
}
Find below function.
func handleErrorWithMessage(_ message: String?, error: Error? = nil) {
print("Error occured with message: \(message), error: \(error).")
}
Hope this helps.

AVPlayer on selected rate only once

I've got an issue with AVPlayer. The case is: I've got one function, that sets the rate of AVPlayer
func applyEffect(effectNumber : Int) {
switch effectNumber {
case 0:
self.AvPlayer.setRate(Float(1.0), time: kCMTimeInvalid, atHostTime: kCMTimeInvalid)
case 1:
self.AvPlayer.setRate(Float(1.5), time: kCMTimeInvalid, atHostTime: kCMTimeInvalid)
case 2:
self.AvPlayer.setRate(Float(0.5), time: kCMTimeInvalid, atHostTime: kCMTimeInvalid)
default:
self.AvPlayer.setRate(Float(1.0), time: kCMTimeInvalid, atHostTime: kCMTimeInvalid)
}
}
For every response I download audio file into AVPlayerItem and save them as array. I've got an observer for item which calls applyEffect function if AVPlayerItem.status is changed, otherwise it applies effect immediately:
if AVCurrentItem.status != AVPlayerItemStatus.readyToPlay {
AVCurrentItem.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.new, context: nil)
pushMessageLoading()
} else {
self.applyEffect(effectNumber: currentEffect)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "status" {
popMessageLoading()
self.applyEffect(effectNumber: currentEffect)
AVCurrentItem.removeObserver(self, forKeyPath: "status")
}
}
The AVPlayer property "automaticallyWaitsToMinimizeStalling" is set to false during the initialisation. So, first time audio plays on a requested rate, but all times after - on rate like 1.0. What's the problem?
The root of evil is simple: I've used AVPlayer.play which equals to AVPlayer.rate = 1.0, instead of this you need to use AVPlayer.rate = desired value

Detect when AVPlayer is playing

How do you detect when an AVPlayer is playing?. There appears to be a slight delay between when the play() function is called and the video actually plays.
For Video:
AVPlayer have an argument called rate (Float), if the rate is greater than 0.0, there a video that currently playing.
You can check the if the rate is !=0: (the rate can be negative if the player goes backwards)
if vidPlayer != nil && vidPlayer.rate != 0 {
println("playing")
}
AVPlayer class reference
As far as I know, I agree with you that there is a slight delay between when the play() function is called and the video actually plays (In another word, the time that the first frame of the video has been rendered). The delay depends on some criteria such as video types (VOD or live streaming), the network condition, ... However, fortunately, we are able to know whenever the first frame of the video rendered, I mean exactly when the video actually plays.
By observing the status of the current AVPlayerItem and whenever it is AVPlayerItemStatusReadyToPlay, that should be the first frame has been rendered.
[self.playerItem addObserver:self forKeyPath:#"status" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL];
-(void)observeValueForKeyPath:(NSString*)path ofObject:(id)object change:(NSDictionary*)change context:(void*) context {
if([self.playerItem status] == AVPlayerStatusReadyToPlay){
NSLog(#"The video actually plays")
}
}
By the way, there is another solution where we observe readyForDisplay status of AVPlayerLayer, it also indicates whenever the video rendered. However, this solution has a drawback as mentioned in Apple document
/*!
#property readyForDisplay
#abstract Boolean indicating that the first video frame has been made ready for display for the current item of the associated AVPlayer.
#discusssion Use this property as an indicator of when best to show or animate-in an AVPlayerLayer into view.
An AVPlayerLayer may be displayed, or made visible, while this propoerty is NO, however the layer will not have any
user-visible content until the value becomes YES.
This property remains NO for an AVPlayer currentItem whose AVAsset contains no enabled video tracks.
*/
#property(nonatomic, readonly, getter=isReadyForDisplay) BOOL readyForDisplay;
Here is the sample code
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
[self.playerLayer addObserver:self forKeyPath:#"readyForDisplay" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL];
-(void)observeValueForKeyPath:(NSString*)path ofObject:(id)object change:(NSDictionary*)change context:(void*) context {
if([self.playerLayer isReadyForDisplay]){
NSLog(#"Ready to display");
}
}
Thereotically, [self.playerLayer isReadyForDisplay] should return YES, however, as the document, it is not guaranted.
I hope this would be helpful.
Swift 4
Method 1
var rateObserver: NSKeyValueObservation?
self.rateObserver = myPlayer.observe(\.rate, options: [.new, .old], changeHandler: { (player, change) in
if player.rate == 1 {
print("Playing")
}else{
print("Stop")
}
})
// Later You Can Remove Observer
self.rateObserver?.invalidate()
Method 2
myPlayer.addObserver(self, forKeyPath: "rate", options: NSKeyValueObservingOptions(rawValue: NSKeyValueObservingOptions.new.rawValue | NSKeyValueObservingOptions.old.rawValue), context: nil)
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "rate" {
if player.rate == 1 {
print("Playing")
}else{
print("Stop")
}
}
}
There are two things you might want to detect:
AVPlayerLayer's isReadyForDisplay is when the first frame has been received.
AVPlayerItem's readyToPlay is when the video actually starts playing.
In order to check both status you can use observability.
You're going to have three objects defined in your class:
var player: AVPlayer?
var playerItem: AVPlayerItem?
var playerLayer: AVPlayerLayer?
The player implementation is going to be like the following:
guard let videoURL = URL(string: "<videoPath>") else {
return
}
asset = AVAsset(url: videoURL)
guard let asset = asset else {
return
}
playerItem = AVPlayerItem(asset: asset, automaticallyLoadedAssetKeys: requiredAssetKeys)
guard let playerItem = playerItem else {
return
}
playerItem.addObserver(self,
forKeyPath: #keyPath(AVPlayerItem.status),
options: [.old, .new],
context: &playerItemContext)
player = AVPlayer(playerItem: playerItem)
playerLayer = AVPlayerLayer(player: player)
playerLayer?.addObserver(self,
forKeyPath: #keyPath(AVPlayerLayer.isReadyForDisplay),
options: [.old, .new],
context: &playerLayerContext)
avpController.player = player
avpController.view.frame.size.height = playerView.frame.size.height
avpController.view.frame.size.width = playerView.frame.size.width
playerView.addSubview(avpController.view)
avpController.player?.play()
Here, the context are either simple integers or enum.
You can handle the events by overriding the observeValue method.
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
guard (context == &playerItemContext) ||
(context == &playerLayerContext) else {
super.observeValue(forKeyPath: keyPath,
of: object,
change: change,
context: context)
return
}
if keyPath == #keyPath(AVPlayerItem.status) {
let status: AVPlayerItem.Status
if let statusNumber = change?[.newKey] as? NSNumber {
status = AVPlayerItem.Status(rawValue: statusNumber.intValue)!
} else {
status = .unknown
}
switch status {
case .readyToPlay:
print("Ready to play")
case .failed:
navigationController?.popViewController(animated: true)
case .unknown:
print("Unknown")
#unknown default:
print("Unknown default")
}
} else if keyPath == #keyPath(AVPlayerLayer.isReadyForDisplay) {
if playerLayer?.isReadyForDisplay ?? false {
print("Ready to display")
}
}
}
Don't forget to remove the observers.
deinit {
playerItem?.removeObserver(self, forKeyPath: #keyPath(AVPlayerItem.status))
playerLayer?.removeObserver(self, forKeyPath: #keyPath(AVPlayerLayer.isReadyForDisplay))
}

AVPlayer, notification for play/pause state?

I'm searching for a way to get notified the exact moment when AVPlayer starts playing. There's the "rate" property, but currently I am checking it periodically with an NSTimer to get updates.
I tried KVO, but apparently it's not KVO compliant.
I know that there are events when the player ENDED. But i'm talking about pause here.
I also KVO subscribed to AVPlayerItem's "status", but it's showing me when the HTTP asset has finished caching, no play/pause. I also started collecting all calls of play/pause, requesting an instant UI update afterwards, but it takes some more runloops before AVPlayer really starts playing. I'd just love to update my button instantly.
Why do you say that "rate" is not KVO complaint?
It works for me.
Here is what I did:
- (void)viewDidLoad
{
...
[self.player addObserver:self forKeyPath:#"rate" options:0 context:nil];
}
And then:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:#"rate"]) {
if ([self.player rate]) {
[self changeToPause]; // This changes the button to Pause
}
else {
[self changeToPlay]; // This changes the button to Play
}
}
}
For iOS 10 onwards You can check new property of AVPlayer timeControlStatus.
if(avPlayerObject.timeControlStatus==AVPlayerTimeControlStatusPaused)
{
//Paused mode
}
else if(avPlayerObject.timeControlStatus==AVPlayerTimeControlStatusPlaying)
{
//Play mode
}
AVPalyer as default observer to track the current duration of the video ,when you pause or resume the video you can get paused time by using one global variable (inside observer update that variable)
CMTime interval = CMTimeMake(1, 1);
//The capture of self here is coming in with your implicit property access of self.currentduration - you can't refer to self or properties on self from within a block that will be strongly retained by self.
//You can get around this by creating a weak reference to self before accessing timerDisp inside your block
__weak typeof(self) weakSelf = self;
self.timeObserverToken = [_player addPeriodicTimeObserverForInterval:interval queue:NULL usingBlock: ^(CMTime time)
{
_currentDuration = (int)CMTimeGetSeconds (_player.currentTime);
if(!_isPlaying)
{
_pausedDuration = _currentDuration;
}
}
If you're targeting iOS 13 and up, you can pull this off elegantly using Combine:
cancellable = myAVPlayerInstance.publisher(for: \.timeControlStatus)
.sink { [unowned self] status in
...
}
where status is any case of AVPlayer.TimeControlStatus
player = AVPlayer(url: URL(fileURLWithPath: path))
player.addObserver(self, forKeyPath: "rate", options: NSKeyValueObservingOptions.new, context: nil)
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "rate" {
if player.rate > 0 {
print("video started")
}
}
}
in swift
Add an observer to your AVPlayer object's rate value:
player.addObserver(self, forKeyPath: "rate", options: [], context: nil)
And override the method that will be called when the rate changes:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "rate", let player = object as? AVPlayer {
if player.rate == 1 {
print("Playing")
} else {
print("Paused")
}
}
}
Need to add an observer to AVPlayer object's rate value:
player?.addObserver(self, forKeyPath: "rate", options: NSKeyValueObservingOptions.new, context: nil)
Override below method to observe changes in rate property
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "rate" {
if let status = player?.timeControlStatus {
switch status{
case .paused:
//Paused mode
print("paused")
case .waitingToPlayAtSpecifiedRate:
//Resumed
print("resumed")
case .playing:
//Video Ended
print("ended")
#unknown default:
print("For future versions")
}
}
}
}

Resources