How to check status of AVPlayer? - ios

I thought that I could check the status of an AVPlayer simply by the property "rate".
This is how I create a player instance:
player = AVPlayer(URL: stream) // streaming from the internet
player!.play()
At some later point I would do something like this
println(player!.rate)
This is what I discovered:
In Simulator I get "0.0" in case the player is not running or "1.0" if it is running.
If I start the player but interrupt the internet connection it changes values from 1 to 0.
However, on my iPhone the property keeps value 1 even if I enter Airplane Mode?!
Do you have any idea why that happens and how I could check the stream condition otherwise?
I have tried an observer so far:
player!.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.New, context: nil)
But even the method "observeValueForKeyPath" does not get fired in my iPhone test case.

Check out the Apple docs here
and scroll to the "Key-Value Observing" section. Especially #3 in that section.
It helped me get my implementation to work. My resulting code looks like this:
//Global
var player = AVPlayer()
func setUpPlayer() {
//...
// Setting up your player code goes here
self.player.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions(), context: nil)
//...
}
// catch changes to status
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
print("obsrved")
}

I could not make it work with adding an observer on the currentItem as user #gabbler suggested.
However it helped using the notification center like this:
NSNotificationCenter.defaultCenter().addObserverForName(
AVPlayerItemFailedToPlayToEndTimeNotification,
object: nil,
queue: nil,
usingBlock: { notification in
self.stop()
})
Note that stop() is a method in the same class which stops the stream as if a stop button were clicked.

Related

AVCapture​Photo​Output isFlashScene Key-Value-Observing

I am following Apple's latest sample code AVCam Swift, which is updated to use AVCapture​Photo​Output.
var isFlashScene: Bool { get }
A Boolean value indicating whether the scene currently being previewed
by the camera warrants use of the flash. This property’s value changes
depending on the scene currently visible to the camera. For example,
you might use this property to highlight the flash control in your
app’s camera UI, indicating to the user that the scene is dark enough
that enabling the flash might be desirable. If the photo capture
output’s supportedFlashModes value is off, this property’s value is
always false. This property supports Key-value observing.
I am trying to Key-value observe this so when Auto Flash Mode indicates that this is a scene that flash will fire (Just like the stock iOS Camera App), So I can change the UI, just like the documentation notes.
So I set it up like this:
private let photoOutput = AVCapturePhotoOutput()
private var FlashSceneContext = 0
self.addObserver(self, forKeyPath: "photoOutput.isFlashScene", options: .new, context: &FlashSceneContext)
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == & FlashSceneContext {
print ("Flash Scene Changed")
}
}
Above never shows a change. Even if I put a log in to check
print (self.photoOutput.isFlashScene)
This comes out as False all the time though out the app.
I also tried :
self.photoOutput.addObserver(self, forKeyPath: "isFlashScene", options: .new, context: &FlashSceneContext)
.... still no change is Flash Scene, it's stuck on False.
self.photoOutput.addObserver(self, forKeyPath: "isFlashScene", options: .new, context: &FlashSceneContext)
Above was the proper way to setup the KVO.
photoSettingsForSceneMonitoring has to be implemented:
let photoSettings = AVCapturePhotoSettings()
photoSettings.flashMode = .auto
photoSettings.isAutoStillImageStabilizationEnabled = true
self.photoOutput.photoSettingsForSceneMonitoring = photoSettings
Works!

Crash on volume button press AVAudioPlayer

My app crashes when I tap either volume button. In my view controller, I am calling setActive on AVAudioSession.sharedInstance() and when the user taps a button I play a song with AVAudioPlayer. In this view controller, whenever a volume button is pressed, wether the player is playing or not, the app crashes with EXC_BAD_ACCESS. I have seen an error message in the debugger occasionally complaining about key-value observing for outputVolume.
Any ideas why my app is crashing?
A CLUE: There are two ways I can get to the view controller that is causing the crash. One way causes the crash and the other does not. Either way I am pushing the view controller on to the navigation controller in the same way.
Have you added something similar? :
let audioSession = AVAudioSession.sharedInstance()
audioSession.addObserver(self, forKeyPath: "outputVolume",
options: NSKeyValueObservingOptions.new, context: nil)
if you did, then you need to override this method and do whatever you need in it :
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "outputVolume"{
}
}

Detect if AVPlayerViewController is playing video or buffering and add overlay to the player

I have to detect whether the video is in playing or buffering mode.I am loading the video from a URL. I have tried the below code and I am able to track after once the video has started playing, but not when it is in buffering state.
Also, I want to add an overlay view in my player. I have tried to add the overlay in AVPlayer but when in full screen mode the overlay disappears.
Need some suggestions. Thanks in advance. Below is my code:
let playerAV = AVPlayerViewController()
var player = AVPlayer()
player = AVPlayer(URL: url)
print(url)
playerAV.player = player
playerAV.view.frame = CGRectMake(0, 0, self.videoView.frame.width, self.videoView.frame.height)
self.addChildViewController(playerAV)
self.videoView.addSubview(playerAV.view)
playerAV.didMoveToParentViewController(self)
playerAV.player?.play()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ChannelDetailViewController.notificationObserver(_:)), name:AVPlayerItemDidPlayToEndTimeNotification , object: player.currentItem)
_ = UIDevice.beginGeneratingDeviceOrientationNotifications
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ChannelDetailViewController.deviceOrientationDidChange(_:)) , name:
UIDeviceOrientationDidChangeNotification, object: nil)
player.addObserver(self, forKeyPath: "rate", options: NSKeyValueObservingOptions.New, context: nil)
player.addPeriodicTimeObserverForInterval(CMTime(value: 1, timescale: 3), queue: dispatch_get_main_queue()) { [weak self] time in
self?.handlePlayerStatus(time)
}
func handlePlayerStatus(time: CMTime) {
if player.status == .ReadyToPlay {
// buffering is finished, the player is ready to play
print("playing")
}
if player.status == .Unknown{
print("Buffering")
}
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if keyPath == "rate" {
if let rate = change?[NSKeyValueChangeNewKey] as? Float {
if player.currentItem!.status == AVPlayerItemStatus.ReadyToPlay{
if rate != 0 && player.error == nil {
print("normal playback")
}
else{
print("playback stopped")
}
}else if player.currentItem?.status == AVPlayerItemStatus.Unknown{
print("test")
}
}
}
print("you are here")
}
check here for project
Check my answer: https://stackoverflow.com/a/38867386/5109911, this shows you how check if the player is loading the buffer, to check if it is ready to play you have to check both player.currentItem.status and player.status
To add an overlay to AVPlayer I suggest using an UIView above your AVPlayer layer.
I took a slightly different approach to a similar problem - updating the UI when the AVPlayer starts actual playback (in our case, it was audio playback, but the same principle would apply).
We used a boundary time to execute a block at the start of playback – specifically after 1/3 of a second of playback – to update the UI at the same time as audible playback:
let times = [NSValue(time:CMTimeMake(1,3))]
_ = self.player.addBoundaryTimeObserver(forTimes: times, queue: DispatchQueue.main, using: {
[weak self] time in
// Code to update the UI goes here, e.g.
self?.someUIView.isHidden = false
})
I have a little more detail on the approach (and a sample Xcode project) on our company blog: http://www.artermobilize.com/blog/2017/02/09/detect-when-ios-avplayer-finishes-buffering-using-swift/
As to question #2, I concur with Marco's suggestion of using an UIView overtop of the AVPlayer layer.

Progress of playbackLikelyToKeepUp

When using an AVPlayer, is there a way to get the progress of playbackLikelyToKeepUp? I was thinking I could look at loadedTimeRanges to see how much has been buffered so far, but from what I understand, the playbackLikelyToKeepUp property is some internally made prediction and does not provide a value of how much data is needed for it to be true.
To put this into perspective, what I'm trying to do is to have a progress view that reaches 100% just as the video starts playing.
To begin playing when AVPlayer will play continuously, you can observe the Key-Value changes on the playbackLikelyToKeepUp, like this:
let PlayerKeepUp = "playbackLikelyToKeepUp"
var isPlayerReady:Bool = false
and then in your Initialiser you add:
// adding the Observers for Status:
self.player?.currentItem?.addObserver(self, forKeyPath: PlayerKeepUp, options: ([NSKeyValueObservingOptions.New, NSKeyValueObservingOptions.Old]), context: &PlayerItemObserverContext)
And Finally, to track when the player is ready to play without getting stuck:
// MARK: KVO Observing Methods:
override public func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
switch (keyPath, context) {
case (PlayerKeepUp as String, &PlayerItemObserverContext):
if(self.player.currentItem?.playbackLikelyToKeepUp == true) {
self.isPlayerReady = true
// HERE YOU FILL UP YOUR PROGRESS VIEW :-)
self.delegate?.playerReady(self.playerURL! as String)
}
break
default:
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
Last but not least, remember to Remove your KVO observance or else you'll crash when de-allocating the player:
deinit {
// Remove observer:
self.player?.currentItem?.removeObserver(self, forKeyPath: PlayerKeepUp, context: &PlayerItemObserverContext)
}
Hope this helps :-)

AVPlayer override video controls

I need to be notified when a control button (on a video) is pressed. For example if I tap on the "pause" or on "full scren" button I need to implement some logic. Can I override methods of AVPlayerViewController? I found AVPlayerViewControllerDelegate but I can't find any methods to override.
I also tried to add an observer to the AVPlayer
player.addObserver(self, forKeyPath: "status", options:NSKeyValueObservingOptions(), context: nil)
and I used:
override func observeValueForKeyPath(keyPath: String,
ofObject object: AnyObject, change: [String : AnyObject],
context: UnsafeMutablePointer<Void>) {
...
}
but I get a notification only when the video is played: this method isn't called if I tap on a control button.
Thanks
keypaths are different, for swift, to check play/pause after clicked:
player .addObserver(self, forKeyPath: "rate", options: NSKeyValueObservingOptions.New, context: nil)
and in
observeValueForKeyPath
check like this
if ((change!["new"] as! Int) == 1)
^ this returns if video played or paused for true/false cases
AVPlayer emit an NSNotification when pause is pressed. AVPlayerDidPauseNotification (or something like that). You can register a listener in the NSNotificationCenter for this notification.

Resources