Hi I am working on application which contains local video streaming. I am searching for callback or notification when user is scrubbing in paused state. I have already added observers for AVPlayer as follows :
[self.avPlayerController.player addObserver:self forKeyPath:@"status" options:0 context:NULL];
[self.avPlayerController.player addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionOld context:NULL];
But the observers are not called when player is paused and user scrubbing. I just want to detect when user finished scrubbing or when user touches progress bar. Any help will be appreciated. Thanks.
Observing rate
and status
should work - the observing callbacks are called in my code, therefore I assume they work for you as well. I guess the problem is that they get called only when the video is paused, not when the users scrubs it. So I will answer to that.
What you would probably want to observe would be currentTime
. I tried it myself, and the KVO are not called for it neither when I try to observe AVPlayer
or its currentItem
. I have done a bit of research, and it seems that this simply won't work (see this SO question). You have to find another way to do it. One way is to add a timer and periodically check the state of it. Combined with observing rate
or timeControlStatus
(see this answer), you should be able to tell that the currentTime is being changed by the user even when the video is paused.
I hope you don't mind reading Swift code, this would be the first approach:
class ViewController: UIViewController {
let player = AVPlayerViewController()
var timer: Timer? = nil
var previousTime: Double = 0
var stopped = false
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
present(player, animated: true, completion: {
self.player.player = AVPlayer(url: URL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")!)
self.player.player?.play()
self.player.player?.addObserver(self, forKeyPath: "rate", options: .new, context: nil)
self.timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { (timer) in
let currentTime = self.player.player?.currentTime().seconds ?? 0
if self.previousTime != currentTime && self.stopped {
print(">>> User scrubbing")
}
self.previousTime = currentTime
})
})
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "rate" && (change?[NSKeyValueChangeKey.newKey] as? Float) == 0 {
print(">>> stopped")
stopped = true
} else if keyPath == "rate" && (change?[NSKeyValueChangeKey.newKey] as? Float) == 1 {
print(">>> played again")
stopped = false
}
}
}
Second approach, one that I would prefer, is subclassing AVPlayer and overriding its seek(to:toleranceBefore:toleranceAfter:completionHandler:)
method. This method is called by the AVPlayerViewController
when user scrubs the video - thus at this point you can tell that the user is scrubbing (although it is called also when user scrubs it while the video is played, so might need to test also if it is playing).
class CustomAVPlayer: AVPlayer {
override func seek(to time: CMTime, toleranceBefore: CMTime, toleranceAfter: CMTime, completionHandler: @escaping (Bool) -> Void) {
super.seek(to: time, toleranceBefore: toleranceBefore, toleranceAfter: toleranceAfter, completionHandler: completionHandler)
// if you don't call this method directly, it's called only by the
// AVPlayerViewController's slider during scrubbing
print(">> user scrubbing")
}
}