Timeline Progress bar for AVPlayer

生来就可爱ヽ(ⅴ<●) 提交于 2019-11-29 19:59:24
iOSPawan

Please use the below code which is from apple example code "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);
}

And in syncScrubber method update the UISlider or UIProgressBar value.

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

Thanks to iOSPawan for the code! I simplified the code to the necessary lines. This might be more clear to understand the concept. Basically I have implemented it like this and it works fine.

Before starting the video:

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

[_player play];

Then you need to have a method to update your progress bar:

- (void)updateProgressBar
{
    double duration = CMTimeGetSeconds(_playerItem.duration);
    double time = CMTimeGetSeconds(_player.currentTime);
    _progressView.progress = (CGFloat) (time / duration);
}
    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))
    }

In my case, the following code works 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!)   
}

for timeline i do this

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

And import CoreMedia framework

lengthSlider is UISlider

Swifty answer to get progress:

private func addPeriodicTimeObserver() {
        // Invoke callback every half second
        let interval = CMTime(seconds: 0.5,
                              preferredTimescale: CMTimeScale(NSEC_PER_SEC))
        // Queue on which to invoke the callback
        let mainQueue = DispatchQueue.main
        // Add time observer
        self.playerController?.player?.addPeriodicTimeObserver(forInterval: interval, queue: mainQueue) { [weak self] time in
            let currentSeconds = CMTimeGetSeconds(time)
            guard let duration = self?.playerController?.player?.currentItem?.duration else { return }
            let totalSeconds = CMTimeGetSeconds(duration)
            let progress: Float = Float(currentSeconds/totalSeconds)
            print(progress)
        }
    }

Ref

I took the answers from the iOSPawan and Raphael and then adapted to my needs. So I have music and UIProgressView which is always in loop and when you go to the next screen and come back the the song and the bar continued where they were left.

Code:

@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;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!