AVPlayer 實現(xiàn)視頻播放總結(jié)

因為產(chǎn)品需求要修改視頻播放的展示策略趣避,就簡單的梳理了一下目前端上的視頻播放功能啸箫,簡單整理了一下基本實現(xiàn)拧廊,以便于之后查閱虹茶。

播放器實現(xiàn)思路

由于視頻的展示在不同的產(chǎn)品上會有不同的樣式盏混,比如列表頁的視頻樣式诗轻,具體詳情頁視頻的樣式以及點擊視頻放大播放的樣式都是不同的苞笨,因此最好把具體的視頻播放邏輯與展示 UI 分開寫稠腊,便于以后的功能添加及修改东跪。


C7198711-CD8E-446C-B4C0-0F9178084E94.png

AVPlayer 實現(xiàn)視頻播放

  • AVPlayer : AVPlayer 提供了單個視頻播放功能畸陡,可以播放本地視頻和網(wǎng)絡資源,提供播放虽填,暫停等功能丁恭。
  • AVPlayerItem : 一個媒體資源管理對象,管理者視頻的一些基本信息和狀態(tài)
  • AVPlayerLayer : 是 CALayer 的子類卤唉,AVPlayer 實例化的對象(視頻內(nèi)容)需要需要放到 AVPlayerLayer 上才能播放涩惑。

視頻播放功能的實現(xiàn)大致需要以下幾個步驟:
1、創(chuàng)建 AVPlayer 實例桑驱,并將其添加到 AVPlayerLayer 實例上竭恬;
2跛蛋、監(jiān)聽 AVPlayerItem 的 status 屬性狀態(tài)變化,判斷視頻是否可以正常播放痊硕;
3赊级、監(jiān)聽 AVPlayerItem 的 loadedTimeRanges 屬性狀態(tài)變化,處理下載進度條顯示岔绸;
4理逊、調(diào)用 addPeriodicTimeObserverForInterval:queue:usingBlock: 方法來處理視頻播放進度條的變化;
5盒揉、調(diào)用- (void)seekToTime: toleranceBefore: toleranceAfter: 方法晋被,實現(xiàn)視頻跳轉(zhuǎn)到某一時刻播放
6、 視頻播放結(jié)束處理

以下是具體實現(xiàn)過程:
創(chuàng)建 AVPlayerItem 對象刚盈,并通過該對象實例化 AVPlayer 對象羡洛,將 AVPlayer 添加到 AVPlayerLayer 對象上

    self.playerItem = [AVPlayerItem playerItemWithURL:videoUrl];
    self.player = [[AVPlayer alloc] initWithPlayerItem:self.playerItem];
    [self.playerLayer setPlayer:self.player];

以上對象都創(chuàng)建完了并不能順利播放視頻,需要知道視頻是否下載成功藕漱,能不能順利播放欲侮,網(wǎng)絡不好或者鏈接無效的情況下我們都應該對其作出不同處理,這里就需要添加 KVO 監(jiān)聽視頻的緩存進度和播放狀態(tài)肋联,并且注意要在適當?shù)臅r候移除威蕉。

    // 監(jiān)聽播放狀態(tài)
    [self.playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
    // 監(jiān)聽緩沖進度
    [self.playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
    [self.playerItem removeObserver:self forKeyPath:@"playbackBufferEmpty" context:nil];

Apple 為我們提供了三種播放狀態(tài):

  • AVPlayerStatusReadyToPlay : 說明已經(jīng)準備ok,可以播放了橄仍;
  • AVPlayerStatusFailed: 可以通過 playerItem.error 信息來獲取失敗原因韧涨,例如比較常見的錯誤碼 playerItem.error.code == NSURLErrorNotConnectedToInternet || playerItem.error.code == NSURLErrorCannotFindHost ,可以給出找不到網(wǎng)絡的錯誤提示沙兰;
  • ** AVPlayerStatusUnknown**: 嘗試下載資源但發(fā)生未知錯誤

另外在項目中還會出現(xiàn)緩存不足無法播放的情況氓奈,這種情況我們需要添加對 playbackBufferEmpty 的KVO觀察, 如果self.playbackBufferEmpty == YES鼎天,則視頻無法正常播放。
實際情況下可能還有其他狀態(tài)暑竟,比如網(wǎng)絡狀態(tài)的好壞轉(zhuǎn)換會影響視頻的播放斋射,對于不同的狀態(tài)我們都應該有相應的處理,以保證視頻的正常播放但荤。

一般的視頻播放器都有下載進度條的顯示罗岖,通過 KVO 方法監(jiān)聽 loadedTimeRanges 屬性我們可以得到視頻緩沖的進度,具體實現(xiàn)可以查看下面代碼腹躁。

typedef NS_ENUM(NSInteger, AVPlayerStatus) {
    AVPlayerStatusUnknown,
    AVPlayerStatusReadyToPlay,
    AVPlayerStatusFailed
};

// KVO 方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    AVPlayerItem *playerItem = (AVPlayerItem *)object;
    if ([keyPath isEqualToString:@"status"]) {
        AVPlayerStatus status = [[change objectForKey:@"new"] integerValue];
        if (status == AVPlayerStatusReadyToPlay) {
            // 停止緩存動畫桑包,開始播放
            // 設置視頻的總時長
            if ([self.delegate respondsToSelector:@selector(videoTotalTime:)]) {
                [self.delegate videoTotalTime:CMTimeGetSeconds(self.player.currentItem.duration)];
            }
            
        } else if (status == AVPlayerStatusFailed) {
            NSLog(@"AVPlayerStatusFailed == %@", playerItem.error);
            return;
        } else if (status == AVPlayerStatusUnknown) {
            return;
        }
    } else if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
        // 處理緩沖進度條
        NSTimeInterval bufferTime = [self currentVideoLoadedTime];
        NSTimeInterval totalTime = CMTimeGetSeconds(self.player.currentItem.duration);
        CGFloat progress = bufferTime/totalTime;
        if ([self.delegate respondsToSelector:@selector(videoPlayer: loadProgress:)]) {
            [self.delegate videoPlayer:self loadProgress:progress];
        }
    } else if ([keyPath isEqualToString:@"playbackBufferEmpty"]) {
    
    } 
}

關于播放的進度需要處理,重點是addPeriodicTimeObserverForInterval:queue:usingBlock: 方法纺非,該方法返回當前播放的 timeline哑了,當視頻暫停赘方、播放或者跳到某一時間進行播放的時候都會調(diào)用該方法。需要注意的是暫停和重新播放并不需要我們做額外的操作弱左,只需要調(diào)用 pause 或者 play 方法即可窄陡,時間的問題該方法已經(jīng)幫我們處理好了。每次調(diào)用該方法對應的要調(diào)用 -removeTimeObserver: 對其進行移除拆火,避免發(fā)生未知的錯誤跳夭。

 __weak typeof(self) weakSelf = self;
    self.playbackTimeObserver = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 30) queue:NULL usingBlock:^(CMTime time) {
        // 播放進度條以及時間的顯示
        if ([weakSelf.delegate respondsToSelector:@selector(videoCurrentTime:)]) {
            Float64 durationTime = CMTimeGetSeconds(weakSelf.playerItem.currentTime);
            [weakSelf.delegate videoCurrentTime:durationTime];
        } 
    }];

// 移除操作
 [self.player removeTimeObserver:_playbackTimeObserver];

關于跳轉(zhuǎn)到某一時刻進行播放,只需要執(zhí)行下面的方法即可们镜,為了跳轉(zhuǎn)到正確的位置币叹,需要將 toleranceBefore 和 toleranceAfter 都設置為 kCMTimeZero。

- (void)seekToTime:(CMTime)time toleranceBefore:(CMTime)toleranceBefore toleranceAfter:(CMTime)toleranceAfter;

到此位置播放邏輯已經(jīng)基本實現(xiàn)模狭,調(diào)用 [self.player play]; 即可實現(xiàn)視頻的播放颈抚。

在具體實現(xiàn)過程中我們需要添加視頻播放結(jié)束通知,以便做下一步處理

   // 添加視頻播放結(jié)束通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playDidEndNotification:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];

// 這里設置視頻播放結(jié)束就自動重播胞皱,也可以停止播放顯示重播按鈕邪意,具體處理看需求
- (void)playDidEndNotification:(NSNotification *)notification {
    self.playEnd = YES;
    [self.delegate isVideoEnd:self.playEnd];
    // 自動重播
    [self.player seekToTime:CMTimeMakeWithSeconds(0, 600) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
    [self.player play];
}

播放器樣式實現(xiàn)

對于進度條,播放暫停按鈕等 UI 的實現(xiàn)在另一個類中進行處理反砌,兩者可以通過 protocol 來進行數(shù)據(jù)交互處理雾鬼,具體的 UI 代碼這里就不一一列出了,可以到 github 上下載簡單的demo 來看一下 宴树。

19B87A1D-D2AA-4012-9225-28A5E71AC1E0.png

播放邏輯與 UI 的展示通過 HJPlayView 進行組合策菜,可以通過 HJPlayViewType 來選擇不同的 UI 樣式,demo 中只給出了全屏播放的 UI 樣子酒贬,列表頁的可以通過繼承 HJMaskView 來自己實現(xiàn)又憨,只需要在以下方法進行添加就好。

- (void)setType:(HJPlayViewType)type {
    - (void)setType:(HJPlayViewType)type {
    if (type == HJPlayViewTypeForPlay) {
        _maskView = [self maskView];
    }
    if (type == HJPlayViewTypeForScan) {
        //添加不同的樣式即可
    }
}
}

這里只是實現(xiàn)了最簡單的播放效果锭吨,繼續(xù)學習~~~

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蠢莺,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子零如,更是在濱河造成了極大的恐慌躏将,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件考蕾,死亡現(xiàn)場離奇詭異祸憋,居然都是意外死亡,警方通過查閱死者的電腦和手機肖卧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門蚯窥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事拦赠∥∩常” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵矛紫,是天一觀的道長赎瞎。 經(jīng)常有香客問我,道長颊咬,這世上最難降的妖魔是什么务甥? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮喳篇,結(jié)果婚禮上敞临,老公的妹妹穿的比我還像新娘。我一直安慰自己麸澜,他們只是感情好挺尿,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著炊邦,像睡著了一般编矾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上馁害,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天窄俏,我揣著相機與錄音,去河邊找鬼碘菜。 笑死凹蜈,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的忍啸。 我是一名探鬼主播仰坦,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼计雌!你這毒婦竟也來了悄晃?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤凿滤,失蹤者是張志新(化名)和其女友劉穎传泊,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鸭巴,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年拦盹,在試婚紗的時候發(fā)現(xiàn)自己被綠了鹃祖。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡普舆,死狀恐怖恬口,靈堂內(nèi)的尸體忽然破棺而出校读,到底是詐尸還是另有隱情,我是刑警寧澤祖能,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布歉秫,位于F島的核電站,受9級特大地震影響养铸,放射性物質(zhì)發(fā)生泄漏雁芙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一钞螟、第九天 我趴在偏房一處隱蔽的房頂上張望兔甘。 院中可真熱鬧,春花似錦鳞滨、人聲如沸洞焙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽澡匪。三九已至,卻和暖如春褒链,著一層夾襖步出監(jiān)牢的瞬間唁情,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工碱蒙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留荠瘪,地道東北人。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓赛惩,卻偏偏與公主長得像哀墓,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子喷兼,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

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