2012-01-04 22 views
14

AVPlayer es completamente personalizable, desafortunadamente hay métodos convenientes en AVPlayer para mostrar la barra de progreso de la línea de tiempo.Barra de progreso de línea de tiempo para AVPlayer

AVPlayer *player = [AVPlayer playerWithURL:URL]; 
AVPlayerLayer *playerLayer = [[AVPlayerLayer playerLayerWithPlayer:avPlayer] retain];[self.view.layer addSubLayer:playerLayer]; 

que tienen una barra de progreso que indica el cómo se ha desempeñado vídeo, y cuánto se mantuvo igual que como MPMoviePlayer.

Entonces, ¿cómo llegar a la línea de tiempo de vídeo desde AVPlayer y cómo actualizar la barra de progreso

me sugieren.

+1

Por favor, considere el uso de AVPlayerViewController. Es muy fácil de hacer la reproducción (pero puede que no se ajuste a sus necesidades). Solo digo en caso de que no lo sepas. (editar) - Vaya, esto es hace tres años: P. –

Respuesta

21

Utilice el código de abajo que es de código de ejemplo de manzana "AVPlayerDemo".

double interval = .1f; 

    CMTime playerDuration = [self playerItemDuration]; // return player duration. 
    if (CMTIME_IS_INVALID(playerDuration)) 
    { 
     return; 
    } 
    double duration = CMTimeGetSeconds(playerDuration); 
    if (isfinite(duration)) 
    { 
     CGFloat width = CGRectGetWidth([yourSlider bounds]); 
     interval = 0.5f * duration/width; 
    } 

    /* Update the scrubber during normal playback. */ 
    timeObserver = [[player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(interval, NSEC_PER_SEC) 
                  queue:NULL 
                usingBlock: 
                 ^(CMTime time) 
                 { 
                  [self syncScrubber]; 
                 }] retain]; 


- (CMTime)playerItemDuration 
{ 
    AVPlayerItem *thePlayerItem = [player currentItem]; 
    if (thePlayerItem.status == AVPlayerItemStatusReadyToPlay) 
    {   

     return([playerItem duration]); 
    } 

    return(kCMTimeInvalid); 
} 

Y en el método syncScrubber, actualice el valor de UISlider o UIProgressBar.

- (void)syncScrubber 
{ 
    CMTime playerDuration = [self playerItemDuration]; 
    if (CMTIME_IS_INVALID(playerDuration)) 
    { 
     yourSlider.minimumValue = 0.0; 
     return; 
    } 

    double duration = CMTimeGetSeconds(playerDuration); 
    if (isfinite(duration) && (duration > 0)) 
    { 
     float minValue = [ yourSlider minimumValue]; 
     float maxValue = [ yourSlider maximumValue]; 
     double time = CMTimeGetSeconds([player currentTime]); 
     [yourSlider setValue:(maxValue - minValue) * time/duration + minValue]; 
    } 
} 
+0

¿para qué es la variable timeObserver? –

+0

necesita timeObserver para cancelar la 'observación' y también necesita retenerla, siempre que quiera observar ...Puedes leer el docu de apple sobre addPeriodicTimeObserverForInterval: queue: usingBlock: –

1

para la línea de tiempo que hago esto

-(void)changeSliderValue { 

double duration = CMTimeGetSeconds(self.player.currentItem.duration); 

[lengthSlider setMaximumValue:(float)duration]; 

lengthSlider.value = CMTimeGetSeconds([self.player currentTime]); 

int seconds = lengthSlider.value,minutes = seconds/60,hours = minutes/60; 

int secondsRemain = lengthSlider.maximumValue - seconds,minutesRemain = secondsRemain/60,hoursRemain = minutesRemain/60; 

seconds = seconds-minutes*60; 

minutes = minutes-hours*60; 

secondsRemain = secondsRemain - minutesRemain*60; 

minutesRemain = minutesRemain - hoursRemain*60; 

NSString *hourStr,*minuteStr,*secondStr,*hourStrRemain,*minuteStrRemain,*secondStrRemain; 

hourStr = hours > 9 ? [NSString stringWithFormat:@"%d",hours] : [NSString stringWithFormat:@"0%d",hours]; 

minuteStr = minutes > 9 ? [NSString stringWithFormat:@"%d",minutes] : [NSString stringWithFormat:@"0%d",minutes]; 

secondStr = seconds > 9 ? [NSString stringWithFormat:@"%d",seconds] : [NSString stringWithFormat:@"0%d",seconds]; 

hourStrRemain = hoursRemain > 9 ? [NSString stringWithFormat:@"%d",hoursRemain] : [NSString stringWithFormat:@"0%d",hoursRemain]; 

minuteStrRemain = minutesRemain > 9 ? [NSString stringWithFormat:@"%d",minutesRemain] : [NSString stringWithFormat:@"0%d",minutesRemain]; 

secondStrRemain = secondsRemain > 9 ? [NSString stringWithFormat:@"%d",secondsRemain] : [NSString stringWithFormat:@"0%d",secondsRemain]; 

timePlayed.text = [NSString stringWithFormat:@"%@:%@:%@",hourStr,minuteStr,secondStr]; 

timeRemain.text = [NSString stringWithFormat:@"-%@:%@:%@",hourStrRemain,minuteStrRemain,secondStrRemain]; 

e importación marco CoreMedia

lengthSlider es UISlider

+0

estoy reproduciendo canciones de url. pero en CMTime duración y CMTime currentTime estoy obteniendo 0. ¿cuál es el problema aquí para el valor 0? ¿Conoces la solución? – Moxarth

+0

aquí hay documentación sobre esto: https://developer.apple.com/documentation/avfoundation/avplayeritem/1389386-duration –

+0

ese código de duración tampoco me ayudó. – Moxarth

16

¡Gracias a iOSPawan por el código! Simplifiqué el código a las líneas necesarias. Esto podría ser más claro para entender el concepto. Básicamente lo he implementado así y funciona bien.

Antes de iniciar el vídeo:

__weak NSObject *weakSelf = self;  
[_player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(1.0/60.0, NSEC_PER_SEC) 
             queue:NULL 
           usingBlock:^(CMTime time){ 
               [weakSelf updateProgressBar]; 
              }]; 

[_player play]; 

A continuación, es necesario tener un método para actualizar la barra de progreso:

- (void)updateProgressBar 
{ 
    double duration = CMTimeGetSeconds(_playerItem.duration); 
    double time = CMTimeGetSeconds(_player.currentTime); 
    _progressView.progress = (CGFloat) (time/duration); 
} 
+0

Estás mal usando __block aquí. __block se usa si es necesario usar la asignación a la variable dentro del bloque. Probablemente quisiste usar __weak – kball

+0

Has actualizado tu código. Pero además, debe mantener una referencia al objeto devuelto por addPeriodicTimeObserverForInterval: para que continúe enviando el mensaje y para que pueda eliminarlo más tarde. https://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVPlayer_Class/#//apple_ref/occ/instm/AVPlayer/addPeriodicTimeObserverForInterval:queue:usingBlock: – kball

+0

timeObserver debe eliminarse después de dejar de reproducir '@result \t Un objeto que se ajusta al protocolo NSObject. Debe conservar este valor devuelto siempre que desee que el jugador invoque el observador de tiempo. \t Pase este objeto a -removeTimeObserver: para cancelar la observación de tiempo. ' – Leo

4
let progressView = UIProgressView(progressViewStyle: UIProgressViewStyle.Bar) 
    self.view.addSubview(progressView) 
    progressView.constrainHeight("\(1.0/UIScreen.mainScreen().scale)") 
    progressView.alignLeading("", trailing: "", toView: self.view) 
    progressView.alignBottomEdgeWithView(self.view, predicate: "") 
    player.addPeriodicTimeObserverForInterval(CMTimeMakeWithSeconds(1/30.0, Int32(NSEC_PER_SEC)), queue: nil) { time in 
     let duration = CMTimeGetSeconds(playerItem.duration) 
     progressView.progress = Float((CMTimeGetSeconds(time)/duration)) 
    } 
1

En mi caso, el código siguiente funciona Swift 3:

var timeObserver: Any? 
override func viewDidLoad() { 
    ........ 
    let interval = CMTime(seconds: 0.05, preferredTimescale: CMTimeScale(NSEC_PER_SEC)) 
    timeObserver = avPlayer.addPeriodicTimeObserver(forInterval: interval, queue: DispatchQueue.main, using: { elapsedTime in 
      self.updateSlider(elapsedTime: elapsedTime)  
     }) 
} 

func updateSlider(elapsedTime: CMTime) { 
    let playerDuration = playerItemDuration() 
    if CMTIME_IS_INVALID(playerDuration) { 
     seekSlider.minimumValue = 0.0 
     return 
    } 
    let duration = Float(CMTimeGetSeconds(playerDuration)) 
    if duration.isFinite && duration > 0 { 
     seekSlider.minimumValue = 0.0 
     seekSlider.maximumValue = duration 
     let time = Float(CMTimeGetSeconds(elapsedTime)) 
     seekSlider.setValue(time, animated: true) 
    } 
} 

private func playerItemDuration() -> CMTime { 
    let thePlayerItem = avPlayer.currentItem 
    if thePlayerItem?.status == .readyToPlay { 
     return thePlayerItem!.duration 
    } 
    return kCMTimeInvalid 
} 

override func viewDidDisappear(_ animated: Bool) { 
    super.viewDidDisappear(animated) 
    avPlayer.removeTimeObserver(timeObserver!) 
} 
0

Tomé las respuestas f del iOSPawan y Raphael y luego se adaptó a mis necesidades. Así que tengo música y UIProgressView, que siempre está en loop, y cuando pasas a la siguiente pantalla y regresas, la canción y la barra continúan donde se quedaron.

Código:

@interface YourClassViewController(){ 

    NSObject * periodicPlayerTimeObserverHandle; 
} 
@property (nonatomic, strong) AVPlayer *player; 
@property (nonatomic, strong) UIProgressView *progressView; 

-(void)viewWillAppear:(BOOL)animated{ 
    [super viewWillAppear:animated]; 

    if(_player != nil && ![self isPlaying]) 
    { 
     [self musicPlay]; 
    } 
} 


-(void)viewWillDisappear:(BOOL)animated 
{ 
    [super viewWillDisappear:animated]; 

    if (_player != nil) { 

     [self stopPlaying]; 
    } 
} 

// ---------- 
//  PLAYER 
// ---------- 

-(BOOL) isPlaying 
{ 
    return ([_player rate] > 0); 
} 

-(void) musicPlay 
{ 
    [[NSNotificationCenter defaultCenter] addObserver:self 
              selector:@selector(playerItemDidReachEnd:) 
               name:AVPlayerItemDidPlayToEndTimeNotification 
               object:[_player currentItem]]; 

    __weak typeof(self) weakSelf = self; 
    periodicPlayerTimeObserverHandle = [_player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(1.0/60.0, NSEC_PER_SEC) 
                      queue:NULL 
                     usingBlock:^(CMTime time){ 
                      [weakSelf updateProgressBar]; 
                     }]; 
    [_player play]; 
} 


-(void) stopPlaying 
{ 
    @try { 

     if(periodicPlayerTimeObserverHandle != nil) 
     { 
      [_player removeTimeObserver:periodicPlayerTimeObserverHandle]; 
      periodicPlayerTimeObserverHandle = nil; 
     } 

     [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil]; 
     [_player pause]; 
    } 
    @catch (NSException * __unused exception) {} 
} 


-(void) playPreviewSong:(NSURL *) previewSongURL 
{ 
    [self configureAVPlayerAndPlay:previewSongURL]; 
} 


-(void) configureAVPlayerAndPlay: (NSURL*) url { 

    if(_player) 
     [self stopPlaying]; 

    AVAsset *audioFileAsset = [AVURLAsset URLAssetWithURL:url options:nil]; 
    AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:audioFileAsset]; 
    _player = [AVPlayer playerWithPlayerItem:playerItem]; 
    [_player addObserver:self forKeyPath:@"status" options:0 context:nil]; 

    CRLPerformBlockOnMainThreadAfterDelay(^{ 
     NSError *loadErr; 
     if([audioFileAsset statusOfValueForKey:@"playable" error:&loadErr] == AVKeyValueStatusLoading) 
     { 
      [audioFileAsset cancelLoading]; 
      [self stopPlaying]; 
      [self showNetworkError:NSLocalizedString(@"Could not play file",nil)]; 
     } 
    }, NETWORK_REQUEST_TIMEOUT); 
} 


- (void)updateProgressBar 
{ 

    double currentTime = CMTimeGetSeconds(_player.currentTime); 
    if(currentTime <= 0.05){ 
     [_progressView setProgress:(float)(0.0) animated:NO]; 
     return; 
    } 

    if (isfinite(currentTime) && (currentTime > 0)) 
    { 
     float maxValue = CMTimeGetSeconds(_player.currentItem.asset.duration); 
     [_progressView setProgress:(float)(currentTime/maxValue) animated:YES]; 
    } 
} 


-(void) showNetworkError:(NSString*)errorMessage 
{ 
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"No connection", nil) message:errorMessage preferredStyle:UIAlertControllerStyleAlert]; 
    [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { 
     // do nothing 
    }]]; 

    [self presentViewController:alert animated:YES completion:nil]; 
} 


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 

    if (object == _player && [keyPath isEqualToString:@"status"]) { 
     if (_player.status == AVPlayerStatusFailed) { 
      [self showNetworkError:NSLocalizedString(@"Could not play file", nil)]; 
     } else if (_player.status == AVPlayerStatusReadyToPlay) { 
      NSLog(@"AVPlayerStatusReadyToPlay"); 
      [TLAppAudioAccess setAudioAccess:TLAppAudioAccessType_Playback]; 
      [self musicPlay]; 

     } else if (_player.status == AVPlayerItemStatusUnknown) { 
      NSLog(@"AVPlayerItemStatusUnknown"); 
     } 
    } 
} 


- (void)playerItemDidReachEnd:(NSNotification *)notification { 

    if ([notification.object isEqual:self.player.currentItem]) 
    { 
     [self.player seekToTime:kCMTimeZero]; 
     [self.player play]; 
    } 
} 


-(void) dealloc{ 

    @try { 
     [_player removeObserver:self forKeyPath:@"status"]; 
    } 
    @catch (NSException * __unused exception) {} 
    [self stopPlaying]; 
    _player = nil; 
}