自定义播放器

一.创建一个类用来显示进度信息

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 -封装框架

你可能感兴趣的:(自定义播放器)