AVFoundation 对于播放封装了主要的三个类 AVPlay、AVPlayerLayer 和 AVPlayerItem。数组
AVPlayer 是一个用于播放基于时间的试听媒体的控制器对象,能够播放本地、分布下载以及 HTTP Live Streaming 协议获得的流媒体。服务器
HTTP Live Streaming(缩写是HLS)是一个由苹果公司提出的基于HTTP的流媒体网络传输协议。是苹果公司QuickTime X和iPhone软件系统的一部分。它的工做原理是把整个流分红一个个小的基于HTTP的文件来下载,每次只下载一些。当媒体流正在播放时,客户端能够选择从许多不一样的备用源中以不一样的速率下载一样的资源,容许流媒体会话适应不一样的数据速率。在开始一个流媒体会话时,客户端会下载一个包含元数据的extended M3U (m3u8)playlist文件,用于寻找可用的媒体流。网络
HLS只请求基本的HTTP报文,与实时传输协议(RTP)不一样,HLS能够穿过任何容许HTTP数据经过的防火墙或者代理服务器。它也很容易使用内容分发网络来传输媒体流。数据结构
苹果公司把HLS协议做为一个互联网草案(逐步提交),在第一阶段中已做为一个非正式的标准提交到IETF。可是,即便苹果偶尔地提交一些小的更新,IETF却没有关于制定此标准的有关进一步的动做。async
AVPlayer 只管理一个单独资源的播放,其子类 AVQueuePlayer 能够管理资源队列。ide
AVPlayerLayer 构建于 Core Animation 之上,扩展了 Core Animation 的 CALayer 类,不提供除内容渲染面之外的任何可视化控件,支持的自定义属性只有 video gravity,能够选择 AVLayerVideoGravityResizeAspect、AVLayerVideoGravityResizeAspectFill、AVLayerVideoGravityResize 三个值,分别是等比例彻底展现,等比例彻底铺满,和不等比例彻底铺满。性能
AVAsset 只包含媒体资源的静态信息,AVPlayerItem 能够创建媒体资源动态视角的数据模型,并保存 AVPlayer 播放状态。ui
从一个待播放的 AVAsset 开始,须要作如下初始化操做网络传输协议
self.avPlayerItem = [AVPlayerItem playerItemWithAsset:self.targetAVAsset];
self.avPlayer = [AVPlayer playerWithPlayerItem:self.avPlayerItem];
self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer];
[self.layer addSublayer:self.avPlayerLayer];
复制代码
依次建立 AVPlayerItem、AVPlayer 和 AVPlayerLayer 三个对象,最终将 AVPlaterLayer 加入到待展现内容的 view 上。可是此时不能当即播放,AVPlayerItem 有一个 status 状态,用于标识当前视频是否准备好被播放,须要监听这一个属性。spa
[RACObserve(self.avPlayerItem, status) subscribeNext:^(id x) {
@strongify(self);
if (self.avPlayerItem.status == AVPlayerItemStatusReadyToPlay) {
// 视频准备就绪
if (self.autoPlayMode) {
self.playerButton.hidden = YES;
[self beginPlay];
} else {
self.playerButton.enabled = YES;
self.playerButton.hidden = NO;
}
}else if (self.avPlayerItem.status == AVPlayerItemStatusFailed){
NSLog(@"failed");
}
}];
复制代码
使用浮点型数据类型来表示时间在视频播放时会因为数据不精确、多时间计算累加致使时间明显偏移,是的数据流没法同步,且不能作到自我描述,在不一样的时间轴进行比较和运算时比较困难。因此 AVFoundation 使用 CMTime 数据结构来表示时间。
typedef struct
{
CMTimeValue value;
CMTimeScale timescale;
CMTimeFlags flags;
CMTimeEpoch epoch; /* CMTime 结构体的纪元数量一般设置为 0,可是你能够用它来区分不相关的时间轴。例如,纪元能够经过使用演示循环每一个周期递增,区分循环0中的时间 N与循环1中的时间 N。*/
} CMTime;
复制代码
CMTime 对时间的描述就是 time = value/timescale。
UIView 寄宿在 CALayer 实例之上,能够继承 UIView 覆写其类方法 + (Class)layerClass
返回特定类型的 CALayer,这样 UIView 在初始化时就会选择此类型来建立宿主 Layer。
+ (Class)layerClass {
return [AVPlayerLayer class];
}
复制代码
接下来在自定义初始化方法里直接传入一个 AVPlayer 对象就能够对 UIView 的根 layer 设置 AVPlayer 属性了。
- (id)initWithPlayer:(AVPlayer *)player {
self = [super initWithFrame:CGRectZero];
if (self) {
self.backgroundColor = [UIColor blackColor];
[(AVPlayerLayer *) [self layer] setPlayer:player];
}
return self;
}
复制代码
能够在加载 AVPlayerItem 时选择一些元数据 key 值进行加载,形式以下
NSArray *keys = @[
@"tracks",
@"duration",
@"commonMetadata",
@"availableMediaCharacteristicsWithMediaSelectionOptions"
];
self.playerItem = [AVPlayerItem playerItemWithAsset:self.asset
automaticallyLoadedAssetKeys:keys];
self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
self.playerView = [[THPlayerView alloc] initWithPlayer:self.player];
复制代码
这样能够在加载 AVPlayerItem 时同时加载音轨、时长、common 元数据和备用。
初始化 AVPlayerItem 以后须要等待其状态变为 AVPlayerItemStatusReadyToPlay,所以须要进行监听
[RACObserve(self.avPlayerItem, status) subscribeNext:^(id x) {
@strongify(self);
if (self.avPlayerItem.status == AVPlayerItemStatusReadyToPlay) {
// 视频准备就绪
CMTime duration = self.playerItem.duration;
[self.player play];
} else if (self.avPlayerItem.status == AVPlayerItemStatusFailed){
// 视频没法播放
}
}];
复制代码
对于播放时间的监听,AVPlayer 提供了两个方法
self.intervalObserver = [self.avPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1, 2) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
NSLog(@"%f", CMTimeGetSeconds(time));
}];
复制代码
这个方法以必定时间间隔,发送消息到指定队列,这里要求队列必须是串行队列,回调 block 的参数是一个用 CMTime 表示的播放器的当前时间。
self.intervalObserver = [self.avPlayer addBoundaryTimeObserverForTimes:@[[NSValue valueWithCMTime:CMTimeMake(1, 2)], [NSValue valueWithCMTime:CMTimeMake(2, 2)]] queue:dispatch_get_main_queue() usingBlock:^{
NSLog(@"..");
}];
复制代码
这个方法接受一个 CMTime 组成的数组,当到达数组包含的边界点时触发回调 block,但 block 不提供当前的 CMTime 值。
同时要注意对监听的释放
if (self.intervalObserver){
[self.avPlayer removeTimeObserver:self.intervalObserver];
}
复制代码
视频播放结束时会发出 AVPlayerItemDidPlayToEndTimeNotification 通知,能够注册此通知来获知视频已经播放结束
[[NSNotificationCenter defaultCenter] addObserverForName:AVPlayerItemDidPlayToEndTimeNotification object:self.avPlayerItem queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
NSLog(@"did play to end");
}];
复制代码
还有一种办法是监听 AVPlayer 的速度 rate,当速度降为 0 时,判断当前时间与总时长的关系
@weakify(self);
[RACObserve(self.avPlayer, rate) subscribeNext:^(id x) {
@strongify(self);
float currentTime = CMTimeGetSeconds(self.avPlayerItem.currentTime);
float durationTime = CMTimeGetSeconds(self.avPlayerItem.duration);
if (self.avPlayer.rate == 0 && currentTime >= durationTime) {
dispatch_async(dispatch_get_main_queue(), ^{
[self endPlayer];
});
}
}];
复制代码
咱们用一个 UISlider 来控制视频播放,UISlider 有三个事件能够加入 selector,分别是
_scrubberSlider = [[UISlider alloc] init];
[_scrubberSlider addTarget:self action:@selector(sliderValueChange) forControlEvents:UIControlEventValueChanged];
[_scrubberSlider addTarget:self action:@selector(sliderStop) forControlEvents:UIControlEventTouchUpInside];
[_scrubberSlider addTarget:self action:@selector(sliderBegin) forControlEvents:UIControlEventTouchDown];
复制代码
同时获取到视频大小后能够设置 slider 的 value 属性
self.scrubberSlider.minimumValue = 0.0;
self.scrubberSlider.maximumValue = CMTimeGetSeconds(self.avPlayerItem.duration);
复制代码
接下来是三个 selector 的实现
- (void)sliderBegin
{
[self pausePlayer];
}
- (void)sliderValueChange
{
[self.avPlayerItem cancelPendingSeeks];
[self.avPlayerItem seekToTime:CMTimeMakeWithSeconds(self.scrubberSlider.value, NSEC_PER_SEC)];
}
- (void)sliderStop
{
[self beginPlay];
}
复制代码
其中当滑动开始时要暂时中止视频播放,滑动过程当中出于性能考虑,调用 cancelPendingSeeks 方法,它能取消以前全部的 seekTime 操做,而后再根据 slider 的 value 值去进行 seekToTime 操做,最后滑动结束后恢复播放。
AVAssetImageGenerator 能够用来生成一个视频的固定时间点的图片序列集合,其具体使用以下。
首先初始化一个 AVAssetImageGenerator 对象
self.imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:targetAVAsset];
self.imageGenerator.maximumSize = CGSizeMake(400.0f, 0.0f);
[self.imageGenerator setRequestedTimeToleranceBefore:kCMTimeZero];
[self.imageGenerator setRequestedTimeToleranceAfter:kCMTimeZero];
复制代码
setRequestedTimeToleranceBefore 和 setRequestedTimeToleranceAfter 方法能够设置获取的帧时值偏移程度,越精确对性能要求越高。
而后生成一串时值数组
CMTime duration = self.targetAVAsset.duration;
NSMutableArray *times = [NSMutableArray array];
CMTimeValue increment = duration.value / 20;
CMTimeValue currentValue = 2.0 * duration.timescale;
while (currentValue <= duration.value) {
CMTime time = CMTimeMake(currentValue, duration.timescale);
[times addObject:[NSValue valueWithCMTime:time]];
currentValue += increment;
}
__block NSUInteger imageCount = times.count;
__block NSMutableArray *images = [NSMutableArray array];
复制代码
最后调用方法生成图片
[self.imageGenerator generateCGImagesAsynchronouslyForTimes:times completionHandler:^(CMTime requestedTime, CGImageRef _Nullable imageref, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * _Nullable error) {
if (result == AVAssetImageGeneratorSucceeded) {
UIImage *image = [UIImage imageWithCGImage:imageref];
[images addObject:image];
} else {
NSLog(@"Error: %@", [error localizedDescription]);
}
if (--imageCount == 0) {
}
}];
复制代码
AVMediaSelectionOption 用于标识 AVAsset 的备用媒体呈现方式,包含备用音频、视频或文本轨道,这些轨道多是特定语言的音频轨道、备用相机角度或字幕。
首先经过 AVAsset 的 availableMediaCharacteristicsWithMediaSelectionOptions 属性来获取当前视频的全部备用轨道,返回的字符串多是 AVMediaCharacteristicVisual(备用视频轨道)、AVMediaCharacteristicAudible(备用音频轨道)、AVMediaCharacteristicLegible(字幕)等。
获取到此数组后,经过 mediaSelectionGroupForMediaCharacteristic 获取到对应类型轨道包含的全部轨道的组合 AVMediaSelectionGroup,而后遍历 AVMediaSelectionGroup 的 options 属性能够获取到全部的 AVMediaSelectionOption 对象。获得 AVMediaSelectionOption 对象后就能够进行 AVPlayerItem 的属性设置了。
NSString *mc = AVMediaCharacteristicLegible;
AVMediaSelectionGroup *group = [self.asset mediaSelectionGroupForMediaCharacteristic:mc];
if (group) {
NSMutableArray *subtitles = [NSMutableArray array];
for (AVMediaSelectionOption *option in group.options) {
[subtitles addObject:option.displayName];
}
// 获取到全部支持的字幕名称
} else {
}
NSString *mc = AVMediaCharacteristicLegible;
AVMediaSelectionGroup *group = [self.asset mediaSelectionGroupForMediaCharacteristic:mc];
BOOL selected = NO;
for (AVMediaSelectionOption *option in group.options) {
if ([option.displayName isEqualToString:subtitle]) {
[self.playerItem selectMediaOption:option inMediaSelectionGroup:group];
// 匹配后设置字幕属性
}
}
[self.playerItem selectMediaOption:nil inMediaSelectionGroup:group];// 设置为 nil 能够取消字幕
复制代码