I've built a custom slider to show the progress of a music track playing and to allow scrubbing within the track.
Both function fine, but there is a slight lag (and jumpy movement) once dragging has stopped and the slider is repositioned - The Apple Music app slider is seamless.
_scrubberSlider = [[ScrubberSlider alloc] initWithFrame:CGRectMake(0, 0, 300, 30)];
_scrubberSlider.continuous = YES;
_scrubberSlider.maximumValue = 1.0f;
_scrubberSlider.minimumValue = 0.0f;
[_scrubberSlider addTarget:self action:@selector(handleSliderMove:) forControlEvents:UIControlEventValueChanged];
- (void) handleSliderMove:(UISlider*)sender
{
CGFloat currentPlayback = sender.value * [[_theMusicPlayer.nowPlayingItem valueForKey:MPMediaItemPropertyPlaybackDuration] floatValue];
_theMusicPlayer.currentPlaybackTime = currentPlayback;
}
-(void) handleTrackTime
{
if (!trackTimer)
{
NSNumber *playBackTime = [NSNumber numberWithFloat: _musicPlayer.currentPlaybackTime];
trackTimer = [NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:@selector(timeIntervalFinished:) userInfo:@{@"playbackTime" : playBackTime} repeats:YES];
}
}
-(void) timeIntervalFinished:(NSTimer*)sender
{
[self playbackTimeUpdated:_musicPlayer.currentPlaybackTime];
}
-(void) playbackTimeUpdated:(CGFloat)playbackTime
{
// Update time label
[self updatePosition];
}
-(void) updatePosition
{
if (!_scrubberSlider.isScrubbing)
{
CGFloat percent = _theMusicPlayer.currentPlaybackTime / [[_theMusicPlayer.nowPlayingItem valueForKey:MPMediaItemPropertyPlaybackDuration] floatValue];
_scrubberSlider.value = percent;
}
}
The custom Slider
@interface ScrubberSlider : UISlider
@property(nonatomic) BOOL isScrubbing;
@end
@implementation ScrubberSlider
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
}
return self;
}
-(BOOL) beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
[super beginTrackingWithTouch:touch withEvent:event];
_isScrubbing = YES;
return YES;
}
- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
[super endTrackingWithTouch:touch withEvent:event];
_isScrubbing = NO;
}
@end
Perhaps you're going about this the wrong way. Instead of trying to be exactly synced to the time of the track (which leads to some pretty nasty race conditions, and is precisely why your slider doesn't update properly when "thrown" rather than dragged, held, then released), try to use the timer you have as a "tick". You can decrease the rate of fire down to, say, once every half a second, then use basically the same logic you have now, but instead of trying to get an exact fractional value in -updatePosition
, you "tick" the slider forward.
-(void) updatePosition
{
if (!_scrubberSlider.isScrubbing)
{
_scrubberSlider.value += .5f;
}
}
It's not perfect, but then again, it's far more efficient and seamless than trying to sync yourself to the timing of a music track with NSTimer (which can be disgustingly inaccurate for precise movements). This also requires that your slider's max value be equivalent to the track's max value, so an example method to start playback would include something like:
- (IBAction)startPlayback:(id)sender {
//... Handle however you wish to play the track
_scrubberSlider.maximumValue = [[_theMusicPlayer.nowPlayingItem valueForKey:MPMediaItemPropertyPlaybackDuration] floatValue];
[self handleTrackTime];
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With