一.创建一个类用来显示进度信息
1.创建一个继承于UIView的类SliderView
2.定义我们需要创建的四个视图变量
/**容器视图*/
@property (nonatomic,strong) UIView *containerView;
/**未播放进度视图*/
@property (nonatomic,strong) UIImageView *bgProgressView;
/**已播放进度视图*/
@property (nonatomic,strong) UIImageView *tintProgressView;
/**进度点视图*/
@property (nonatomic,strong) UIImageView *dotProgressView;
3.因为有多个视图要创建,所以我们抽出来形成一个方法
#pragma mark -------返回一个图片视图 ---------
-(UIImageView *)viewWithFrame:(CGRect)frame color:(UIColor *)color{
//创建视图
UIImageView *imgView = [[UIImageView alloc] initWithFrame:frame];
//设置背景颜色
imgView.backgroundColor = color;
//显示
[self.containerView addSubview:imgView];
return imgView;
}
4.重写initWithFrame方法,创建四个视图
#pragma mark -------重写initWithFrame方法 布局 ---------
-(instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
//创建容器视图
self.containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, frame .size.height)];
//背景颜色
_containerView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.2];
//显示
[self addSubview:_containerView];
//未播放进度视图
self.bgProgressView = [self viewWithFrame:CGRectMake(kSize, (self.frame.size.height-kProgressHeight)/2.0, frame.size.width-2*kSize, kProgressHeight) color:[UIColor lightGrayColor]];
//圆角
_bgProgressView.layer.cornerRadius = 2.5;
//已播放进度视图
self.tintProgressView = [self viewWithFrame:CGRectMake(kSize, (self.frame.size.height-kProgressHeight)/2.0, 0, kProgressHeight) color:[UIColor orangeColor]];
//圆角
_tintProgressView.layer.cornerRadius = 2.5;
//进度点视图
self.dotProgressView = [self viewWithFrame:CGRectMake(0, 0, 16, 16) color:[UIColor orangeColor]];
//进度点视图中心点移动到最左边
_dotProgressView.center = CGPointMake(kSize, self.frame.size.height/2.0);
//设置圆角
_dotProgressView.layer.cornerRadius = 8;
}
return self;
}
5.在initWithFrame方法中添加滑动手势
//添加滑动手势
UIPanGestureRecognizer *panGes = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
[self addGestureRecognizer:panGes];
//设置进度条的初始状态是正常
self.status = kProgressStatusNormal;
二.创建一个视图用来显示播放器
1.创建一个继承于UIView的类PlayerView
2.用一个类方法来创建该类,并定义一个变量来保存传递过来的视频的urlString
/**保存传递过来的URL字符串*/
@property (nonatomic,strong) NSString *urlString;
//创建播放器视图
+(PlayerView *)playerViewFrame:(CGRect)frame url:(NSString *)urlString{
//创建
PlayerView *playerView = [[PlayerView alloc] initWithFrame:frame];
//设置背景颜色
playerView.backgroundColor = [UIColor grayColor];
//保存url
playerView.urlString = urlString;
return playerView;
}
3.重写initWithFrame方法,创建进度条视图,控制播放的按钮,显示的文本
//重写initWithFrame方法 布局
-(instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
//创建进度条
self.slider = [[SliderView alloc] initWithFrame:CGRectMake(0, self.frame.size.height-kSliderHeight, self.frame.size.width, kSliderHeight)];
[self addSubview:_slider];
//控制播放的按钮
self.controlBtn = [UIButton buttonWithType:UIButtonTypeCustom];
_controlBtn.frame = CGRectMake(0, 0, 22, 22);
_controlBtn.center = CGPointMake(kSize/2.0, _slider.frame.size.height/2.0);
[_controlBtn setBackgroundImage:[UIImage imageNamed:@"play"] forState:UIControlStateNormal];
[_controlBtn addTarget:self action:@selector(changeStatus:) forControlEvents:UIControlEventTouchUpInside];
[_slider addSubview:_controlBtn];
//显示时间的文本
self.timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(self.slider.frame.size.width-kSize, 0, kSize, self.slider.frame.size.height)];
_timeLabel.text = @"00:00";
_timeLabel.textColor = [UIColor whiteColor];
_timeLabel.font = [UIFont fontWithName:@"Helvetica" size:16];
_timeLabel.textAlignment = NSTextAlignmentCenter;
[_slider addSubview:_timeLabel];
}
return self;
}
//改变播放状态
-(void)changeStatus:(UIButton *)sender{
if (_player.rate == 0) {
//播放
[sender setBackgroundImage:[UIImage imageNamed:@"pause"] forState:UIControlStateNormal];
[self play];
}else{
//暂停
[sender setBackgroundImage:[UIImage imageNamed:@"play"] forState:UIControlStateNormal];
[self pause];
}
}
//播放
-(void)play{
[_player play];
}
//暂停
-(void)pause{
[_player pause];
}
AVPlayer
有一个属性rate
表示视频播放的速度,所以当rate为0时,可以判断视频不在播放
4.当urlString一有了数据,就可以用来播放了
//重写urlString的set方法
-(void)setUrlString:(NSString *)urlString{
_urlString = urlString;
//创建播放器
self.player = [AVPlayer playerWithURL:[NSURL URLWithString:urlString]];
//创建显示图层
AVPlayerLayer *layer = [AVPlayerLayer playerLayerWithPlayer:_player];
layer.frame = self.bounds;
[self.layer insertSublayer:layer atIndex:0];
//防止循环引用
__block typeof(self) weakSelf = self;
//监听播放进度改变的消息
[_player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(1, NSEC_PER_SEC) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
//获取当前播放的比例
CGFloat progress = CMTimeGetSeconds(_player.currentTime)/CMTimeGetSeconds(_player.currentItem.duration);
//改变滑动视图
weakSelf.slider.progress = progress;
//改变时间
int playTime = CMTimeGetSeconds(_player.currentItem.duration)*progress;
weakSelf.timeLabel.text = [weakSelf timeStringWithSecond:playTime];
}];
}
AVPlayer 给我们直接提供了观察播放进度的方法-添加周期时间观察者,简而言之就是,每隔一段时间后执行 block
- (id)addPeriodicTimeObserverForInterval:(CMTime)interval
queue:(nullable dispatch_queue_t)queue
usingBlock:(void (^)(CMTime time))block;
使用这个方法还需要了解专门用于标识电影时间的结构体CMTime
typedef struct{
CMTimeValue value; // 帧数
CMTimeScale timescale; // 帧率(影片每秒有几帧)
CMTimeFlags flags;
CMTimeEpoch epoch;
} CMTime;
AVPlayerItem 的 duration 属性就是一个 CMTime 类型的数据。 如果我们想要获取影片的总秒数那么就可以用 duration.value / duration.timeScale 计算出来,也可以使用 CMTimeGetSeconds 函数
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,其他一样
5.SliderView接受PlayerView传来的比例,更改已播放视图的宽度以及点视图的位置
/**接受视频播放的比例*/
@property (nonatomic,assign) CGFloat progress;
#pragma mark -------重写progress的set方法,随着视频的播放更改进度 ---------
-(void)setProgress:(CGFloat)progress{
_progress = progress;
//更改进度
[self seekToPoint:progress*self.bgProgressView.frame.size.width];
}
#pragma mark -------进度点移动以及拖拽状态下视频内容随之改变 ---------
-(void)seekToPoint:(CGFloat)current{
//在合理的范围之内
if (current >= 0 && current <= self.bgProgressView.frame.size.width) {
//更改已播放进度视图的宽度
_tintProgressView.frame = CGRectMake(kSize, (self.frame.size.height-kProgressHeight)/2.0, current, kProgressHeight);
//更改进度点的位置
_dotProgressView.center = CGPointMake(kSize+current, self.frame.size.height/2.0);
}
}
6.当我们手动拖拽进度条的时候需要回调给播放器,拖拽的进度。并且拖拽的时候,不能播放,所以我们需要定义一个枚举判断当前进度条的状态,并回调给播放器
相关信息的定义
//判断当前进度条的状态
typedef NS_ENUM(NSInteger,kProgressStatus) {
kProgressStatusDrag, //拖拽
kProgressStatusNormal //正常
};
//定义一个block,用于返回拖拽的比例从而设置视频的跳转
typedef void(^SliderBlock)(CGFloat progress);
//定义一个block,用于返回进度条是否被拖拽,进度条被拖拽的时候是不播放的
typedef void(^ProgressStausBlock)(kProgressStatus status);
/**定义一个回调拖拽比例的block类型的变量*/
@property (nonatomic,copy) SliderBlock sliderBlock;
/**定义一个回调进度条状态的block类型的变量*/
@property (nonatomic,copy) ProgressStausBlock statusBlock;
首先在initWithFrame里面设置当前进度条的状态
//设置进度条的初始状态是正常
self.status = kProgressStatusNormal;
定义拖动手势的方法
#pragma mark -------滑动手势 拖动快进快退 ---------
-(void)pan:(UIPanGestureRecognizer *)panGesture{
//获得触摸点
CGPoint location = [panGesture locationInView:self.bgProgressView];
if (panGesture.state == UIGestureRecognizerStateBegan) {
//开始滑动
//进入拖拽状态 暂停播放
self.status = kProgressStatusDrag;
if (self.statusBlock) {
self.statusBlock(kProgressStatusDrag);
}
}else if(panGesture.state == UIGestureRecognizerStateChanged){
//滑动过程中
[self seekToPoint:location.x];
}else if(panGesture.state == UIGestureRecognizerStateEnded){
//滑动结束
//进入正常状态 开始播放
self.status = kProgressStatusNormal;
if (self.statusBlock) {
self.statusBlock(kProgressStatusNormal);
}
}
}
完善seekToPoint方法
#pragma mark -------进度点移动以及拖拽状态下视频内容随之改变 ---------
-(void)seekToPoint:(CGFloat)current{
//在合理的范围之内
if (current >= 0 && current <= self.bgProgressView.frame.size.width) {
//更改已播放进度视图的宽度
_tintProgressView.frame = CGRectMake(kSize, (self.frame.size.height-kProgressHeight)/2.0, current, kProgressHeight);
//更改进度点的位置
_dotProgressView.center = CGPointMake(kSize+current, self.frame.size.height/2.0);
//拖拽情况下才需要跳转视频
if (self.status == kProgressStatusDrag) {
if (self.sliderBlock) {
self.sliderBlock(current/self.bgProgressView.frame.size.width);
}
}
}
}
7.播放器接受相关信息的回调并作出相关的反应
//重写initWithFrame方法 布局
-(instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
//创建进度条
self.slider = [[SliderView alloc] initWithFrame:CGRectMake(0, self.frame.size.height-kSliderHeight, self.frame.size.width, kSliderHeight)];
[self addSubview:_slider];
//防止循环引用
__block typeof(self) weakSelf = self;
//进度条的状态
[_slider setStatusBlock:^(kProgressStatus status) {
if (status == kProgressStatusNormal) {
//正常播放
[weakSelf play];
//调整按钮图片
[weakSelf.controlBtn setBackgroundImage:[UIImage imageNamed:@"pause"] forState:UIControlStateNormal];
}else{
//暂停播放
[weakSelf pause];
//调整按钮图片
[weakSelf.controlBtn setBackgroundImage:[UIImage imageNamed:@"play"] forState:UIControlStateNormal];
}
}];
//进度条拖拽的比例
[_slider setSliderBlock:^(CGFloat progress) {
//获得视频应该显示那一片段的时间点
int time = CMTimeGetSeconds(self.player.currentItem.duration)*progress;
//跳转到相应的片段
[weakSelf.player seekToTime:CMTimeMake(time*NSEC_PER_SEC, NSEC_PER_SEC) completionHandler:^(BOOL finished) {
}];
}];
}
return self;
}
8.通过点击进度视图,达到快进快退的效果
#pragma mark -------触摸事件 点击快进快退---------
//触摸开始
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
//获得触摸点
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.bgProgressView];
//暂停播放
self.status = kProgressStatusDrag;
if (self.statusBlock) {
self.statusBlock(kProgressStatusDrag);
}
//跳转
[self seekToPoint:location.x];
}
//触摸结束
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
//开始播放
self.status = kProgressStatusNormal;
if (self.statusBlock) {
self.statusBlock(kProgressStatusNormal);
}
}
9.加载播放器视图
- (void)viewDidLoad {
[super viewDidLoad];
//创建
self.playerView = [PlayerView playerViewFrame:CGRectMake(0, (self.view.frame.size.height-Height)/2.0, self.view.frame.size.width, Height) url:@"http://127.0.0.1/upLoad/video/abc.mov"];
//显示
[self.view addSubview:_playerView];
}
三.运行结果
demo链接
https://pan.baidu.com/s/1kITSz83zkCTQg86ZPOJfJA 密码:3kn8
参考文章
https://www.jianshu.com/p/6cb137340732 -基本使用
https://www.jianshu.com/p/11e05d684c05 -封装框架