Scrubber (UISlider) in AVPlayer?

avfoundation, avplayer, ios

Solution

I guess the problem is, that your scrubber is still updating from the video, while you are using the seekbar. Implement it in a way, that you pause the player during the scrubbing, and you won't have this bug anymore. Checkout my solution:

The function to update your player:

- (IBAction)seekbarAction:(UISlider *)sender {
    CMTime videoLength = playerItem1.duration;  //gets the video duration
    float videoLengthInSeconds = videoLength.value/videoLength.timescale; //transfers the CMTime duration into seconds

    [player1 seekToTime:CMTimeMakeWithSeconds(videoLengthInSeconds*sender.value, 1)];
}

And another seekbar action with "Touch down" in order to pause the video:

- (IBAction)pauseSeek:(id)sender {
    [player1 pause];
}

And another seekbar action with "Touch up" in order to resume the video when you release the scrubber. Hope this helps.

Problem

When you play remote video via `AVPlayer` and start rewinding, the scrubber is buggy. I'm doing a player based on this Apple example. How to implement it smoothly? Code from my project follows - https://github.com/nullproduction/Player ``` - (void)initScrubberTimer { double interval = .1f; CMTime playerDuration = [self playerItemDuration]; if (CMTIME_IS_INVALID(playerDuration)) { return; } double duration = CMTimeGetSeconds(playerDuration); if (isfinite(duration)) { CGFloat width = CGRectGetWidth([scrubberSlider bounds]); interval = 0.5f * duration / width; } __weak id weakSelf = self; CMTime intervalSeconds = CMTimeMakeWithSeconds(interval, NSEC_PER_SEC); mTimeObserver = [self.player addPeriodicTimeObserverForInterval:intervalSeconds queue:dispatch_get_main_queue() usingBlock:^(CMTime time) { [weakSelf syncScrubber]; }]; } - (void)syncScrubber { CMTime playerDuration = [self playerItemDuration]; if (CMTIME_IS_INVALID(playerDuration)) { scrubberSlider.minimumValue = 0.0; return; } double duration = CMTimeGetSeconds(playerDuration); if (isfinite(duration)) { float minValue = [scrubberSlider minimumValue]; float maxValue = [scrubberSlider maximumValue]; double time = CMTimeGetSeconds([self.player currentTime]); [scrubberSlider setValue:(maxValue - minValue) * time / duration + minValue]; } } - (IBAction)beginScrubbing:(id)sender { mRestoreAfterScrubbingRate = [self.player rate]; [self.player setRate:0.f]; [self removePlayerTimeObserver]; } - (IBAction)scrub:(id)sender { if ([sender isKindOfClass:[UISlider class]]) { UISlider* slider = sender; CMTime playerDuration = [self playerItemDuration]; if (CMTIME_IS_INVALID(playerDuration)) { return; } double duration = CMTimeGetSeconds(playerDuration); if (isfinite(duration)) { float minValue = [slider minimumValue]; float maxValue = [slider maximumValue]; float value = [slider value]; double time = duration * (value - minValue) / (maxValue - minValue); [self.player seekToTime:CMTimeMakeWithSeconds(time, NSEC_PER_SEC)]; } } } - (IBAction)endScrubbing:(id)sender { if (!mTimeObserver) { CMTime playerDuration = [self playerItemDuration]; if (CMTIME_IS_INVALID(playerDuration)) { return; } double duration = CMTimeGetSeconds(playerDuration); if (isfinite(duration)) { CGFloat width = CGRectGetWidth([scrubberSlider bounds]); double tolerance = 0.5f * duration / width; __weak id weakSelf = self; CMTime intervalSeconds = CMTimeMakeWithSeconds(tolerance, NSEC_PER_SEC); mTimeObserver = [self.player addPeriodicTimeObserverForInterval:intervalSeconds queue:dispatch_get_main_queue() usingBlock: ^(CMTime time) { [weakSelf syncScrubber]; }]; } } if (mRestoreAfterScrubbingRate) { [self.player setRate:mRestoreAfterScrubbingRate]; mRestoreAfterScrubbingRate = 0.f; } } ```

Original source