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; } } ```