Timeline Progress bar for AVPlayer

后端 未结 8 2192
情深已故
情深已故 2020-12-22 22:31

AVPlayer is fully customizable, unfortunately there are convenient methods in AVPlayer for showing the time line progress bar.

AVPl         


        
相关标签:
8条回答
  • 2020-12-22 22:32

    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;
    }
    
    0 讨论(0)
  • 2020-12-22 22:35

    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!)   
    }
    
    0 讨论(0)
  • 2020-12-22 22:37

    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

    0 讨论(0)
  • 2020-12-22 22:40

    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];
        }
    } 
    
    0 讨论(0)
  • 2020-12-22 22:46

    I know it's an old question, but someone may find it useful. It's only Swift version:

     //set the timer, which will update your progress bar. You can use whatever time interval you want
    
     private func setupProgressTimer() {
        timer = Timer.scheduledTimer(withTimeInterval: 0.02, repeats: true, block: { [weak self] (completion) in
            guard let self = self else { return }
            self.updateProgress()
        })
    }
    
    //update progression of video, based on it's own data
    
    private func updateProgress() {
        guard let duration = player?.currentItem?.duration.seconds,
            let currentMoment = player?.currentItem?.currentTime().seconds else { return }
    
        progressBar.progress = Float(currentMoment / duration)
    }
    
    0 讨论(0)
  • 2020-12-22 22:48
        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))
        }
    
    0 讨论(0)
提交回复
热议问题