2013-02-28 12 views
7

Ho creato un dispositivo di scorrimento personalizzato per mostrare lo stato di avanzamento di una traccia musicale e consentire lo scrubbing all'interno della traccia.UISlider personalizzato come l'app Musica

Entrambe funzionano bene, ma c'è un leggero ritardo (e un movimento nervoso) una volta che il trascinamento si è fermato e il cursore è riposizionato - Il dispositivo di scorrimento dell'app Apple Music è senza interruzioni.

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

Lo 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 
+0

controllare questo componenti, sarà più facile lavorare con http://www.cocoacontrols.com/search?utf8=%E2%9C%93&q=music –

+1

Ho avuto lo stesso problema. Non sono mai riuscito a risolverlo completamente, ma il seguente ha aiutato: 1. Non impostare currentPlaybackTime se il valore non è stato modificato 2. Non impostare currentPlaybackTime ogni modifica: memorizzare il valore in una variabile di istanza e impostarlo tramite un timer solo poche volte al secondo. Tuttavia, non è mai stato fluido come il cursore di Apple. – EricS

+0

si potrebbe provare 1. aggiungere un leggero ritardo prima di impostare _isScrubbing = NO e 2). prova ad animare il cambio di valore invece di impostare _scrubberSlider.value direttamente –

risposta

2

Dopo un sacco di registrazione - ho scoperto che la componente UISlider ha avuto un leggero ritardo aggiornamento stesso - tra 1/10 e 3/10s di secondo - che ha causato il salto.

ho anche cambiato l'intervallo del timer a 0,5 secondi e ho anche aiutato l'aggiornamento con l'aggiunta di una variabile savedValue all'interno ScrubberSlider e BOOL hasFinishedMoving, quindi i beginTrackingWithTouchendTrackingWithTouch metodi all'interno ScrubberSlider assomiglia a questo:

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

e cambiato il updatePosition includere questi VAR e convalide:

-(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; 
     } 
    } 
} 
3

personalizzato Forse si sta andando su questo nel modo sbagliato. Invece di cercare di sincronizzarsi esattamente con il tempo della traccia (che porta ad alcune condizioni di gara piuttosto brutte, ed è proprio il motivo per cui il tuo slider non si aggiorna correttamente quando viene "lanciato" piuttosto che trascinato, trattenuto, quindi rilasciato), prova a usa il timer che hai come "tick". È possibile ridurre la velocità di fuoco fino a, diciamo, una volta ogni mezzo secondo, quindi utilizzare fondamentalmente la stessa logica che si ha ora, ma invece di cercare di ottenere un valore frazionario esatto in -updatePosition, si "spunta" lo slider in avanti.

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

Non è perfetto, ma poi di nuovo, è molto più efficiente e senza soluzione di continuità che cercare di sincronizzare te stesso per i tempi di un brano musicale con NSTimer (che può essere disgustosamente impreciso per movimenti precisi). Questo richiede anche che il valore massimo del tuo cursore essere equivalente al valore massimo della traccia, quindi un metodo di esempio per avviare la riproduzione dovrebbe includere qualcosa di simile:

- (IBAction)startPlayback:(id)sender { 
    //... Handle however you wish to play the track 
    _scrubberSlider.maximumValue = [[_theMusicPlayer.nowPlayingItem valueForKey:MPMediaItemPropertyPlaybackDuration] floatValue]; 
    [self handleTrackTime]; 
} 
1

dell'aggiornamento timer continuativamente sul cursore di trascinamento (senza jum movimento py) dovremmo minimizzare il valore di scheduleTimerWithTimeInterval. Quindi, se il vostro valore del timer è qualcosa di simile: -

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

provare una piccola modifica, riducendo al minimo valore scheduledTimerWithTimeInterval

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



-(void)updateElapsedTime { 

    //do your stuffs here 
} 
2

Il AVFoundation framework ha un metodo chiamato addPeriodicTimeObserverForInterval:queue:usingBlock: che funziona molto bene con il AVPlayer. C'è uno AVPlayer demo che ha uno scrubbing molto fluido. Non sono riuscito a trovare un metodo simile per AVAudioPlayer, ma forse lo AVPlayer demo ti darà alcune idee su come ottenere uno scrubbing uniforme.

Parte del problema è trovare l'intervallo di tempo giusto, che sono sicuro che lo AVPlayer demo può aiutarti. Se devi usare un timer, probabilmente è meglio usare uno CADisplayLink (invece di NSTimer) poiché è più preciso quando si tratta di aggiornare la visualizzazione di qualcosa sullo schermo.

Sembra che tu stia utilizzando lo MPMusicPlayerController. Se stai facendo i tuoi controlli, probabilmente stai meglio con AVAudioPlayer o AVPlayer.