1

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.

The iCoder
  • 1,414
  • 3
  • 19
  • 39

1 Answers1

4

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")
    }
}
Milan Nosáľ
  • 19,169
  • 4
  • 55
  • 90
  • I have used second approach thanks it really helped me. – The iCoder Sep 08 '17 at 09:49
  • Can we get when user stops scrubbing from second approach. – The iCoder Sep 12 '17 at 12:31
  • I'm afraid that if you want to detect that the user is touching the slider, and not that she is moving it, that would not be possible by any of the approach I suggested. Both rely on changing the current time, but if the user scrubs, and then hold the finger down still touching the slider, but not moving it, the `seek` method would not get called again until the user moves the slider again.. – Milan Nosáľ Sep 12 '17 at 12:42
  • Now I don't have any specific recommendation for you, but I would probably try to play around with the `AVPlayerViewController` and try to find a way to get access to the slider and add a target on it for touch event, or something similar. Another, but more heavyweight solution would be to implement your own `PlayerViewController` in which you would define your own controls, thus you would have full control over the slider. But that requires a lot of coding (although this way you can be definitely sure that you can achieve your goal). – Milan Nosáľ Sep 12 '17 at 12:45