2012-01-04 99 views
14

AVPlayer是完全可定製的,不幸的是在AVPlayer中有方便的方法來顯示時間線進度條。AVPlayer的時間軸進度條

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

我有一個進度條,指示視頻是如何被該打,多少仍只是像MPMoviePlayer

因此,如何從AVPlayer以及如何獲取視頻的時間軸來更新進度條

推薦了我。

+1

請考慮使用AVPlayerViewController。播放非常簡單(但可能不適合您的需求)。只是說,如果你不知道它。 (編輯) - 這是三年前的哎呀:P。 – 2015-05-22 21:57:50

回答

21

請使用下面的代碼是從蘋果的示例代碼 「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); 
} 

而在syncScrubber方法更新UISlider或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

timeObserver的變量是什麼? – 2013-06-14 10:23:31

+0

你需要timeObserver來實際取消'觀察',你也需要保留它,只要你想觀察..您可以閱讀關於addPeriodicTimeObserverForInterval的apple文檔:queue:usingBlock: – 2015-02-11 17:23:06

1

的時間表我這樣做

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

和進口CoreMedia框架

lengthSlider是UISlider

+0

我正在播放來自url的歌曲。但在CMTime持續時間和CMTime currentTime我變得0。這裏的0值有什麼問題?你知道解決方案嗎? – Moxarth 2017-09-19 13:52:42

+0

這裏是關於這個的文檔:https://developer.apple.com/documentation/avfoundation/avplayeritem/1389386-duration – 2017-09-19 14:44:50

+0

那段時間代碼對我也沒有幫助。 – Moxarth 2017-09-20 13:40:04

16

感謝iOSPawan的代碼! 我簡化了代碼到必要的行。理解這個概念可能會更清楚。基本上我已經實現了這樣,它工作正常。

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

[_player play]; 

然後,你需要有一個方法來更新你的進度條:

開始視頻之前

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

您在這裏誤用__block。如果需要使用塊的變量賦值,則使用__block。您可能打算使用__weak – kball 2016-05-26 20:51:16

+0

已更新您的代碼。但另外 - 您需要保留對addPeriodicTimeObserverForInterval返回的對象的引用:以便它繼續發送消息,以便稍後可以將其刪除。 https://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVPlayer_Class/#//apple_ref/occ/instm/AVPlayer/addPeriodicTimeObserverForInterval:queue:usingBlock: – kball 2016-05-26 20:57:28

+0

timeObserver應該在停止播放後刪除'@result \t符合NSObject協議的對象。只要您希望玩家調用時間觀察者,您就必須保留此返回值。 \t傳遞此對象到-removeTimeObserver:取消時間觀察.' – Leo 2017-07-25 07:32:47

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

在我的情況下,下面的代碼工作斯威夫特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

我把答案f從iOSPawan和Raphael開始,然後適應我的需求。 所以我有音樂和UIProgressView,它始終處於循環狀態,當你進入下一個屏幕並返回時,歌曲繼續留在他們的位置。

代碼:

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