AVPlayer自定制视频播放器——视频播放器基本实现
Posted _Unique_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AVPlayer自定制视频播放器——视频播放器基本实现相关的知识,希望对你有一定的参考价值。
在ios多媒体开发的过程中,经常会用到视频播放器,简单是视频播放器,直接使用苹果封装好的MPMoviePlayerController和MPMoviePlayerViewController就可以实现视频播放功能了,但是,多数情况下,都需要自定制视频播放器,这是,就要使用神器AVPlayer来进行开发了,下面,就讲述一下AVPlayer的使用,这里列出两篇比较好的博客,供大家参考:
AVFoundation编程指南2-用AVPlayer播放视频 前一篇博客主要是简答介绍了怎样自定制视音频播放器,后一篇则比较深层次的讲解了视频播放器的相关信息,有兴趣的同学也可以了解一下AVFoundation框架,跟着本篇博客,读者可以自定义出一个完整的视频播放器。好了,废话不多说,开始进行视频播放的讲解。 首先,要使用AVPlayer进行自定制视频播放,要引入头文件:<span style="font-size:18px;">#import <AVFoundation/AVFoundation.h></span>
因为AVPlayer属于AVFoundation框架,所以要引入这个头文件。其次,当然是要创建我们的视频播放器AVPlayer了,这里在.h文件中声明了一个全局的Player对象,便于在不同的函数中进行相关操作。
<span style="font-size:18px;">@property (nonatomic,strong) AVPlayer * player;</span>
然后,在初始化方法中对其进行初始化。在初始化过程中,需要传入视频的URL,这个URL是NSURL类型的,这里简单说明一下,AVPlayer支持本地视频播放和媒体视频播放,因此,这个URL既可以是本地视频的URL,也可以是网络视频的URL,本篇博客选取了一段网络视频:
<span style="font-size:18px;"> //网络视频
NSString * urlStr = [NSString stringWithFormat:@"http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8"];
urlStr = [urlStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSURL * url = [NSURL URLWithString:urlStr];</span>
首先用字符串传进来一个地址,然后,为了对字符串进行UTF-8编码,之前是用的其他的方法,但是,在iOS9之后,已经被启用了,所以这里用上面的方法,进行编码,前后,用编码之后的字符串初始化URL。其实,如果不考虑比较多的内容的话,直接用下面的方法就可以创建一个AVPlayer了:
<span style="font-size:18px;">self.player = [[AVPlayer alloc] initWithURL:url];</span>
这其实是最简单的方法,但是一般不推荐使用,操作起来会很不方便。其实,自定制音频播放,也是用AVPlayer自定制,到这里的话,基本上就能够实现音频的播放了。但是,视频播放器的话,还要有画面,因此,还需要用这个Player去初始化一个图层,然后将图层加到当前的view的Layer上,这样,就有画面了,这里也是创建了一个全局的AVPlayerLayer对象:
<span style="font-size:18px;">@property (nonatomic,strong) AVPlayerLayer * playerLayer;</span>
然后在.m中,接着上面的方法写入下面的代码:
<span style="font-size:18px;"> self.player = [[AVPlayer alloc] initWithURL:url];
self.playerLayer.frame = self.layer.bounds;
self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
[self.layer addSublayer:self.playerLayer];</span>
我在创建的时候,是在一个View中创建的,所以直接是self.layer,如果是在Controller中,则是self.view.layer,然后,设置一下playerLayer的大小和方向。这样,就创建了一个Player。当然,上面说过,这只是简单的创建方式,通常情况下使用下面将要介绍的方式进行创建: 首先在.h中定义了一个AVPlayerItem对象:
<span style="font-size:18px;">@property (nonatomic,strong) AVPlayerItem * playerItem;</span>
然后,在.m文件中进行下面的操作:
<span style="font-size:18px;"> AVURLAsset * movieAsset = [[AVURLAsset alloc] initWithURL:URL options:nil];
self.playerItem = [AVPlayerItem playerItemWithAsset:movieAsset];
self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
self.playerLayer.frame = self.layer.bounds;
self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
[self.layer addSublayer:self.playerLayer];</span>
使用这种方式,就创建了一个player,并加到的当前视图的layer上。这里涉及到了其他的两个类AVURLAsset和AVPlayerItem。这里的AVURLAsset是AVAsset的子类,AVAsset不能直接用AVAsset进行初始化,需要用子类初始化,AVAsset和AVURLAsset其实是一个资源类,代表了视频资源,AVAsset也是AVFoundation中最终要的一个类,是对资源的抽象,想详细了解的,可以了解一下AVFoundation框架。而AVPlayerItem对应的其实就是要播放的视频了,先通过URL来创建一个视频播放器资源,然后,再用这个资源来初始化一个要播放的视频,之后,用这个视频初始化播放器,将播放器指定要播放的图层,基本上就创建完成了一个视频播放器,这里的初始化,是一层层进行的,希望大家不要被绕晕了,可以反复揣摩一下。player有一个rate属性,用来表示视频播放的速度,取值范围是0-1,1为正常速度,0的话,表示视频暂停了。上面说到,AVPlayerItem其实就相当于要播放的视频,因此,可以通过这个item,可以获得视频的总时长以及当前缓存到了哪里,所以,为了获取这些信息,要对item的相关属性进行监听:
<span style="font-size:18px;"> /**
* 监听AVPlayerItem的属性
*/
[self.playerItem addObserver:self forKeyPath:@"status" options:0 context:NULL];
[self.playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:0 context:NULL];
self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
</span>
然后,添加监听的方法:
<span style="font-size:18px;">/**
* KVO监听playItem的属性变化
*
* @param keyPath keyPath description
* @param object object description
* @param change change description
* @param context context description
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
AVPlayerItem * item = self.player.currentItem;
if ([keyPath isEqualToString:@"status"])
//正在播放
if (AVPlayerItemStatusReadyToPlay == item.status)
NSLog(@"正在播放...,视频总长度:%.2f",CMTimeGetSeconds(item.duration));
else if (AVPlayerItemStatusUnknown == item.status)
NSLog(@"视频加载中");
else if (AVPlayerStatusFailed == item.status)
NSLog(@"视频获取失败");
NSLog(@"%@",item.error);
else if([keyPath isEqualToString:@"loadedTimeRanges"])
NSArray *array=item.loadedTimeRanges;
CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];//本次缓冲时间范围
float startSeconds = CMTimeGetSeconds(timeRange.start);
float durationSeconds = CMTimeGetSeconds(timeRange.duration);
NSTimeInterval totalBuffer = startSeconds + durationSeconds;//缓冲总长度
NSLog(@"共缓冲:%.2f",totalBuffer);
</span>
通过监听status属性,来获得当前食品播放的状态,当状态为AVPlayerItemStatusReadyToPlay的时候,便是视频已经准备好了,此时,就可以播放当前的视频,可以在这里调用方法:
<span style="font-size:18px;"> [self.player play];</span>
来播放当前的视频,player有一个属性叫做currentItem,这个属性,就是当前player的item,也就是前面初始化过程中的那个item。监听item的loadedTimeRanges属性,可以获得当前缓冲了多少视频以及视频的总长度。item的loadedTimeRanges其实是一个数组,里面存放了CMTimeRange类型的结构体,通过获得该array的firstObject可以获得本次缓冲的时间信息timeRange,timeRange.start表示本次缓冲的开始位置,timeRange.duration表示本次缓冲的视频长度,两者相加,就获得了缓冲的总时长,这就是好多播放器中,底部进度条中缓冲的视频长度的获取方式。由于loadedTimeRanges经常要变化,所以,会反复出发这个KVO的监听,因此,可以做到随时刷新缓冲进度。此外,当退出播放器页面的时候,要移除相关的观察者。
<span style="font-size:18px;">//移除观察者
-(void)removeObserverFromPlayerItem:(AVPlayerItem *)playerItem
[playerItem removeObserver:self forKeyPath:@"status"];
[playerItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
</span>
然后在dealloc调用这个方法该方法即可。 此外,当视频播放完成之后,还会有相关的通知,在这里,可以对其进行监听,当播放完成之后,进行相关的UI刷新:
<span style="font-size:18px;">/**
* 添加播放器通知
*/
-(void)addNotification
//给AVPlayerItem添加播放完成通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem];
</span>
例如,可以更改播放按钮的图片:
<span style="font-size:18px;">- (void)playbackFinished:(NSNotification *)notification
[self.playButton setImage:[UIImage imageNamed:@"button_normal"] forState:UIControlStateNormal];
</span>
既然监听通知了,就要在dealloc中移除监听:
<span style="font-size:18px;"> [self removeNotification];</span>
上面只是创建了视频播放器,下面将讲解视频播放器的控制。我们在进行视频自定制的时候,还要实现视频播放器的播放、暂停、继续播放、停止功能,因此,还要进行一些其他的操作,我这里将播放和继续播放进行了区分,这里说的播放,是从头开始播放,继续播放,则是从上次暂停的位置进行播放,因此要设置一个属性,来保存当前播放的位置:
<span style="font-size:18px;">//当前播放进度
@property (nonatomic,assign) double currentTime;</span>
下面是从头播放的方法:
<span style="font-size:18px;">/**
* 开始播放
*/
- (void)play
AVPlayerItem * item = self.player.currentItem;
// [item seekToTime:CMTimeMakeWithSeconds(0, 1.0)];
[item seekToTime:CMTimeMakeWithSeconds(0, 1.0) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
self.progressBar.value = 0;
[self.player play];
//设置播放速度
</span>
在该方法中,用到了seekToTime方法,该方法用来从指定位置开始播放,传入一个CMTime类型的时间值,来指定比方的位置。我的项目中,添加了一个进度条(UISlider),来表示当前播放的进度,因此,当从0来说播放的时候,在这里将slider的value设置成0。这里还注释掉了一个seek方法,下面简单说一下,第一个seek方法,seek的时间没有第二个精确,但第二个更好性能,但还是推荐使用第二个。 下面是暂停的方法:
<span style="font-size:18px;">/**
* 暂停播放
*/
- (void)pause
self.currentTime = [self playableCurrentTime];
[self.player pause];
//设置播放按钮
[self.playButton setImage:[UIImage imageNamed:@"button_normal"] forState:UIControlStateNormal];
</span>
Avplayer自带一个pause方法,因此,这里可以直接调用,还有上面的play方法也是自带的。下面是resume方法:
<span style="font-size:18px;">/**
* 继续播放
*/
- (void)resume
AVPlayerItem * item = self.player.currentItem;
// [item seekToTime:CMTimeMakeWithSeconds(self.currentTime, 1.0)];
[item seekToTime:CMTimeMakeWithSeconds(self.currentTime, 1.0) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
[self.player play];
//设置播放速度
self.player.rate = self.rate;
</span>
上面说到了player的rate属性,其实这个属性的取值并不一定非要在0-1之间,当大于1的时候,会加速播放,小于1会减缓,这里可以通过这个属性来控制播放的速度。下面是停止方法:
<span style="font-size:18px;">/**
* 停止
*/
- (void)stop
AVPlayerItem * item = self.player.currentItem;
// [item seekToTime:CMTimeMakeWithSeconds(0, 1.0)];
[item seekToTime:CMTimeMakeWithSeconds(0, 1.0) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
[self.player pause];
self.currentTime = 0;
</span>
这里使用seek方法,seek到0,然后调用player的pause方法,将视频pause,就实现了视频的stop功能。 此外,有些情况下,还会遇到这样的需求,就是将视频静音,在AVFoundation中,已经为我们提供了这样一个方法,就是将play的volume值设置为0。但是,这里要仔细考虑一下了,当设置为0之后,要想设置回来,怎么办呢?因此,要将当前的volume保存起来。定义一个变量保存当前的音量。
//音量
@property (nonatomic,assign) float volumn;
当点击静音按钮的时候,调用下面的方法即可:
<span style="font-size:18px;">/**
* 设置静音
*
* @param mute 静音传入的一个BOOL值,YES为静音,NO不静音
*/
- (void)playerMute:(BOOL)mute
if (mute)
[self.player setVolume:0];
else
[self.player setVolume:self.volumn];
</span>
这里有一个问题需要注意,这里设置的音量,只是应用中的音量,并不是系统音量,也就是说,当系统音量为0的时候,及时这个volume再大,也是没有声音的,因此,当想要恢复音量的时候,这里的volume一般都设置1,即正常的音量。这时候,有些通过就会想了,那么,该怎么获取系统音量,然后点击手机上的音量键来调节音量,其实还是有方法的,下面是我写的另一篇博客: iOS更改系统音量 想获取系统音量,请跳转到这个博客,了解一下就好,也可以查询一下其他的博客。 下面,再讲一些其他的控制。 自定制播放器的过程中,当视频播放进度发生改变的时候,我们也希望对应的进度条也跟着变化,因此,要监听视频播放的进度,可以使用下面的方法进行实现:
<span style="font-size:18px;">/**
* 进度更新设置,监听视频播放进度,同时更新进度条的value
*/
- (void)addProgressBarObserver
AVPlayerItem *playerItem=self.player.currentItem;
__weak typeof(self) weakSelf = self;
[self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time)
float current = CMTimeGetSeconds(time);
float total = CMTimeGetSeconds([playerItem duration]);
if (current)
[weakSelf.progressBar setValue:(current/total) animated:YES];
];
</span>
这里将进度条更新的操作封装成了一个方法,其实还是调用了AVPlayer自带的方法
<span style="font-size:18px;">- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;</span>
这个方法的前一个参数还是CMTime类型的,后面是一个block,表示每个interval的时间,就回调一下这个block,这样的话,我们就可以在这里通过播放进度和总时长,来设置进度条的value值了。
通过以上的方法,基本上就能实现一个简单的视频播放器了,可能有些地方说的不好或者说法有误,欢迎大家在下面进行评论,指出我的错误,大家共同进步。想进一步了解视频播放器的内容,欢迎阅读下一篇博客:
AVPlayer自定制视频播放器(2)——耳机线控、中断以及AVAudioSession的使用
以上是关于AVPlayer自定制视频播放器——视频播放器基本实现的主要内容,如果未能解决你的问题,请参考以下文章
向上滑动(将应用程序置于后台)并且视频在自定义 AVPLayer 中播放时如何停止画中画?
AVPlayer 在在线模式下停止播放 AES 加密的离线 HLS 视频