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
Timeline Progress bar for AVPlayer
提问by Sanjeev Rao
AVPlayer
is fully customizable, unfortunately there are convenient methods in AVPlayer
for 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 AVPlayer
and 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)
}
}
回答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;
}