详解iOS视频播放方式

多媒体这整个系列的文章自己也准备好开始整理了,先从视频音频最简单也是最常用的播放出发慢慢的往下深究,探索到底层的编码解码等等,这篇文章就从视频的播放这个最简单的说起。

iOS的视频播放方式有几种?其实要是只是简单的想播放一段视频并且对UI没什么要求的话的确比较简单,很容易搞定,但我相信这种情况除了你的Demo一般是不会出现的,对播放UI的定义以及可能有各种不同的需求对应着你是不能随便写个播放器就没事了的。

最原始的播放

要不是刚接触iOS开发的同学应该是知道MediaPlayer这个框架的,要是想简单的使用它播放视频,可能几行代码就能搞定了,它里面有一个MPMoviePlayerViewController,利用起来简单的不要不要的。

不过遗憾的是自从iOS 9.0开始,它是被Apple遗弃了的,9.0之后的项目建议用的我们下面再说,你要是有维护9.0之前的项目,可能它你也有必要了解一下,我们也介绍一个它的基本的使用,以及它里面的整个播放的代码逻辑。

工程师以前写过一个三方,KRVideoPlayer

这个播放器就是基于MediaPlayer框架写的,里面就两个文件,代码也是相当的简单,你直接把它源码下载下来之后我们当一个了解MediaPlayer的Demo简单的说一下。下满是它git上面展示的gif Demo图片:

你在看看它源码里面的文件:只有 KRVideoPlayerControlView 和 KRVideoPlayerController 两个,简单分析它们:

1、KRVideoPlayerControlView 继承自 UIView

说白了这个文件写的就是播放器的UI,包括一些播放按钮,进度条,以及全屏切换等等

2、KRVideoPlayerController   集成自 MPMoviePlayerController

继承之后直接使用MPMoviePlayerController来播放视频,是在它初始化的时候在self.view 上添加 KRVideoPlayerControlView 这个自定义的UI,你可以看到下面的代码:

// 初始化KRVideoPlayerController
- (instancetype)initWithFrame:(CGRect)frame
{
 self = [super init];
 if (self) {
 self.view.frame = frame;
 self.view.backgroundColor = [UIColor blackColor];
 self.controlStyle = MPMovieControlStyleNone;
 [self.view addSubview:self.videoControl];
 self.videoControl.frame = self.view.bounds;
 [self configObserver];
 [self configControlAction];
 }
 return self;
}
// 懒加载KRVideoPlayerControlView
- (KRVideoPlayerControlView *)videoControl
{
 if (!_videoControl) {
 _videoControl = [[KRVideoPlayerControlView alloc] init];
 }
 return _videoControl;
}

关于MediaPlayer还有下面的需要你留意一下:

1、关于播放或者暂停等的方法都是在MPMediaPlayback协议里面的

2、MPMoviePlayerController就是遵守了上面说的MPMediaPlayback协议,下面的MPMoviePlayerController源码:

3、在给MPMoviePlayerController写的类别MPMovieProperties、MPMoviePlayerThumbnailGeneration、MPMoviePlayerTimedMetadataAdditions包含了这个播放器几乎所有的功能,淡然这部分的方法代代码都是在MPMoviePlayerController.h中,有兴趣或者需要的可以command进去了解。

4、上面介绍的三方提供给大家的不仅仅是一份代码,希望我们都能理解一个思路,就是自定义的播放器我们该怎么去理解去动手做。这点后面我还会再提。

关于MediaPlayer的暂时就提这么多,有问题欢迎交流。

该升级一下了

嗯,该升级一下了,说到这里就的说我们前面说到的9.0系统之后的播放器,这说这个之前顺便提一个自己的见解,以前我们开发应用的时候我记得最开始适配的最低版本是7.0以上的,到前两年发展到8.0以上,按照我自己的理解,在11系统发布后我们要是做新应用或者旧的项目项目维护的时候应该要慢慢的舍弃7.0以及8.0的了,也就是最低版本按照9.0开始,因为不管是7.0还是8.0,用户所占的比例真的是很小很下了,并且一些新鲜的功能在我们的低版本是不支持的, 维护的成本也会慢慢的变得越来越大,当然这些也都不是空穴来风,可以上网去搜一下8.0之前版本系统占得比例,以及8.0、7.0给整个维护带来的成本,我在最近逛一些论坛的时候也有同行在说这个问题了。好了回到正题!

说我们的正题:9.0之后Apple建议用的: AVKit框架,首先AVKit框架是8.0之后出现的,它是建立在我们熟悉的AVFoundation框架之上的.

利用AVKit进行视频播放时我们整理一下我们需要的大致都在这几个类或者协议当中:

1、AVPlayerItem                          (视频要播放的元素)

2、AVPlayerLayer                        (播放显示视频的图层界面)

3、AVPlayer                                (用于播放音视频)

4、AVPlayerViewController            (控制器)

5、AVPlayerViewControllerDelegate(协议)

要是想要彻底的了解AVFoundation这个框架是不容易的,这个框架的确很庞大,有一本书叫做 《AV Foundation 开发秘籍》有兴趣的可以去购买看看,自己也在学习当中,后续的文章全都会整理在这个系列当中。

这篇文章就等于是给这个系列开一个头,这个框架的学习之路应该是漫长的,也希望自己能坚持完吧这个系列文章全都总结出来。下面把上面说的各个类分别说一下:

1、AVPlayerItem

在我们使用AVPlayer播放视频的时候,提供视频信息的就是AVPlayerItem,一个AVPlayerItem对应着你提供的一个视频Url资源,这个理解它的时候可以把它比作一个Model, 你初始化了AVPlayerItem之后,并不是马上就可以使用它了,因为凡是和Url网络扯上关系的,都需要时间,等AVPlayerItem加载好之后就可以使用它了,那这一步我们怎么处理呢?

1> : 答案是利用KVO观察statues属性为 AVPlayerStatusReadyToPlay,看看这个属性的定义:

@property (nonatomic, readonly) AVPlayerStatus status  它是一个只读属性,这点也需要注意,其实也就理解利用KVO的原因。

2>: 顺便总结要是你要显示当前视屏的缓存进度,你需要监测它的loadedTimeRanges属性。

2、AVPlayerLayer

它主要负责的就是视频的显示,继承自CALayer,其实你可以把它理解成我们的View。我们自定义的那些播放时候的控件就是添加在它上面的,比如我们能看到的播放按钮,停止按钮,或者播放进度条等等。

3、 AVPlayer

它主要负责的是管理视频播放,暂停等等,相当于一个视频管理器,要是类比的话他就是一个ViewController(当然不是真正的ViewController),这三者就基本含括了一个基本的视频播,基于着三者我们总结一下播放一个视频的基本的过程:

首先,得到视频的URL 根据URL创建AVPlayerItem 把AVPlayerItem 提供给 AVPlayer AVPlayerLayer 显示视频。 AVPlayer 控制视频, 播放, 暂停, 跳转 等等。播放过程中获取缓冲进度,获取播放进度。视频播放完成后做些什么,是暂停还是循环播放,还是获取最后一帧图像。

4、AVPlayerViewController

它是Apple 帮我们封装好的可以一个视频播放控制器,它就有一个  @property (nonatomic, strong, nullable) AVPlayer *player 的属性,前面的AVPlayer也就像相应的需要赋值给它,它里面还有一些我们需要理解一下的属性,我们也把它写出来,具体代码我们下面再看:

player:                                    设置播放器 showsPlaybackControls:           设置是否显示媒体播放组件,默认YES videoGravity:                           设置视频拉伸模式 allowsPictureInPicturePlayback: 设置是否允许画中画回放,默认YES delegate:                                设置代理

5、AVPlayerViewControllerDelegate

这个代理就是前面说的AVPlayerViewController的协议,它主要的是为画中画的设置的代理,前面介绍 AVPlayerViewController 的时候有看到过一个是否允许画中画的属性,具体什么是画中画相信大家都了解,看过直接的朋友应该都看到了这个技术点的具体应用。我们看看它里面的饭法规主要都干了些什么?

// 1、即将开始画中画
- (void)playerViewControllerWillStartPictureInPicture:(AVPlayerViewController *)playerViewController;
// 2、开始画中画
- (void)playerViewControllerDidStartPictureInPicture:(AVPlayerViewController *)playerViewController;
// 3、画中画失败
- (void)playerViewController:(AVPlayerViewController *)playerViewController failedToStartPictureInPictureWithError:(NSError *)error;
// 4、即将结束画中画
- (void)playerViewControllerWillStopPictureInPicture:(AVPlayerViewController *)playerViewController;
// 5、结束画中画
- (void)playerViewControllerDidStopPictureInPicture:(AVPlayerViewController *)playerViewController;

我们看一个简单的Demo

我们先不说关于AVFoundation复杂的东西,因为自己也是在学习这个 AVFoundation当中,我们先看一些很简单的Demo,就简单的利用一下AVFoundation 播放一下视频:

我们在简单的看一下我们写的这部分的代码,简单的先使用了一下我们说的上面的一些知识点:

- (void)viewDidLoad {
 [super viewDidLoad];
 // Do any additional setup after loading the view.
 self.view.backgroundColor = [UIColor whiteColor];
 self.avPlayerItem = [AVPlayerItem playerItemWithURL:[NSURL URLWithString:MovieURL]];
 self.avPlayer = [[AVPlayer alloc]initWithPlayerItem:self.avPlayerItem];
 self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer];
 self.avPlayerLayer.frame = CGRectMake(10, 100, 355, 200);
 [self.view.layer addSublayer:self.avPlayerLayer];
 // 添加观察者
 [self addObserverWithAVPlayerItem];
}
#pragma mark --
#pragma mark -- KVO
-(void)addObserverWithAVPlayerItem{
 //状态添加观察者
 [self.avPlayerItem addObserver:self forKeyPath:@"status" options:(NSKeyValueObservingOptionNew) context:nil];
 // 缓存进度添加观察者
 [self.avPlayerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
 AVPlayerItem * avplayeritem = (AVPlayerItem *)object;
 if ([keyPath isEqualToString:@"status"]) {
 AVPlayerStatus status = [[change objectForKey:@"new"] intValue];
 if (status == AVPlayerStatusReadyToPlay) {
  NSLog(@"准备好播放");
  CMTime duration = avplayeritem.duration;
  NSLog(@"视频总时长:%.2f",CMTimeGetSeconds(duration));
  // 播放
  [self.avPlayer play];
 }else if (status == AVPlayerStatusFailed){
  NSLog(@"视频准备发生错误");
 }else{
  NSLog(@"位置错误");
 }
 }else if ([keyPath isEqualToString:@"loadedTimeRanges"]){
  // 可以自定义缓存进度
  NSTimeInterval timeInterval = [self alreadyCacheVideoProgress];
  NSLog(@"视频已经缓存的时长:%.2f",timeInterval);
 }
}

#pragma mark --
#pragma mark -- alreadyCacheVideoProgress
-(NSTimeInterval)alreadyCacheVideoProgress{

 // 先获取到它的缓存的进度
 NSArray * cacheVideoTime = [self.avPlayerItem loadedTimeRanges];
 // CMTimeRange 结构体 start duration 表示起始位置 和 持续时间
 // 获取缓冲区域
 CMTimeRange timeRange = [cacheVideoTime.firstObject CMTimeRangeValue];
 float startSeconds = CMTimeGetSeconds(timeRange.start);
 float durationSeconds = CMTimeGetSeconds(timeRange.duration);
 // 计算总缓冲时间 = start + duration
 NSTimeInterval result = startSeconds + durationSeconds;
 return result;
}

这些点我们有必要注意一下

1、CMTime  一个专门用于标识视频时间的结构体

/*!
	@typedef	CMTime
	@abstract	Rational time value represented as int64/int32.
*/
typedef struct
{
	CMTimeValue	value;		/*! @field value The value of the CMTime. value/timescale = seconds. 帧数 */
	CMTimeScale	timescale;	/*! @field timescale The timescale of the CMTime. value/timescale = seconds.帧率(影片每秒有几帧)*/ CMTimeFlags flags; /*! @field flags The flags, eg. kCMTimeFlags_Valid, kCMTimeFlags_PositiveInfinity, etc. */  CMTimeEpoch epoch; /*! @field epoch Differentiates between equal timestamps that are actually different because of looping, multi-item sequencing, etc. Will be used during comparison: greater epochs happen after lesser ones. Additions/subtraction is only possible within a single epoch, however, since epoch length may be unknown/variable. */} CMTime;

前面的代码中我们看到有一个获取视频总长度的方法:

CMTime duration = avplayeritem.duration;
NSLog(@"视频总时长:%.2f",CMTimeGetSeconds(duration));

可以看到CMTimeGetSeconds这个函数把一个CMTime类型转化成一个浮点型,如果一个影片为60帧/每秒, 当前想要跳转到120帧的位置,也就是两秒的位置,那么就可以创建一个 CMTime 类型数据。它通常可以用下面两个函数来创建.

1>: CMTimeMake(int64_t value, int32_t scale)                          Eg: CMTime time1 = CMTimeMake(120, 60);

2>:CMTimeMakeWithSeconds(Flout64 seconds, int32_t scale)   Eg: CMTime time2 = CMTimeWithSeconds(120, 60);

CMTimeMakeWithSeconds 和 CMTimeMake 区别在于,第一个函数的第一个参数可以是float,其他一样。

- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;

比如说:我们把时间间隔设置为, 1/ 10 秒,然后 block 里面更新 UI。就是一秒钟更新10次UI,我们验证一下:

[self.avPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1, 10) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
  // CMTime的timescale的定义帮助理解下面代码
  // @field timescale The timescale of the CMTime. value/timescale = seconds.
  float currentPlayTime = (double)self.avPlayerItem.currentTime.value/ self.avPlayerItem.currentTime.timescale;
  NSLog(@"当前播放进度:%f",currentPlayTime);
 }];

我们随便截取出一段打印的日志,看一下结果就可以验证:

2、AVPlayerItem  视频播放结束通知

/* Note that NSNotifications posted by AVPlayerItem may be posted on a different thread from the one on which the observer was registered. */

// notifications          description
AVF_EXPORT NSString *const AVPlayerItemTimeJumpedNotification			 NS_AVAILABLE(10_7, 5_0);	// the item's current time has changed discontinuously
AVF_EXPORT NSString *const AVPlayerItemDidPlayToEndTimeNotification NS_AVAILABLE(10_7, 4_0); // item has played to its end time
AVF_EXPORT NSString *const AVPlayerItemFailedToPlayToEndTimeNotification NS_AVAILABLE(10_7, 4_3); // item has failed to play to its end time
AVF_EXPORT NSString *const AVPlayerItemPlaybackStalledNotification NS_AVAILABLE(10_9, 6_0); // media did not arrive in time to continue playback
AVF_EXPORT NSString *const AVPlayerItemNewAccessLogEntryNotification	 NS_AVAILABLE(10_9, 6_0);	// a new access log entry has been added
AVF_EXPORT NSString *const AVPlayerItemNewErrorLogEntryNotification		 NS_AVAILABLE(10_9, 6_0);	// a new error log entry has been added

// notification userInfo key         type
AVF_EXPORT NSString *const AVPlayerItemFailedToPlayToEndTimeErrorKey NS_AVAILABLE(10_7, 4_3); // NSError

3、这些个三方框架

(1): VKVideoPlayer

(2): ALMoviePlayerController

(3): PBJVideoPlayer

(4): 还有这个比较厉害的MobileVLCKit

关于上面上的这些三方都给出了连接,最后一个给的是一篇帮助我们集成的文章,这些三方在后面这个系列文章的总结中会一点点慢慢的全都说一下,在这里只提一下有这些框架在,有兴趣可以先了解,后面我在总结。

(0)

相关推荐

  • 详解iOS视频播放方式

    多媒体这整个系列的文章自己也准备好开始整理了,先从视频音频最简单也是最常用的播放出发慢慢的往下深究,探索到底层的编码解码等等,这篇文章就从视频的播放这个最简单的说起. iOS的视频播放方式有几种?其实要是只是简单的想播放一段视频并且对UI没什么要求的话的确比较简单,很容易搞定,但我相信这种情况除了你的Demo一般是不会出现的,对播放UI的定义以及可能有各种不同的需求对应着你是不能随便写个播放器就没事了的. 最原始的播放 要不是刚接触iOS开发的同学应该是知道MediaPlayer这个框架的,要是

  • 详解IOS 单例的两种方式

    详解IOS 单例的两种方式 方法一: #pragma mark - #pragma mark sharedSingleton methods //单例函数 static RtDataModel *sharedSingletonManager = nil; + (RtDataModel *)sharedManager { @synchronized(self) { if (sharedSingletonManager == nil) { sharedSingletonManager = [[sel

  • 详解IOS开发中生成推送的pem文件

    详解IOS开发中生成推送的pem文件 具体步骤如下: 首先,需要一个pem的证书,该证书需要与开发时签名用的一致. 具体生成pem证书方法如下: 1. 登录到 iPhone Developer Connection Portal(http://developer.apple.com/iphone/manage/overview/index.action )并点击 App IDs 2. 创建一个不使用通配符的 App ID .通配符 ID 不能用于推送通知服务.例如,  com.itotem.ip

  • 详解IOS串行队列与并行队列进行同步或者异步的实例

    详解IOS串行队列与并行队列进行同步或者异步的实例 IOS中GCD的队列分为串行队列和并行队列,任务分为同步任务和异步任务,他们的排列组合有四种情况,下面分析这四种情况的工作方式. 同步任务,使用GCD dispatch_sync 进行派发任务 - (void)testSync { dispatch_queue_t serialQueue = dispatch_queue_create("com.zyt.queue", DISPATCH_QUEUE_SERIAL); dispatch_

  • 详解iOS自定义UITabBar与布局

    在小编整理过的文章iOS项目基本框架搭建中,我们详细说明了如何对TabBarItem的图片属性以及文字属性进行一些自定义配置.但是,很多时候,我们需要修改TabBarItem的图片和文字属性之外,还需要自定义TabBarItem的位置,这样系统自带的TabBar的样式并不能满足我们的项目需求,所以我们需要对系统的UITabBar进行自定义,以达到我们的项目需求.例如新浪微博App的底部tab的item就无法用自带的TabBarItem进行实现,最中间那个[+]发布微博并不是用来切换tab的,而是

  • 详解 iOS 系统中的视图动画

    动画为用户界面的状态转换提供了流畅的可视化效果, 在 iOS 中大量使用了动画效果, 包括改变视图位置. 大小. 从可视化树中删除视图, 隐藏视图等. 你可以考虑用动画效果给用户提供反馈或者用来实现有趣的特效. 在 iOS 系统中, Core Animation 提供了内置的动画支持, 创建动画不需要任何绘图的代码, 你要做的只是激发指定的动画, 接下来就交给 Core Animation 来渲染, 总之, 复杂的动画只需要几行代码就可以了. 哪些属性可以添加动画效果 根据 iOS 视图编程指南

  • 详解IOS判断当前网络状态的三种方法

    在项目中,为了好的用户体验,有些场景必须线判断网络状态,然后才能决定该干嘛.比如视频播放,需要线判断是Wifi还是4G,Wifi直接播放,4G先提示用户.获取网络状态的方法大概有三种: 1. Reachability 这是苹果的官方演示demo中使用到的方法,我们可以到苹果官方文档里下载Demo(点击左上角Download Sample Code 即可下载),然后把Demo里的Reachability.h和.m考到自己项目中,并在Build Phases 的 Link Binary 添加Syst

  • 详解IOS WebRTC的实现原理

    概述 它在2011年5月开放了工程的源代码,在行业内得到了广泛的支持和应用,成为下一代视频通话的标准. WebRTC的音视频通信是基于P2P,那么什么是P2P呢? 它是点对点连接的英文缩写. P2P连接模式 一般我们传统的连接方式,都是以服务器为中介的模式: 类似http协议:客户端?服务端(当然这里服务端返回的箭头仅仅代表返回请求数据). 我们在进行即时通讯时,进行文字.图片.录音等传输的时候:客户端A?服务器?客户端B. 而点对点的连接恰恰数据通道一旦形成,中间是不经过服务端的,数据直接从一

  • 详解iOS 实现一对多代理方案

    目录 实现方案一 实现方案二 实现方案一 利用可变数组. 签协议方需要add到代理的数组中, 然后协议遍历数组中的对象,进行分发.缺点是需要数组对其内部元素是强引用, 需要在合适的地方对其进行释放,否则会有内存泄漏 代理协议的对象.h写法 #import <UIKit/UIKit.h> NS_ASSUME_NONNULL_BEGIN @protocol TestSubViewDelegate <NSObject> - (void)testSendSomeMessageToOther

  • 详解IOS如何防止抓包

    目录 抓包原理 防止抓包 一.发起请求之前判断是否存在代理,存在代理就直接返回,请求失败. 二.我们可以在请求配置中清空代理,让请求不走代理 SSL Pinning(AFN+SSL Pinning)推荐 扩展 抓包原理 其实原理很是简单:一般抓包都是通过代理服务来冒充你的服务器,客户端真正交互的是这个假冒的代理服务,这个假冒的服务再和我们真正的服务交互,这个代理就是一个中间者 ,我们所有的数据都会通过这个中间者,所以我们的数据就会被抓取.HTTPS 也同样会被这个中间者伪造的证书来获取我们加密的

随机推荐