AVPlayer 本地、網(wǎng)絡(luò)視頻播放相關(guān)

iOS開發(fā)常用的兩種視頻播放方式,一種是使用MPMoviePlayerController,還有就是使用AVPlayer。MPMoviePlayerController系統(tǒng)高度封裝使用起來(lái)很方便,但是如果要高度自定義播放器就比較麻煩。而AVPlayer則恰好相反,靈活性更強(qiáng),使用起來(lái)也麻煩一點(diǎn)。本文將對(duì)AVPlayer的使用做個(gè)簡(jiǎn)單的介紹。

1、AVPlayer加載播放視頻

AVPlayer繼承NSObject,所以單獨(dú)使用AVPlayer時(shí)無(wú)法顯示視頻的,必須將視頻圖層添加到AVPlayerLayer中方能顯示視頻。使用AVPlayer首先了解一下幾個(gè)常用的類:

AVAsset:AVAsset類專門用于獲取多媒體的相關(guān)信息,包括獲取多媒體的畫面、聲音等信息。
AVURLAsset:AVAsset的子類,可以根據(jù)一個(gè)URL路徑創(chuàng)建一個(gè)包含媒體信息的AVURLAsset對(duì)象。
AVPlayerItem:一個(gè)媒體資源管理對(duì)象,管理者視頻的一些基本信息和狀態(tài),一個(gè)AVPlayerItem對(duì)應(yīng)著一個(gè)視頻資源。

AVPlayer加載視頻的代碼如下:

    AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:[NSURL URLWithString:@"http://bos.nj.bpc.baidu.com/tieba-smallvideo/11772_3c435014fb2dd9a5fd56a57cc369f6a0.mp4"]];
    //添加監(jiān)聽
    [playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
    [playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
    self.avPlayer = [AVPlayer playerWithPlayerItem:playerItem];
    
    AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer];
    //設(shè)置模式
    playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    playerLayer.contentsScale = [UIScreen mainScreen].scale;
    playerLayer.frame = CGRectMake(0, 100, self.view.bounds.size.width, 200);
    [self.view.layer addSublayer:playerLayer];



//監(jiān)聽回調(diào)
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
    AVPlayerItem *playerItem = (AVPlayerItem *)object;
    
    if ([keyPath isEqualToString:@"loadedTimeRanges"]){
        
    }else if ([keyPath isEqualToString:@"status"]){
        if (playerItem.status == AVPlayerItemStatusReadyToPlay){
            NSLog(@"playerItem is ready");
            [self.avPlayer play];
        } else{
            NSLog(@"load break");
        }
    }
}

此處代碼中添加了對(duì)AVPlayerItem的"loadedTimeRanges"和"status"屬性監(jiān)聽,status枚舉值有 AVPlayerItemStatusUnknown,AVPlayerItemStatusReadyToPlay, AVPlayerItemStatusFailed。只有當(dāng)status為AVPlayerItemStatusReadyToPlay是調(diào)用 AVPlayer的play方法視頻才能播放。

運(yùn)行效果
D82C00B0-4D83-4B1A-82EC-B245F15F40E0.png

2、AVPlayer當(dāng)前緩沖進(jìn)度以及當(dāng)前播放進(jìn)度的處理

獲取視頻當(dāng)前的緩沖進(jìn)度:

通過(guò)監(jiān)聽AVPlayerItem的"loadedTimeRanges",可以實(shí)時(shí)知道當(dāng)前視頻的進(jìn)度緩沖,計(jì)算方法如下:

- (NSTimeInterval)availableDurationWithplayerItem:(AVPlayerItem *)playerItem
{
    NSArray *loadedTimeRanges = [playerItem loadedTimeRanges];
    CMTimeRange timeRange = [loadedTimeRanges.firstObject CMTimeRangeValue];// 獲取緩沖區(qū)域
    NSTimeInterval startSeconds = CMTimeGetSeconds(timeRange.start);
    NSTimeInterval durationSeconds = CMTimeGetSeconds(timeRange.duration);
    NSTimeInterval result = startSeconds + durationSeconds;// 計(jì)算緩沖總進(jìn)度
    return result;
}
獲取視頻當(dāng)前的播放進(jìn)度:
    //視頻當(dāng)前的播放進(jìn)度
    NSTimeInterval current = CMTimeGetSeconds(self.avPlayer.currentTime);
    //視頻的總長(zhǎng)度
    NSTimeInterval total = CMTimeGetSeconds(self.avPlayer.currentItem.duration);

AVPlayer提供了一個(gè)Block回調(diào),當(dāng)播放進(jìn)度改變的時(shí)候回主動(dòng)回調(diào)該Block,但是當(dāng)視頻卡頓的時(shí)候是不會(huì)回調(diào)的,可以在該回調(diào)里面處理進(jìn)度條以及播放時(shí)間的刷新,詳細(xì)方法如下:

     __weak __typeof(self) weakSelf = self;
    [self.avPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
        //當(dāng)前播放的時(shí)間
        NSTimeInterval current = CMTimeGetSeconds(time);
        //視頻的總時(shí)間
        NSTimeInterval total = CMTimeGetSeconds(weakSelf.avPlayer.currentItem.duration);
        //設(shè)置滑塊的當(dāng)前進(jìn)度
        weakSelf.slider.sliderPercent = current/total;
        NSLog(@"%f", weakSelf.slider.sliderPercent);
       //設(shè)置時(shí)間
        weakSelf.timeLabel.text = [NSString stringWithFormat:@"%@/%@", [weakSelf formatPlayTime:current], [weakSelf formatPlayTime:total]];
    }];


//將時(shí)間轉(zhuǎn)換成00:00:00格式
- (NSString *)formatPlayTime:(NSTimeInterval)duration
{
    int minute = 0, hour = 0, secend = duration;
    minute = (secend % 3600)/60;
    hour = secend / 3600;
    secend = secend % 60;
    return [NSString stringWithFormat:@"%02d:%02d:%02d", hour, minute, secend];
}
改變視頻當(dāng)前的播放進(jìn)度:

當(dāng)滑塊滑動(dòng)的時(shí)候需要改變當(dāng)前視頻的播放進(jìn)度,代碼如下:

//處理滑塊
- (void)progressValueChange:(AC_ProgressSlider *)slider
{
       //當(dāng)視頻狀態(tài)為AVPlayerStatusReadyToPlay時(shí)才處理(當(dāng)視頻沒(méi)加載的時(shí)候,直接禁止掉滑塊事件)
       if (self.avPlayer.status == AVPlayerStatusReadyToPlay) {
        NSTimeInterval duration = self.slider.sliderPercent* CMTimeGetSeconds(self.avPlayer.currentItem.duration);
        CMTime seekTime = CMTimeMake(duration, 1);

        [self.avPlayer seekToTime:seekTime completionHandler:^(BOOL finished) {

        }];
    }
}
播放進(jìn)度控件的定制:

該控件應(yīng)該包含4個(gè)部分,總進(jìn)度、緩沖進(jìn)度、當(dāng)前播放進(jìn)度還有一個(gè)滑塊。效果圖如下:


08D61DA5-5645-4B1D-A170-ABAAB6088B19.png

最簡(jiǎn)單的實(shí)現(xiàn)方式是UIProgressView跟UISlider兩個(gè)控件疊加起來(lái),效果不是太好。demo是自定義的UIControl,詳細(xì)實(shí)現(xiàn)方式請(qǐng)查看demo中的AC_ProgressSlider類。

當(dāng)視頻卡頓的時(shí)候處理旋轉(zhuǎn)loading方法:

上面說(shuō)過(guò)- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;該方法在卡頓的時(shí)候不會(huì)回調(diào),所以只用該方法處理不了這種情況。我好像也沒(méi)找到相關(guān)的api,所以demo中采用的是開啟定時(shí)器,然后用一個(gè)lastTime保留當(dāng)前的播放進(jìn)度,當(dāng)下次調(diào)用的時(shí)候用lastTime跟當(dāng)前的進(jìn)度進(jìn)行比較,如果相等說(shuō)明播放卡頓了,代碼如下:

    self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(upadte)];
    [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

//更新方法
- (void)upadte
{
    NSTimeInterval current = CMTimeGetSeconds(self.avPlayer.currentTime);
    NSTimeInterval total = CMTimeGetSeconds(self.avPlayer.currentItem.duration);
   //如果用戶在手動(dòng)滑動(dòng)滑塊,則不對(duì)滑塊的進(jìn)度進(jìn)行設(shè)置重繪
    if (!self.slider.isSliding) {
        self.slider.sliderPercent = current/total;
    }
    
    if (current!=self.lastTime) {
        [self.activity stopAnimating];
        self.timeLabel.text = [NSString stringWithFormat:@"%@/%@", [self formatPlayTime:current], [self formatPlayTime:total]];
    }else{
        [self.activity startAnimating];
    }
    self.lastTime = current;
}

3、AVPlayer播放暫停的處理

這個(gè)比較簡(jiǎn)單、 分別是pause和play方法

//播放暫停按鈕
- (void)playOrPauseAction:(UIButton *)sender
{
    sender.selected = !sender.selected;
    
    if (self.avPlayer.rate == 1) {
        [self.avPlayer pause];
        self.link.paused = YES;
        [self.activity stopAnimating];
    } else {
        [self.avPlayer play];
        self.link.paused = NO;
    }
}

4、AVPlayer播放完成的處理

添加通知即可

 //播放完成通知
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(moviePlayDidEnd)
                                                 name:AVPlayerItemDidPlayToEndTimeNotification
                                               object:nil];

5、更換當(dāng)前播放的AVPlayerItem

當(dāng)視頻播放完時(shí)或者用戶切換不同的視頻時(shí)候就要更換當(dāng)前的視頻,代碼如下:

//切換當(dāng)前播放的內(nèi)容
- (void)changeCurrentplayerItemWithAC_VideoModel:(AC_VideoModel *)model
{
    if (self.avPlayer) {
        
        //由暫停狀態(tài)切換時(shí)候 開啟定時(shí)器,將暫停按鈕狀態(tài)設(shè)置為播放狀態(tài)
        self.link.paused = NO;
        self.playButton.selected = NO;
        
        //移除當(dāng)前AVPlayerItem對(duì)"loadedTimeRanges"和"status"的監(jiān)聽
        [self removeObserveWithPlayerItem:self.avPlayer.currentItem];
        AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:model.url];
        [self addObserveWithPlayerItem:playerItem];
        self.avPlayerItem = playerItem;
        //更換播放的AVPlayerItem
        [self.avPlayer replaceCurrentItemWithPlayerItem:playerItem];
        
        self.playButton.enabled = NO;
        self.slider.enabled = NO;
    }
}

感覺寫的有點(diǎn)亂,詳細(xì)的看demo吧,demo運(yùn)行效果如下:

海賊王.gif

完整代碼7牛下載連接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,428評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,024評(píng)論 3 413
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,285評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,548評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,328評(píng)論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,878評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評(píng)論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,098評(píng)論 0 286
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,616評(píng)論 1 331
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,554評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,725評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評(píng)論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,971評(píng)論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,361評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,613評(píng)論 1 280
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,339評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,695評(píng)論 2 370

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