自定义播放器

一.创建一个类用来显示进度信息
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{ CMTimeValuevalue; // 帧数 CMTimeScaletimescale; // 帧率(影片每秒有几帧) CMTimeFlagsflags; CMTimeEpochepoch; } 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 -封装框架

    推荐阅读