基于 AVPlayer 自定义播放器
如果我只是简单的播放一个视频,而不需要考虑播放器的界面。iOS9.0 之前使用 MPMoviePlayerController
, 或者内部自带一个 view 的 MPMoviePlayerViewController
. iOS9.0 之后,可以使用 AVPictureInPictureController
, AVPlayerViewController
, 或者 WKWebView
。
以上系统提供的播放器由于高度的封装性, 使得自定义播放器变的很难。 所以,如果我需要自定义播放器样式的时候,可以使用 AVPlayer
。 AVPlayer 存在于 AVFoundtion 中,更接近于底层,也更加灵活。
Representing and Using Media with AVFoundation
AVFoundtion 框架中主要使用 AVAsset
类来展示媒体信息,比如: title, duration, size 等等。
AVAsset : 存储媒体信息的一个抽象类,不能直接使用。
AVURLAsset : AVAsset 的一个子类,使用 URL 进行实例化,实例化对象包换 URL 对应视频资源的所有信息。
AVPlayerItem : 有一个属性为 asset。起到观察和管理视频信息的作用。 比如,asset, tracks , status, duration ,loadedTimeRange 等。
我的理解是, AVPlayItem 相当于 Model 层,包含 Media 的信息和播放状态,并提供这些数据给视频观察者 比如:属性 asset
,URL视频的信息. loadedTimeRanges
,已缓冲进度。
AVPlayerItem 使用
1. 初始化
playerItemWithURL
或者 initWithURL:
在使用 AVPlayer 播放视频时,提供视频信息的是 AVPlayerItem,一个 AVPlayerItem 对应着一个URL视频资源。
初始化一个 AVPlayItem 对象后,其属性并不是马上就可以使用。我们必须确保 AVPlayerItem 已经被加载好了,可以播放了,才能使用。 毕竟凡是和网络扯上关系的都需要时间去加载。 那么,什么时候属性才能正常使用呢。 官方文档给出了解决方案:
直到 AVPlayerItem 的
status
属性为AVPlayerItemStatusReadyToPlay
.使用 KVO 键值观察者,其属性。
因此我们在使用的时候,使用 URL 初始化 AVPlayerItem 后,还要给它添加观察者。
2. 添加观察者
AVPlayreItem 的属性需要当 status 为 ReadyToPlay 的时候才可以正常使用。
观察status属性
[_playerItem addObserver:self forKeyPath:@"status" options:(NSKeyValueObservingOptionNew) context:nil]; // 观察status属性,
观察loadedTimeRanges
如果想做缓冲进度条,显示当前视频的缓存进度,则需要观察 loadedTimeRanges
.
[_playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil]; // 观察缓冲进度
AVPlayer & AVPlayerLayer
AVPlayer创建方式
AVPlayer 有三种创建方式:
init
,initWithURL:
,initWithPlayerItem:
(URL,Item遍历构造器方法)
使用 AVPlayer 时需要注意,AVPlayer 本身并不能显示视频, 显示视频的是 AVPlayerLayer。 AVPlayerLayer 继承自 CALayer,添加到 view.layer 上就可以使用了。
AVPlayerLayer创建方式
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
[superlayer addSublayer:playerLayer];
AVPlayerLayer 显示视频,AVPlayerItem 提供视频信息, AVPlayer 管理和调控。 这是不是非常熟悉。 我觉得这里也体现了 MVC 的思想(虽然AVPlayer继承自NSObject), 把响应层, 显示层, 信息层, 三层分离了。 明确了每层做的任务,使用起来就会更加得心应手。
使用 AVPlayer 的核心,在于 AVPlayer 和 AVPlayerItem, AVPlayerLayer 添加到视图的layer 上后,就没有什么事儿了。 思考一下,整个播放视频的步骤。
首先,得到视频的URL
根据URL创建AVPlayerItem
把AVPlayerItem 提供给 AVPlayer
AVPlayerLayer 显示视频。
AVPlayer 控制视频, 播放, 暂停, 跳转 等等。
播放过程中获取缓冲进度,获取播放进度。
视频播放完成后做些什么,是暂停还是循环播放,还是获取最后一帧图像。
播放步骤
1. 布局页面,初始化 AVPlayer 和 AVPlayerLayer
// setAVPlayer
self.player = [[AVPlayer alloc] init];
_playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
[self.playerView.layer addSublayer:_playerLayer];
2. 根据 URL 获取 AVPayerItem,并替换 AVPlayer 的 AVPlayerItem
在第一步,布局初始化时,AVPlayer 并没有 AVPlayerItem,AVPlayer 提供了 - (void)replaceCurrentItemWithPlayerItem:(nullable AVPlayerItem *)item;
方法,用于切换视频。
- (void)updatePlayerWithURL:(NSURL *)url {
_playerItem = [AVPlayerItem playerItemWithURL:url]; // create item
[_player replaceCurrentItemWithPlayerItem:_playerItem]; // replaceCurrentItem
[self addObserverAndNotification]; // 注册观察者,通知
}
3. KVO 获取视频信息, 观察缓冲进度
观察 AVPlayerItem 的 status
属性,当状态变为 AVPlayerStatusReadyToPlay
时才可以使用。
也可以观察 loadedTimeRanges
获取缓冲进度
注册观察者:
[_playerItem addObserver:self forKeyPath:@"status" options:(NSKeyValueObservingOptionNew) context:nil]; // 观察status属性
执行观察者方法:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
AVPlayerItem *item = (AVPlayerItem *)object;
if ([keyPath isEqualToString:@"status"]) {
AVPlayerStatus status = [[change objectForKey:@"new"] intValue]; // 获取更改后的状态
if (status == AVPlayerStatusReadyToPlay) {
CMTime duration = item.duration; // 获取视频长度
// 设置视频时间
[self setMaxDuration:CMTimeGetSeconds(duration)];
// 播放
[self play];
} else if (status == AVPlayerStatusFailed) {
NSLog(@"AVPlayerStatusFailed");
} else {
NSLog(@"AVPlayerStatusUnknown");
} } else if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
NSTimeInterval timeInterval = [self availableDurationRanges]; // 缓冲时间
CGFloat totalDuration = CMTimeGetSeconds(_playerItem.duration); // 总时间
[self.loadedProgress setProgress:timeInterval / totalDuration animated:YES]; // 更新缓冲条
}
}
4. 播放过程中响应:播放、 暂停、 跳转
AVPlayer 提供了 play
, pause
, 和 - (void)seekToTime:(CMTime)time completionHandler:(void (^)(BOOL finished))completionHandler
方法。
在看 AVPlayer 的 seekToTime 之前,先来认识一个结构体。
CMTime 是专门用于标识电影时间的结构体.
typedef struct{
CMTimeValue value; // 帧数
CMTimeScale timescale; // 帧率(影片每秒有几帧)
CMTimeFlags flags;
CMTimeEpoch epoch;
} CMTime;
AVPlayerItem 的 duration 属性就是一个 CMTime 类型的数据。 如果我们想要获取影片的总秒数那么就可以用 duration.value / duration.timeScale 计算出来。也可以使用 CMTimeGetSeconds 函数
CMTimeGetSeconds(CMtime time)
double seconds = CMTimeGetSeconds(item.duration); // 相当于 duration.value / duration.timeScale
如果一个影片为60frame(帧)每秒, 当前想要跳转到 120帧的位置,也就是两秒的位置,那么就可以创建一个 CMTime 类型数据。
CMTime,通常用如下两个函数来创建.
CMTimeMake(int64_t value, int32_t scale)
CMTime time1 = CMTimeMake(120, 60);
CMTimeMakeWithSeconds(Flout64 seconds, int32_t scale)
CMTime time2 = CMTimeWithSeconds(120, 60);
CMTimeMakeWithSeconds 和CMTimeMake 区别在于,第一个函数的第一个参数可以是float,其他一样。
拖拽方法如下:
- (IBAction)playerSliderValueChanged:(id)sender {
_isSliding = YES;
[self pause]; // 跳转到拖拽秒处
// self.playProgress.maxValue = value / timeScale
// value = progress.value * timeScale
// CMTimemake(value, timeScale) = (progress.value, 1.0)
CMTime changedTime = CMTimeMakeWithSeconds(self.playProgress.value, 1.0);
[_playerItem seekToTime:changedTime completionHandler:^(BOOL finished) {
// 跳转完成后
}];
}
5. 观察 AVPlayer 播放进度
AVPlayerItem 是使用 KVO 模式观察状态,和 缓冲进度。而 AVPlayer 给我们直接提供了 观察播放进度更为方便的方法。
- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;
方法名如其意, “添加周期时间观察者” ,参数1 interal
为CMTime 类型的,参数2 为一个 返回值为空,参数为 CMTime 的block类型。
简而言之就是,每隔一段时间后执行 block。
比如: 我把时间间隔设置为, 1/ 30 秒,然后 block 里面更新 UI。就是一秒钟更新 30次UI。
播放进度代码如下:
// 观察播放进度
- (void)monitoringPlayback:(AVPlayerItem *)item {
__weak typeof(self)WeakSelf = self; // 观察间隔, CMTime 为30分之一秒
_playTimeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMake(1, 30.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
if (_touchMode != TouchPlayerViewModeHorizontal) {
// 获取 item 当前播放秒
float currentPlayTime = (double)item.currentTime.value/ item.currentTime.timescale;
// 更新slider, 如果正在滑动则不更新
if (_isSliding == NO) {
[WeakSelf updateVideoSlider:currentPlayTime];
}
} else {
return;
}
}];
}
注意: 给 palyer 添加了 timeObserver 后,不使用的时候记得移除 removeTimeObserver
否则会占用大量内存。
比如,我在dealloc里面做了移除:
- (void)dealloc {
[self removeObserveAndNOtification];
[_player removeTimeObserver:_playTimeObserver]; // 移除playTimeObserver}
6. AVPlayerItem 通知
AVPlaerItem 播放完成后,系统会自动发送通知,通知的定义详情请见 AVPlayerItem.h
.
/* 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
因此,如果我们想要在某个状态下,执行某些操作。监听 AVPlayerItem 的相关通知就行了。 比如,我想要播放完成后,暂停播放。 给AVPlayerItemDidPlayToEndTimeNotification
添加观察者。
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil]; // 播放完成后
- (void)playbackFinished:(NSNotification *)notification {
NSLog(@"视频播放完成通知");
_playerItem = [notification object];
[_playerItem seekToTime:kCMTimeZero]; // item 跳转到初始
//[_player play]; // 循环播放
}
最后
使用 AVPlayer 的时候,一定要注意 AVPlayer 、 AVPlayerLayer 和 AVPlayerItem 三者之间的关系。 AVPlayer 负责控制播放, layer 显示播放, item 提供数据,当前播放时间, 已加载情况。 Item 中一些基本的属性, status, duration, loadedTimeRanges, currentTime(当前播放时间)。
最重要的还是多总结,6月份写的这个 Demo ,现在才总结,懒癌到晚期 =。 =
当然,如果我写的文章有幸让你看到了最后,那么, 或许 你想要更多的功能。比如,横竖屏旋转,一些交互动画等等。 我有个简单地 demo,实现了一些小小的功能,放到了 github 上, 里面还有很多不足,多沟通交流 = W = 。
基于 AVPlayer 自定义播放器的更多相关文章
- 基于ffmpeg网络播放器的教程与总结
基于ffmpeg网络播放器的教程与总结 一. 概述 为了解决在线无广告播放youku网上的视频.(youku把每个视频切换成若干个小视频). 视频资源解析可以从www.flvcd. ...
- 使用html5中video自定义播放器必备知识点总结以及JS全屏API介绍
一.video的js知识点: controls(控制器).autoplay(自动播放).loop(循环)==video默认的: 自定义播放器中一些JS中提供的方法和属性的记录: 1.play()控制视 ...
- 一个简单的基于 DirectShow 的播放器 2(对话框类)
上篇文章分析了一个封装DirectShow各种接口的封装类(CDXGraph):一个简单的基于 DirectShow 的播放器 1(封装类) 本文继续上篇文章,分析一下调用这个封装类(CDXGrap ...
- 一个简单的基于 DirectShow 的播放器 1(封装类)
DirectShow最主要的功能就是播放视频,在这里介绍一个简单的基于DirectShow的播放器的例子,是用MFC做的,今后有机会可以基于该播放器开发更复杂的播放器软件. 注:该例子取自于<D ...
- 从零开始学 Web 之 HTML5(四)拖拽接口,Web存储,自定义播放器
大家好,这里是「 从零开始学 Web 系列教程 」,并在下列地址同步更新...... github:https://github.com/Daotin/Web 微信公众号:Web前端之巅 博客园:ht ...
- 基于VLC的播放器开发
VLC的C++封装 因为工作需要,研究了一段时间的播放器开发,如果从头开始做,可以学习下FFmpeg(http://www.ffmpeg.org/),很多播放器都是基于FFmpeg开发的,但是这样工作 ...
- h5自定义播放器得实现原理
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 用H5和js写一个移动端自定义播放器
前言 由于html5自带的播放器样式不怎么好看,大多数人都是自己写一个来满足业务需求.这一次的需求如下: 1.不要上一曲下一曲 2.有进度条和播放暂停按钮 3.有时间显示 demo实现功能 1.进度条 ...
- Vue + WebRTC 实现音视频直播(附自定义播放器样式)
1. 什么是WebRTC 1.1 WebRTC简介 WebRTC,名称源自网页即时通信(英语:Web Real-Time Communication)的缩写,是一个支持网页浏览器进行实时语音对话或视频 ...
随机推荐
- Neutron 如何支持多种 network provider - 每天5分钟玩转 OpenStack(70)
Neutron 的架构是非常开放的,可以支持多种 network provider,只要遵循一定的设计原则和规范.本节我们将开始讨论这个主题. 先讨论一个简单的场景:在 Neutorn 中使用 lin ...
- android:theme决定AlertDialog的背景颜色
最近遇到一个很奇怪的问题,两个项目弹出的dialog背景颜色不一样,一个是黑色的,一个是白色的,最后发现是AndroidManifest.xml文件里面application指定的android:th ...
- 应用程序框架实战十八:DDD分层架构之聚合
前面已经介绍了DDD分层架构的实体和值对象,本文将介绍聚合以及与其高度相关的并发主题. 我在之前已经说过,初学者第一步需要将业务逻辑尽量放到实体或值对象中,给实体“充血”,这样可以让业务逻辑高度内聚, ...
- 1、NoSQL概述
最近抽时间把Redis学了一下,所以就在网上找了一些资料.然后找到尚硅谷-周阳老师的视频教程,觉得里面的讲的挺好.所以就把他视频当中的资料教程整理出来. 单机MySQL的美好时代 在90年代,一个网站 ...
- JDBC与JAVA数据库编程
一.JDBC的概念 1. JDBC (Java DataBase Connectivity) Java数据库连接 a) 主要提供java数据库应用程序的API支持 2. JDBC的主要功能 a) 创建 ...
- ASP.NET Web API 异常日志记录
如果在 ASP.NET MVC 应用程序中记录异常信息,我们只需要在 Global.asax 的 Application_Error 中添加代码就可以了,比如: public class MvcApp ...
- (十五)WebGIS中平移功能的设计和实现
文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.前言 这一章我们将详细讲解WebGIS工具栏中另一个基础工具——平 ...
- Oracle常用的SQL方法总结
在项目中一般需要对一些数据进行处理,以下提供一些基本的SQL语句: 1.基于条件的插入和修改:需要在表中插入一条记录,插入前根据key标识判断.如果标识符不存在,则插入新纪录,如果标识符存在,则根据语 ...
- Windows Server 2008 下解析二级域名的方法
昨天去了客户那里部署网站,用的是客户那边的windows server 2008. 本文主要以总结问题点的形式来说. 问题1:本机的数据库是SQL SERVER 2008R2,客户那边的数据库是SQL ...
- android防止内存溢出浅析
Android的虚拟机是基于寄存器的Dalvik,它的最大堆大小一般是16M.但是Android采用的是Java语言编写,所以在很大程度上,Android的内存机制等同于Java的内存机制,在刚开始开 ...