自定義播放器

一.創(chuàng)建一個(gè)類用來顯示進(jìn)度信息

1.創(chuàng)建一個(gè)繼承于UIView的類SliderView

2.定義我們需要?jiǎng)?chuàng)建的四個(gè)視圖變量

/**容器視圖*/
@property (nonatomic,strong) UIView *containerView;

/**未播放進(jìn)度視圖*/
@property (nonatomic,strong) UIImageView *bgProgressView;

/**已播放進(jìn)度視圖*/
@property (nonatomic,strong) UIImageView *tintProgressView;

/**進(jìn)度點(diǎn)視圖*/
@property (nonatomic,strong) UIImageView *dotProgressView;

3.因?yàn)橛卸鄠€(gè)視圖要?jiǎng)?chuàng)建司倚,所以我們抽出來形成一個(gè)方法

#pragma mark -------返回一個(gè)圖片視圖 ---------
-(UIImageView *)viewWithFrame:(CGRect)frame color:(UIColor *)color{
    
    //創(chuàng)建視圖
    UIImageView *imgView = [[UIImageView alloc] initWithFrame:frame];
    
    //設(shè)置背景顏色
    imgView.backgroundColor = color;
    
    //顯示
    [self.containerView addSubview:imgView];
    
    return imgView;
}

4.重寫initWithFrame方法,創(chuàng)建四個(gè)視圖

#pragma mark -------重寫initWithFrame方法 布局 ---------
-(instancetype)initWithFrame:(CGRect)frame{
    if (self = [super initWithFrame:frame]) {
        
        //創(chuàng)建容器視圖
        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];
        
        //未播放進(jìn)度視圖
        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;
        
        //已播放進(jìn)度視圖
        self.tintProgressView = [self viewWithFrame:CGRectMake(kSize, (self.frame.size.height-kProgressHeight)/2.0, 0, kProgressHeight) color:[UIColor orangeColor]];
        //圓角
        _tintProgressView.layer.cornerRadius = 2.5;
        
        //進(jìn)度點(diǎn)視圖
        self.dotProgressView = [self viewWithFrame:CGRectMake(0, 0, 16, 16) color:[UIColor orangeColor]];
        //進(jìn)度點(diǎn)視圖中心點(diǎn)移動(dòng)到最左邊
        _dotProgressView.center = CGPointMake(kSize, self.frame.size.height/2.0);
        //設(shè)置圓角
        _dotProgressView.layer.cornerRadius = 8;
    }
    return self;
}

5.在initWithFrame方法中添加滑動(dòng)手勢(shì)

//添加滑動(dòng)手勢(shì)
UIPanGestureRecognizer *panGes = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
[self addGestureRecognizer:panGes];
        
//設(shè)置進(jìn)度條的初始狀態(tài)是正常
self.status = kProgressStatusNormal;

二.創(chuàng)建一個(gè)視圖用來顯示播放器

1.創(chuàng)建一個(gè)繼承于UIView的類PlayerView

2.用一個(gè)類方法來創(chuàng)建該類榛搔,并定義一個(gè)變量來保存?zhèn)鬟f過來的視頻的urlString

/**保存?zhèn)鬟f過來的URL字符串*/
@property (nonatomic,strong) NSString *urlString;

//創(chuàng)建播放器視圖
+(PlayerView *)playerViewFrame:(CGRect)frame url:(NSString *)urlString{
    
    //創(chuàng)建
    PlayerView *playerView = [[PlayerView alloc] initWithFrame:frame];
    
    //設(shè)置背景顏色
    playerView.backgroundColor = [UIColor grayColor];
    
    //保存url
    playerView.urlString = urlString;
    
    return playerView;
}

3.重寫initWithFrame方法棚贾,創(chuàng)建進(jìn)度條視圖,控制播放的按鈕,顯示的文本

//重寫initWithFrame方法 布局
-(instancetype)initWithFrame:(CGRect)frame{
    if (self = [super initWithFrame:frame]) {
        
        //創(chuàng)建進(jìn)度條
        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];
        
        //顯示時(shí)間的文本
        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;
}

//改變播放狀態(tài)
-(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有一個(gè)屬性rate表示視頻播放的速度祈纯,所以當(dāng)rate為0時(shí),可以判斷視頻不在播放

4.當(dāng)urlString一有了數(shù)據(jù)叼耙,就可以用來播放了

//重寫urlString的set方法
-(void)setUrlString:(NSString *)urlString{
    
    _urlString = urlString;
    
    //創(chuàng)建播放器
    self.player = [AVPlayer playerWithURL:[NSURL URLWithString:urlString]];
    
    //創(chuàng)建顯示圖層
    AVPlayerLayer *layer = [AVPlayerLayer playerLayerWithPlayer:_player];
    layer.frame = self.bounds;
    [self.layer insertSublayer:layer atIndex:0];
    
    //防止循環(huán)引用
    __block typeof(self) weakSelf = self;
    
    //監(jiān)聽播放進(jìn)度改變的消息
    [_player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(1, NSEC_PER_SEC) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
       
        //獲取當(dāng)前播放的比例
        CGFloat progress = CMTimeGetSeconds(_player.currentTime)/CMTimeGetSeconds(_player.currentItem.duration);
        
        //改變滑動(dòng)視圖
        weakSelf.slider.progress = progress;
        
        //改變時(shí)間
        int playTime = CMTimeGetSeconds(_player.currentItem.duration)*progress;
        weakSelf.timeLabel.text = [weakSelf timeStringWithSecond:playTime];
        
    }];
}

AVPlayer 給我們直接提供了觀察播放進(jìn)度的方法-添加周期時(shí)間觀察者,簡(jiǎn)而言之就是腕窥,每隔一段時(shí)間后執(zhí)行 block

- (id)addPeriodicTimeObserverForInterval:(CMTime)interval 
                queue:(nullable dispatch_queue_t)queue 
                     usingBlock:(void (^)(CMTime time))block;

使用這個(gè)方法還需要了解專門用于標(biāo)識(shí)電影時(shí)間的結(jié)構(gòu)體CMTime

typedef struct{    
    CMTimeValue    value;     // 幀數(shù)    
    CMTimeScale    timescale;  // 幀率(影片每秒有幾幀)    
    CMTimeFlags    flags;            
    CMTimeEpoch    epoch;
} CMTime;

AVPlayerItem 的 duration 屬性就是一個(gè) CMTime 類型的數(shù)據(jù)。 如果我們想要獲取影片的總秒數(shù)那么就可以用 duration.value / duration.timeScale 計(jì)算出來筛婉,也可以使用 CMTimeGetSeconds 函數(shù)

double seconds = CMTimeGetSeconds(item.duration); 
// 相當(dāng)于 duration.value / duration.timeScale

如果一個(gè)影片為60frame(幀)每秒簇爆, 當(dāng)前想要跳轉(zhuǎn)到 120幀的位置,也就是兩秒的位置爽撒,那么就可以創(chuàng)建一個(gè) CMTime 類型數(shù)據(jù)

CMTime,通常用如下兩個(gè)函數(shù)來創(chuàng)建

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 區(qū)別在于入蛆,第一個(gè)函數(shù)的第一個(gè)參數(shù)可以是float,其他一樣

5.SliderView接受PlayerView傳來的比例硕勿,更改已播放視圖的寬度以及點(diǎn)視圖的位置

/**接受視頻播放的比例*/
@property (nonatomic,assign) CGFloat progress;

#pragma mark -------重寫progress的set方法哨毁,隨著視頻的播放更改進(jìn)度 ---------
-(void)setProgress:(CGFloat)progress{
    
    _progress = progress;
    
    //更改進(jìn)度
    [self seekToPoint:progress*self.bgProgressView.frame.size.width];
}

#pragma mark -------進(jìn)度點(diǎn)移動(dòng)以及拖拽狀態(tài)下視頻內(nèi)容隨之改變 ---------
-(void)seekToPoint:(CGFloat)current{
    
    //在合理的范圍之內(nèi)
    if (current >= 0 && current <= self.bgProgressView.frame.size.width) {
        
        //更改已播放進(jìn)度視圖的寬度
        _tintProgressView.frame = CGRectMake(kSize, (self.frame.size.height-kProgressHeight)/2.0, current, kProgressHeight);
        
        //更改進(jìn)度點(diǎn)的位置
        _dotProgressView.center = CGPointMake(kSize+current, self.frame.size.height/2.0);
    }
}

6.當(dāng)我們手動(dòng)拖拽進(jìn)度條的時(shí)候需要回調(diào)給播放器,拖拽的進(jìn)度首尼。并且拖拽的時(shí)候挑庶,不能播放,所以我們需要定義一個(gè)枚舉判斷當(dāng)前進(jìn)度條的狀態(tài)软能,并回調(diào)給播放器

相關(guān)信息的定義

//判斷當(dāng)前進(jìn)度條的狀態(tài)
typedef NS_ENUM(NSInteger,kProgressStatus) {
    kProgressStatusDrag, //拖拽
    kProgressStatusNormal //正常
};

//定義一個(gè)block迎捺,用于返回拖拽的比例從而設(shè)置視頻的跳轉(zhuǎn)
typedef void(^SliderBlock)(CGFloat progress);

//定義一個(gè)block,用于返回進(jìn)度條是否被拖拽查排,進(jìn)度條被拖拽的時(shí)候是不播放的
typedef void(^ProgressStausBlock)(kProgressStatus status);

/**定義一個(gè)回調(diào)拖拽比例的block類型的變量*/
@property (nonatomic,copy) SliderBlock sliderBlock;

/**定義一個(gè)回調(diào)進(jìn)度條狀態(tài)的block類型的變量*/
@property (nonatomic,copy) ProgressStausBlock statusBlock;

首先在initWithFrame里面設(shè)置當(dāng)前進(jìn)度條的狀態(tài)

//設(shè)置進(jìn)度條的初始狀態(tài)是正常
self.status = kProgressStatusNormal;

定義拖動(dòng)手勢(shì)的方法

#pragma mark -------滑動(dòng)手勢(shì) 拖動(dòng)快進(jìn)快退 ---------
-(void)pan:(UIPanGestureRecognizer *)panGesture{
    
    //獲得觸摸點(diǎn)
    CGPoint location = [panGesture locationInView:self.bgProgressView];
    
    if (panGesture.state == UIGestureRecognizerStateBegan) {
        
        //開始滑動(dòng)
        
        //進(jìn)入拖拽狀態(tài) 暫停播放
        self.status = kProgressStatusDrag;
        if (self.statusBlock) {
            self.statusBlock(kProgressStatusDrag);
        }
        
    }else if(panGesture.state == UIGestureRecognizerStateChanged){
        
        //滑動(dòng)過程中
        [self seekToPoint:location.x];
    }else if(panGesture.state == UIGestureRecognizerStateEnded){
        
        //滑動(dòng)結(jié)束
        
        //進(jìn)入正常狀態(tài) 開始播放
        self.status = kProgressStatusNormal;
        if (self.statusBlock) {
            self.statusBlock(kProgressStatusNormal);
        }
    }
}

完善seekToPoint方法

#pragma mark -------進(jìn)度點(diǎn)移動(dòng)以及拖拽狀態(tài)下視頻內(nèi)容隨之改變 ---------
-(void)seekToPoint:(CGFloat)current{
    
    //在合理的范圍之內(nèi)
    if (current >= 0 && current <= self.bgProgressView.frame.size.width) {
        
        //更改已播放進(jìn)度視圖的寬度
        _tintProgressView.frame = CGRectMake(kSize, (self.frame.size.height-kProgressHeight)/2.0, current, kProgressHeight);
        
        //更改進(jìn)度點(diǎn)的位置
        _dotProgressView.center = CGPointMake(kSize+current, self.frame.size.height/2.0);
        
        //拖拽情況下才需要跳轉(zhuǎn)視頻
        if (self.status == kProgressStatusDrag) {
            
            if (self.sliderBlock) {
                self.sliderBlock(current/self.bgProgressView.frame.size.width);
            }
        }
        
    }
}

7.播放器接受相關(guān)信息的回調(diào)并作出相關(guān)的反應(yīng)

//重寫initWithFrame方法 布局
-(instancetype)initWithFrame:(CGRect)frame{
    if (self = [super initWithFrame:frame]) {
        
        //創(chuàng)建進(jìn)度條
        self.slider = [[SliderView alloc] initWithFrame:CGRectMake(0, self.frame.size.height-kSliderHeight, self.frame.size.width, kSliderHeight)];
        [self addSubview:_slider];
        
        //防止循環(huán)引用
        __block typeof(self) weakSelf = self;
        
        //進(jìn)度條的狀態(tài)
        [_slider setStatusBlock:^(kProgressStatus status) {
           
            if (status == kProgressStatusNormal) {
                
                //正常播放
                [weakSelf play];
                
                //調(diào)整按鈕圖片
                [weakSelf.controlBtn setBackgroundImage:[UIImage imageNamed:@"pause"] forState:UIControlStateNormal];
                
            }else{
                
                //暫停播放
                [weakSelf pause];
                
                //調(diào)整按鈕圖片
                [weakSelf.controlBtn setBackgroundImage:[UIImage imageNamed:@"play"] forState:UIControlStateNormal];
            }
        }];
        
        //進(jìn)度條拖拽的比例
        [_slider setSliderBlock:^(CGFloat progress) {
            
            //獲得視頻應(yīng)該顯示那一片段的時(shí)間點(diǎn)
            int time = CMTimeGetSeconds(self.player.currentItem.duration)*progress;
            
            //跳轉(zhuǎn)到相應(yīng)的片段
            [weakSelf.player seekToTime:CMTimeMake(time*NSEC_PER_SEC, NSEC_PER_SEC) completionHandler:^(BOOL finished) {
                
            }];
        }];
      
    }
    return self;
}

8.通過點(diǎn)擊進(jìn)度視圖凳枝,達(dá)到快進(jìn)快退的效果

#pragma mark -------觸摸事件 點(diǎn)擊快進(jìn)快退---------
//觸摸開始
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    //獲得觸摸點(diǎn)
    UITouch *touch = [touches anyObject];
    CGPoint location = [touch locationInView:self.bgProgressView];
    

    //暫停播放
    self.status = kProgressStatusDrag;
    if (self.statusBlock) {
        self.statusBlock(kProgressStatusDrag);
    }
    
    //跳轉(zhuǎn)
    [self seekToPoint:location.x];
}

//觸摸結(jié)束
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    //開始播放
    self.status = kProgressStatusNormal;
    if (self.statusBlock) {
        self.statusBlock(kProgressStatusNormal);
    }
}

9.加載播放器視圖

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //創(chuàng)建
    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];
    
}

三.運(yùn)行結(jié)果

demo鏈接
https://pan.baidu.com/s/1kITSz83zkCTQg86ZPOJfJA 密碼:3kn8

參考文章
http://www.reibang.com/p/6cb137340732 -基本使用

http://www.reibang.com/p/11e05d684c05 -封裝框架

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市跋核,隨后出現(xiàn)的幾起案子岖瑰,更是在濱河造成了極大的恐慌,老刑警劉巖砂代,帶你破解...
    沈念sama閱讀 222,627評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蹋订,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡刻伊,警方通過查閱死者的電腦和手機(jī)露戒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門椒功,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人智什,你說我怎么就攤上這事动漾。” “怎么了荠锭?”我有些...
    開封第一講書人閱讀 169,346評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵旱眯,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我证九,道長(zhǎng)删豺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,097評(píng)論 1 300
  • 正文 為了忘掉前任甫贯,我火速辦了婚禮吼鳞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘叫搁。我一直安慰自己赔桌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,100評(píng)論 6 398
  • 文/花漫 我一把揭開白布渴逻。 她就那樣靜靜地躺著疾党,像睡著了一般。 火紅的嫁衣襯著肌膚如雪惨奕。 梳的紋絲不亂的頭發(fā)上雪位,一...
    開封第一講書人閱讀 52,696評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音梨撞,去河邊找鬼雹洗。 笑死,一個(gè)胖子當(dāng)著我的面吹牛卧波,可吹牛的內(nèi)容都是我干的时肿。 我是一名探鬼主播,決...
    沈念sama閱讀 41,165評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼港粱,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼螃成!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起查坪,我...
    開封第一講書人閱讀 40,108評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤寸宏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后偿曙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體氮凝,經(jīng)...
    沈念sama閱讀 46,646評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,709評(píng)論 3 342
  • 正文 我和宋清朗相戀三年望忆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了覆醇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片朵纷。...
    茶點(diǎn)故事閱讀 40,861評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡炭臭,死狀恐怖永脓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鞋仍,我是刑警寧澤常摧,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站威创,受9級(jí)特大地震影響落午,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜肚豺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,196評(píng)論 3 336
  • 文/蒙蒙 一溃斋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吸申,春花似錦梗劫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至日丹,卻和暖如春走哺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背哲虾。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工丙躏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人束凑。 一個(gè)月前我還...
    沈念sama閱讀 49,287評(píng)論 3 379
  • 正文 我出身青樓晒旅,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親湘今。 傳聞我的和親對(duì)象是個(gè)殘疾皇子敢朱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,860評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容