ios AVPlayer 的时间轴进度条

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8725505/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 16:08:52  来源:igfitidea点击:

Timeline Progress bar for AVPlayer

iphoneiosavfoundationavplayeruiprogressview

提问by Sanjeev Rao

AVPlayeris fully customizable, unfortunately there are convenient methods in AVPlayerfor showing the time line progress bar.

AVPlayer是完全可定制的,不幸的是有一些方便的方法AVPlayer来显示时间线进度条。

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

I have an progress bar that indicates the how video has been played, and how much remained just as like MPMoviePlayer.

我有一个进度条,指示视频是如何播放的,还有多少与MPMoviePlayer.

So how to get the timeline of video from AVPlayerand how to update the progress bar

那么如何从中获取视频的时间线AVPlayer以及如何更新进度条

Suggest me.

建议我。

回答by iOSPawan

Please use the below code which is from apple example code "AVPlayerDemo".

请使用以下来自苹果示例代码“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.

并在 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];
    }
} 

回答by 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.

感谢 iOSPawan 的代码!我将代码简化为必要的行。这可能更清楚地理解这个概念。基本上我已经像这样实现了它并且它工作正常。

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

回答by amleszk

    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))
    }

回答by Harman

In my case, the following code works Swift 3:

就我而言,以下代码适用于 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!)   
}

回答by Zaid Pathan

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

参考

回答by Igor Bidiniuc

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

并导入 CoreMedia 框架

lengthSlider is UISlider

lengthSlider 是 UISlider

回答by Tiago Mendes

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.

我从 iOSPawan 和 Raphael 那里得到了答案,然后根据我的需要进行了调整。所以我有音乐和 UIProgressView,它总是在循环中,当你转到下一个屏幕并回来时,歌曲和酒吧继续在他们离开的地方。

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