8

我已经构建了一个自定义滑块来显示音乐曲目的播放进度并允许在曲目内进行擦洗。

两者功能都很好,但是一旦停止拖动并且重新定位滑块,就会出现轻微的延迟(和跳跃的运动) - Apple Music 应用程序滑块是无缝的。

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

自定义滑块

@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
4

4 回答 4

3

也许你正在以错误的方式解决这个问题。与其尝试与轨道的时间完全同步(这会导致一些非常恶劣的比赛条件,这正是为什么您的滑块在“抛出”而不是拖动、按住然后释放时不能正确更新的原因),而是尝试使用您拥有的计时器作为“滴答”。您可以将射速降低到例如每半秒一次,然后使用与现在基本相同的逻辑,但不是试图获得精确的小数值-updatePosition,而是向前“勾选”滑块。

-(void) updatePosition
{
   if (!_scrubberSlider.isScrubbing)
   {
       _scrubberSlider.value += .5f;
   }
}

它并不完美,但话又说回来,它比尝试使用 NSTimer 将自己与音乐曲目的时间同步(这对于精确的动作可能令人作呕的不准确)更加有效和无缝。这还要求滑块的最大值等于轨道的最大值,因此开始播放的示例方法包括:

- (IBAction)startPlayback:(id)sender {
    //... Handle however you wish to play the track
    _scrubberSlider.maximumValue = [[_theMusicPlayer.nowPlayingItem valueForKey:MPMediaItemPropertyPlaybackDuration] floatValue];
    [self handleTrackTime];
}
于 2013-03-03T19:17:34.437 回答
2

经过大量日志记录后 - 我发现 UISlider 组件在更新自身时有轻微的延迟 - 在 1/10 到 3/10 秒之间 - 这导致了跳跃。

我还将计时器间隔更改为 0.5 秒,并通过在savedValue内部添加变量ScrubberSlider和 BOOL来帮助更新hasFinishedMoving,因此 beginTrackingWithTouch endTrackingWithTouchScrubberSlider 中的方法如下所示:

-(BOOL) beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
    [super beginTrackingWithTouch:touch withEvent:event];

    _hasFinishedMoving = NO;

    return YES;
}

- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
    [super endTrackingWithTouch:touch withEvent:event];

    _savedValue = self.value;

   _hasFinishedMoving = YES;
}

并更改updatePosition为包括这些变量和验证:

-(void) updatePosition
{
   if (!_scrubberSlider.tracking) // this is a UISlider BOOL
   {
       if (_scrubberSlider.hasFinishedMoving)
       {
           _scrubberSlider.value = _scrubberSlider.savedValue;
           _scrubberSlider.hasFinishedMoving = NO;
       }
       else
       {
           CGFloat percent = _theMusicPlayer.currentPlaybackTime / [[_theMusicPlayer.nowPlayingItem valueForKey:MPMediaItemPropertyPlaybackDuration] floatValue];

           _scrubberSlider.value = percent;
       }
   }
}
于 2013-03-07T22:38:02.720 回答
2

AVFoundation 框架有一个名为的方法,addPeriodicTimeObserverForInterval:queue:usingBlock:它与AVPlayer. 有一个AVPlayer 演示,它具有非常平滑的擦洗。我找不到类似的方法AVAudioPlayer,但也许AVPlayer 演示会给你一些关于如何实现平滑擦洗的想法。

部分问题在于找到正确的计时器间隔,我相信AVPlayer 演示可以帮助您。如果您必须使用计时器,最好使用 a CADisplayLink(而不是NSTimer),因为在处理更新屏幕上某些内容的显示时这会更准确。

看起来你正在使用MPMusicPlayerController. 如果您正在制作自己的控件,那么使用AVAudioPlayeror可能会更好AVPlayer

于 2013-03-08T18:12:30.407 回答
1

要在滑块拖动时连续更新计时器(不跳动),我们应该最小化 scheduleTimerWithTimeInterval 的值。所以,如果你的计时器值是这样的: -

timer=[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(updateElapsedTime) userInfo:nil repeats:YES];

通过最小化 scheduleTimerWithTimeInterval 值尝试进行小修改

timer=[NSTimer scheduledTimerWithTimeInterval:0.001 target:self selector:@selector(updateElapsedTime) userInfo:nil repeats:YES];



-(void)updateElapsedTime {

     //do your stuffs here
}
于 2013-03-08T07:11:02.473 回答