当前位置:   article > 正文

AVPlayer自定制视频播放器(1)——视频播放器基本实现

AVPlayer自定制视频播放器(1)——视频播放器基本实现

在iOS多媒体开发的过程中,经常会用到视频播放器,简单是视频播放器,直接使用苹果封装好的MPMoviePlayerController和MPMoviePlayerViewController就可以实现视频播放功能了,但是,多数情况下,都需要自定制视频播放器,这是,就要使用神器AVPlayer来进行开发了,下面,就讲述一下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,本篇博客选取了一段网络视频:

  1. <span style="font-size:18px;"> //网络视频
  2. NSString * urlStr = [NSString stringWithFormat:@"http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8"];
  3. urlStr = [urlStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
  4. 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中,接着上面的方法写入下面的代码:

  1. <span style="font-size:18px;"> self.player = [[AVPlayer alloc] initWithURL:url];
  2. self.playerLayer.frame = self.layer.bounds;
  3. self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
  4. [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文件中进行下面的操作:

  1. <span style="font-size:18px;"> AVURLAsset * movieAsset = [[AVURLAsset alloc] initWithURL:URL options:nil];
  2. self.playerItem = [AVPlayerItem playerItemWithAsset:movieAsset];
  3. self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
  4. self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
  5. self.playerLayer.frame = self.layer.bounds;
  6. self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
  7. [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的相关属性进行监听:

  1. <span style="font-size:18px;"> /**
  2. * 监听AVPlayerItem的属性
  3. */
  4. [self.playerItem addObserver:self forKeyPath:@"status" options:0 context:NULL];
  5. [self.playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:0 context:NULL];
  6. self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
  7. </span>


然后,添加监听的方法:

  1. <span style="font-size:18px;">/**
  2. * KVO监听playItem的属性变化
  3. *
  4. * @param keyPath keyPath description
  5. * @param object object description
  6. * @param change change description
  7. * @param context context description
  8. */
  9. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
  10. AVPlayerItem * item = self.player.currentItem;
  11. if ([keyPath isEqualToString:@"status"]) {
  12. //正在播放
  13. if (AVPlayerItemStatusReadyToPlay == item.status) {
  14. NSLog(@"正在播放...,视频总长度:%.2f",CMTimeGetSeconds(item.duration));
  15. }
  16. else if (AVPlayerItemStatusUnknown == item.status){
  17. NSLog(@"视频加载中");
  18. }
  19. else if (AVPlayerStatusFailed == item.status){
  20. NSLog(@"视频获取失败");
  21. NSLog(@"%@",item.error);
  22. }
  23. } else if([keyPath isEqualToString:@"loadedTimeRanges"]){
  24. NSArray *array=item.loadedTimeRanges;
  25. CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];//本次缓冲时间范围
  26. float startSeconds = CMTimeGetSeconds(timeRange.start);
  27. float durationSeconds = CMTimeGetSeconds(timeRange.duration);
  28. NSTimeInterval totalBuffer = startSeconds + durationSeconds;//缓冲总长度
  29. NSLog(@"共缓冲:%.2f",totalBuffer);
  30. }
  31. }</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的监听,因此,可以做到随时刷新缓冲进度。此外,当退出播放器页面的时候,要移除相关的观察者。

  1. <span style="font-size:18px;">//移除观察者
  2. -(void)removeObserverFromPlayerItem:(AVPlayerItem *)playerItem{
  3. [playerItem removeObserver:self forKeyPath:@"status"];
  4. [playerItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
  5. }</span>

然后在dealloc调用这个方法该方法即可。
此外,当视频播放完成之后,还会有相关的通知,在这里,可以对其进行监听,当播放完成之后,进行相关的UI刷新:

  1. <span style="font-size:18px;">/**
  2. * 添加播放器通知
  3. */
  4. -(void)addNotification{
  5. //给AVPlayerItem添加播放完成通知
  6. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem];
  7. }</span>

例如,可以更改播放按钮的图片:

  1. <span style="font-size:18px;">- (void)playbackFinished:(NSNotification *)notification{
  2. [self.playButton setImage:[UIImage imageNamed:@"button_normal"] forState:UIControlStateNormal];
  3. }</span>

既然监听通知了,就要在dealloc中移除监听:

<span style="font-size:18px;">    [self removeNotification];</span>

上面只是创建了视频播放器,下面将讲解视频播放器的控制。我们在进行视频自定制的时候,还要实现视频播放器的播放、暂停、继续播放、停止功能,因此,还要进行一些其他的操作,我这里将播放和继续播放进行了区分,这里说的播放,是从头开始播放,继续播放,则是从上次暂停的位置进行播放,因此要设置一个属性,来保存当前播放的位置:

  1. <span style="font-size:18px;">//当前播放进度
  2. @property (nonatomic,assign) double currentTime;</span>

下面是从头播放的方法:

  1. <span style="font-size:18px;">/**
  2. * 开始播放
  3. */
  4. - (void)play{
  5. AVPlayerItem * item = self.player.currentItem;
  6. // [item seekToTime:CMTimeMakeWithSeconds(0, 1.0)];
  7. [item seekToTime:CMTimeMakeWithSeconds(0, 1.0) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
  8. self.progressBar.value = 0;
  9. [self.player play];
  10. //设置播放速度
  11. }</span>

在该方法中,用到了seekToTime方法,该方法用来从指定位置开始播放,传入一个CMTime类型的时间值,来指定比方的位置。我的项目中,添加了一个进度条(UISlider),来表示当前播放的进度,因此,当从0来说播放的时候,在这里将slider的value设置成0。这里还注释掉了一个seek方法,下面简单说一下,第一个seek方法,seek的时间没有第二个精确,但第二个更好性能,但还是推荐使用第二个。
下面是暂停的方法:

  1. <span style="font-size:18px;">/**
  2. * 暂停播放
  3. */
  4. - (void)pause{
  5. self.currentTime = [self playableCurrentTime];
  6. [self.player pause];
  7. //设置播放按钮
  8. [self.playButton setImage:[UIImage imageNamed:@"button_normal"] forState:UIControlStateNormal];
  9. }</span>

Avplayer自带一个pause方法,因此,这里可以直接调用,还有上面的play方法也是自带的。下面是resume方法:

  1. <span style="font-size:18px;">/**
  2. * 继续播放
  3. */
  4. - (void)resume{
  5. AVPlayerItem * item = self.player.currentItem;
  6. // [item seekToTime:CMTimeMakeWithSeconds(self.currentTime, 1.0)];
  7. [item seekToTime:CMTimeMakeWithSeconds(self.currentTime, 1.0) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
  8. [self.player play];
  9. //设置播放速度
  10. self.player.rate = self.rate;
  11. }</span>

上面说到了player的rate属性,其实这个属性的取值并不一定非要在0-1之间,当大于1的时候,会加速播放,小于1会减缓,这里可以通过这个属性来控制播放的速度。下面是停止方法:

  1. <span style="font-size:18px;">/**
  2. * 停止
  3. */
  4. - (void)stop{
  5. AVPlayerItem * item = self.player.currentItem;
  6. // [item seekToTime:CMTimeMakeWithSeconds(0, 1.0)];
  7. [item seekToTime:CMTimeMakeWithSeconds(0, 1.0) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
  8. [self.player pause];
  9. self.currentTime = 0;
  10. }</span>

这里使用seek方法,seek到0,然后调用player的pause方法,将视频pause,就实现了视频的stop功能。
此外,有些情况下,还会遇到这样的需求,就是将视频静音,在AVFoundation中,已经为我们提供了这样一个方法,就是将play的volume值设置为0。但是,这里要仔细考虑一下了,当设置为0之后,要想设置回来,怎么办呢?因此,要将当前的volume保存起来。定义一个变量保存当前的音量。

  1. //音量
  2. @property (nonatomic,assign) float volumn;

当点击静音按钮的时候,调用下面的方法即可:

  1. <span style="font-size:18px;">/**
  2. * 设置静音
  3. *
  4. * @param mute 静音传入的一个BOOL值,YES为静音,NO不静音
  5. */
  6. - (void)playerMute:(BOOL)mute{
  7. if (mute) {
  8. [self.player setVolume:0];
  9. } else {
  10. [self.player setVolume:self.volumn];
  11. }
  12. }</span>

这里有一个问题需要注意,这里设置的音量,只是应用中的音量,并不是系统音量,也就是说,当系统音量为0的时候,及时这个volume再大,也是没有声音的,因此,当想要恢复音量的时候,这里的volume一般都设置1,即正常的音量。这时候,有些通过就会想了,那么,该怎么获取系统音量,然后点击手机上的音量键来调节音量,其实还是有方法的,下面是我写的另一篇博客:
想获取系统音量,请跳转到这个博客,了解一下就好,也可以查询一下其他的博客。
下面,再讲一些其他的控制。
自定制播放器的过程中,当视频播放进度发生改变的时候,我们也希望对应的进度条也跟着变化,因此,要监听视频播放的进度,可以使用下面的方法进行实现:

  1. <span style="font-size:18px;">/**
  2. * 进度更新设置,监听视频播放进度,同时更新进度条的value
  3. */
  4. - (void)addProgressBarObserver{
  5. AVPlayerItem *playerItem=self.player.currentItem;
  6. __weak typeof(self) weakSelf = self;
  7. [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
  8. float current = CMTimeGetSeconds(time);
  9. float total = CMTimeGetSeconds([playerItem duration]);
  10. if (current) {
  11. [weakSelf.progressBar setValue:(current/total) animated:YES];
  12. }
  13. }];
  14. }</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的使用

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号