自定义播放器

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容