視頻播放器介紹文檔
目錄介紹
- 01.該視頻播放器介紹
- 02.視頻播放器功能
- 03.視頻播放器架構(gòu)說(shuō)明
- 04.視頻播放器如何使用
- 05.播放器詳細(xì)Api文檔
- 06.播放器封裝思路
- 07.播放器示例展示圖
- 08.添加自定義視圖
- 09.視頻播放器優(yōu)化處理
- 10.播放器問(wèn)題記錄說(shuō)明
- 11.性能優(yōu)化和庫(kù)大小
- 12.視頻緩存原理介紹
- 13.查看視頻播放器日志
- 14.該庫(kù)異常code說(shuō)明
- 15.該庫(kù)系列wiki文檔
- 16.版本更新文檔記錄
00.視頻播放器通用框架
- 基礎(chǔ)封裝視頻播放器player疚察,可以在ExoPlayer、MediaPlayer比驻,聲網(wǎng)RTC視頻播放器內(nèi)核别惦,原生MediaPlayer可以自由切換
- 對(duì)于視圖狀態(tài)切換和后期維護(hù)拓展夫椭,避免功能和業(yè)務(wù)出現(xiàn)耦合蹭秋。比如需要支持播放器UI高度定制仁讨,而不是該lib庫(kù)中UI代碼
- 針對(duì)視頻播放陪竿,音頻播放,播放回放闰挡,以及視頻直播的功能长酗。使用簡(jiǎn)單桐绒,代碼拓展性強(qiáng)茉继,封裝性好烁竭,主要是和業(yè)務(wù)徹底解耦,暴露接口監(jiān)聽給開發(fā)者處理業(yè)務(wù)具體邏輯
- 該播放器整體架構(gòu):播放器內(nèi)核(自由切換) + 視頻播放器 + 邊播邊緩存 + 高度定制播放器UI視圖層
01.該視頻播放器介紹
1.1 該庫(kù)說(shuō)明
播放器功能 | MediaPlayer | ExoPlayer | IjkPlayer | RTC | TXPlayer |
---|---|---|---|---|---|
UI/Player/業(yè)務(wù)解耦 | 支持 | 支持 | 支持 | ||
切換視頻播放模式 | 支持 | 支持 | 支持 | ||
視頻無(wú)縫切換 | 支持 | 支持 | 支持 | ||
調(diào)節(jié)播放進(jìn)度 | 支持 | 支持 | 支持 | ||
網(wǎng)絡(luò)環(huán)境監(jiān)聽 | 支持 | 支持 | 支持 | ||
滑動(dòng)改變亮度/聲音 | 支持 | 支持 | 支持 | ||
設(shè)置視頻播放比例 | 支持 | 支持 | 支持 | ||
自由切換視頻內(nèi)核 | 支持 | 支持 | 支持 | ||
記錄播放位置 | 支持 | 支持 | 支持 | ||
清晰度模式切換 | 支持 | 支持 | 支持 | ||
重力感應(yīng)自動(dòng)進(jìn)入 | 支持 | 支持 | 支持 | ||
鎖定屏幕功能 | 支持 | 支持 | 支持 | ||
倍速播放 | 不支持 | 支持 | 支持 | ||
視頻小窗口播放 | 支持 | 支持 | 支持 | ||
列表小窗口播放 | 支持 | 支持 | 支持 | ||
邊播邊緩存 | 支持 | 支持 | 支持 | ||
同時(shí)播放多個(gè)視頻 | 支持 | 支持 | 支持 | ||
仿快手預(yù)加載 | 支持 | 支持 | 支持 | ||
基于內(nèi)核無(wú)UI | 支持 | 支持 | 支持 | ||
添加彈幕 | 支持 | 支持 | 支持 | ||
全屏顯示電量 | 支持 | 支持 | 支持 |
1.2 該庫(kù)功能說(shuō)明
類型 | 功能說(shuō)明 |
---|---|
項(xiàng)目結(jié)構(gòu) | VideoCache緩存lib睬魂,VideoKernel視頻內(nèi)核lib氯哮,VideoPlayer視頻UIlib |
內(nèi)核 | MediaPlayer喉钢、ExoPlayer出牧、IjkPlayer歇盼,后期接入Rtc和TXPlayer |
協(xié)議/格式 | http/https豹缀、concat邢笙、rtsp、hls叮雳、rtmp帘不、file杨箭、m3u8互婿、mkv慈参、webm、mp3侈净、mp4等 |
畫面 | 調(diào)整顯示比例:默認(rèn)畜侦、16:9旋膳、4:3途事、填充尸变;播放時(shí)旋轉(zhuǎn)畫面角度(0,90,180,270)召烂;鏡像旋轉(zhuǎn) |
布局 | 內(nèi)核和UI分離奏夫,和市面GitHub上大多數(shù)播放器不一樣,方便定制廊谓,通過(guò)addView添加 |
播放 | 正常播放蒸痹,小窗播放叠荠,列表播放竖共,仿抖音播放 |
自定義 | 可以自定義添加視頻UI層公给,可以說(shuō)UI和Player高度分離淌铐,支持自定義渲染層SurfaceView |
02.視頻播放器功能
- A基礎(chǔ)功能
- A.1.1 能夠自定義視頻加載loading類型腿准,設(shè)置視頻標(biāo)題,設(shè)置視頻底部圖片街望,設(shè)置播放時(shí)長(zhǎng)等基礎(chǔ)功能
- A.1.2 可以切換播放器的視頻播放狀態(tài)防症,播放錯(cuò)誤炭玫,播放未開始榴鼎,播放開始盗似,播放準(zhǔn)備中接癌,正在播放椭符,暫停播放,正在緩沖等等狀態(tài)
- A.1.3 可以自由設(shè)置播放器的播放模式,比如丈秩,正常播放,全屏播放瓶籽,和小屏幕播放汤求。其中全屏播放支持旋轉(zhuǎn)屏幕裤唠。
- A.1.4 可以支持多種視頻播放類型,比如,原生封裝視頻播放器,還有基于ijkPlayer封裝的播放器。
- A.1.5 可以設(shè)置是否隱藏播放音量,播放進(jìn)度,播放亮度等,可以通過(guò)拖動(dòng)seekBar改變視頻進(jìn)度。還支持設(shè)置n秒后不操作則隱藏頭部和頂部布局功能
- A.1.6 可以設(shè)置豎屏模式下全屏模式和橫屏模式下的全屏模式,方便多種使用場(chǎng)景
- A.1.7 top和bottom面版消失和顯示:點(diǎn)擊視頻畫面會(huì)顯示、隱藏操作面板;顯示后不操作會(huì)5秒后自動(dòng)消失【也可以設(shè)置n秒消失時(shí)間】
- B高級(jí)功能
- B.1.1 支持一遍播放一遍緩沖的功能,其中緩沖包括兩部分,第一種是播放過(guò)程中緩沖驶俊,第二種是暫停過(guò)程中緩沖
- B.1.2 基于ijkPlayer,ExoPlayer想鹰,Rtc,原生MediaPlayer等的封裝播放器何缓,支持多種格式視頻播放
- B.1.3 可以設(shè)置是否記錄播放位置碌廓,設(shè)置播放速度谷婆,設(shè)置屏幕比例
- B.1.4 支持滑動(dòng)改變音量【屏幕右邊】期贫,改變屏幕亮度【屏幕左邊】唯灵,屏幕底測(cè)左右滑動(dòng)調(diào)節(jié)進(jìn)度
- B.1.5 支持list頁(yè)面中視頻播放,滾動(dòng)后暫停播放,播放可以自由設(shè)置是否記錄狀態(tài)斑匪。并且還支持刪除視頻播放位置狀態(tài)庶橱。
- B.1.6 切換橫豎屏:切換全屏?xí)r,隱藏狀態(tài)欄泉孩,顯示自定義top(顯示電量)句喷;豎屏?xí)r恢復(fù)原有狀態(tài)
- B.1.7 支持切換視頻清晰度模式
- B.1.8 添加鎖屏功能脏嚷,豎屏不提供鎖屏按鈕父叙,橫屏全屏?xí)r顯示趾唱,并且鎖屏?xí)r甜癞,屏蔽手勢(shì)處理
- C拓展功能【這塊根據(jù)實(shí)際情況選擇是否需要使用悠咱,一般視頻付費(fèi)App會(huì)有這個(gè)工鞥】
- C1產(chǎn)品需求:類似優(yōu)酷析既,愛(ài)奇藝視頻播放器部分邏輯眼坏。比如如果用戶沒(méi)有登錄也沒(méi)有看視頻權(quán)限宰译,則提示試看視頻[自定義布局]沿侈;如果用戶沒(méi)有登錄但是有看視頻權(quán)限缀拭,則正常觀看智厌;如果用戶登錄铣鹏,但是沒(méi)有充值會(huì)員,部分需要權(quán)限視頻則進(jìn)入試看模式葵第,試看結(jié)束后彈出充值會(huì)員界面缀台;如果用戶余額不足哮奇,比如余額只有99元哲身,但是視頻觀看要199元勘天,則又有其他提示脯丝。
- C2自身需求:比如封裝好了視頻播放庫(kù)伏伐,那么點(diǎn)擊視頻上登錄按鈕則跳到登錄頁(yè)面巾钉;點(diǎn)擊充值會(huì)員頁(yè)面也跳到充值頁(yè)面。這個(gè)通過(guò)定義接口秘案,可以讓使用者通過(guò)方法調(diào)用,靈活處理點(diǎn)擊事件潦匈。
- C.1.1 可以設(shè)置試看模式阱高,設(shè)置試看時(shí)長(zhǎng)。試看結(jié)束后就提示登錄或者充值……
- C.1.2 對(duì)于設(shè)置視頻的寬高茬缩,建議設(shè)置成4:3或者16:9或者常用比例,如果不是常用比例凰锡,則可能會(huì)有黑邊未舟。其中黑邊的背景可以設(shè)置
- C.1.3 可以設(shè)置播放有權(quán)限的視頻時(shí)的各種文字描述,而沒(méi)有把它寫在封裝庫(kù)中掂为,使用者自己設(shè)定
- C.1.4 鎖定屏幕功能裕膀,這個(gè)參考大部分播放器,只有在全屏模式下才會(huì)有
03.視頻播放器架構(gòu)說(shuō)明
- 視頻常見(jiàn)的布局視圖
- 視頻底圖(用于顯示初始化視頻時(shí)的封面圖)勇哗,視頻狀態(tài)視圖【加載loading昼扛,播放異常,加載視頻失敗欲诺,播放完成等】
- 改變亮度和聲音【改變聲音視圖抄谐,改變亮度視圖】渺鹦,改變視頻快進(jìn)和快退,左右滑動(dòng)快進(jìn)和快退視圖(手勢(shì)滑動(dòng)的快進(jìn)快退提示框)
- 頂部控制區(qū)視圖(包含返回健蛹含,title等)毅厚,底部控制區(qū)視圖(包含進(jìn)度條,播放暫停浦箱,時(shí)間吸耿,切換全屏等)
- 鎖屏布局視圖(全屏?xí)r展示,其他隱藏)憎茂,底部播放進(jìn)度條視圖(很多播放器都有這個(gè))珍语,清晰度列表視圖(切換清晰度彈窗)
- 后期可能涉及的布局視圖
- 手勢(shì)指導(dǎo)頁(yè)面(有些播放器有新手指導(dǎo)功能),離線下載的界面(該界面中包含下載列表, 列表的item編輯(全選, 刪除))
- 用戶從wifi切換到4g網(wǎng)絡(luò)竖幔,提示網(wǎng)絡(luò)切換彈窗界面(當(dāng)網(wǎng)絡(luò)由wifi變?yōu)?g的時(shí)候會(huì)顯示)
- 圖片廣告視圖(帶有倒計(jì)時(shí)消失)板乙,開始視頻廣告視圖,非會(huì)員試看視圖
- 彈幕視圖(這個(gè)很重要)拳氢,水印顯示視圖募逞,倍速播放界面(用于控制倍速),底部視頻列表縮略圖視圖
- 投屏視頻視圖界面馋评,視頻直播間刷禮物界面放接,老師開課界面,展示更多視圖(下載留特,分享纠脾,切換音頻等)
- 視頻播放器的痛點(diǎn)
- 播放器內(nèi)核難以切換
- 不同的視頻播放器內(nèi)核,由于api不一樣蜕青,所以難以切換操作苟蹈。要是想兼容內(nèi)核切換,就必須自己制定一個(gè)視頻接口+實(shí)現(xiàn)類的播放器
- 播放器內(nèi)核和UI層耦合
- 也就是說(shuō)視頻player和ui操作柔和到了一起右核,尤其是兩者之間的交互慧脱。比如播放中需要更新UI進(jìn)度條,播放異常需要顯示異常UI贺喝,都比較難處理播放器狀態(tài)變化更新UI操作
- UI難以自定義或者修改麻煩
- 比如常見(jiàn)的視頻播放器菱鸥,會(huì)把視頻各種視圖寫到xml中,這種方式在后期代碼會(huì)很大躏鱼,而且改動(dòng)一個(gè)小的布局氮采,則會(huì)影響大。這樣到后期往往只敢加代碼染苛,而不敢刪除代碼……
- 有時(shí)候難以適應(yīng)新的場(chǎng)景扳抽,比如添加一個(gè)播放廣告,老師開課,或者視頻引導(dǎo)業(yè)務(wù)需求贸呢,則需要到播放器中寫一堆業(yè)務(wù)代碼镰烧。迭代到后期,違背了開閉原則楞陷,視頻播放器需要做到和業(yè)務(wù)分離
- 視頻播放器結(jié)構(gòu)不清晰
- 這個(gè)是指該視頻播放器能否看了文檔后快速上手怔鳖,知道封裝的大概流程。方便后期他人修改和維護(hù)固蛾,因此需要將視頻播放器功能分離结执。比如切換內(nèi)核+視頻播放器(player+controller+view)
- 播放器內(nèi)核難以切換
- 需要達(dá)到的目的和效果
- 基礎(chǔ)封裝視頻播放器player,可以在ExoPlayer艾凯、MediaPlayer献幔,聲網(wǎng)RTC視頻播放器內(nèi)核,原生MediaPlayer可以自由切換
- 對(duì)于視圖狀態(tài)切換和后期維護(hù)拓展趾诗,避免功能和業(yè)務(wù)出現(xiàn)耦合蜡感。比如需要支持播放器UI高度定制,而不是該lib庫(kù)中UI代碼
- 針對(duì)視頻播放恃泪,視頻投屏郑兴,音頻播放,播放回放贝乎,以及視頻直播的功能
- 通用視頻框架特點(diǎn)
- 一定要解耦合
- 播放器內(nèi)核與播放器解耦: 支持更多的播放場(chǎng)景情连、以及新的播放業(yè)務(wù)快速接入,并且不影響其他播放業(yè)務(wù)览效,比如后期添加阿里云播放器內(nèi)核却舀,或者騰訊播放器內(nèi)核
- 播放器player與視頻UI解耦:支持添加自定義視頻視圖,比如支持添加自定義廣告锤灿,新手引導(dǎo)挽拔,或者視頻播放異常等視圖,這個(gè)需要較強(qiáng)的拓展性
- 適合多種業(yè)務(wù)場(chǎng)景
- 比如適合播放單個(gè)視頻衡招,多個(gè)視頻,以及列表視頻每强,或者類似抖音那種一個(gè)頁(yè)面一個(gè)視頻始腾,還有小窗口播放視頻。也就是適合大多數(shù)業(yè)務(wù)場(chǎng)景
- 一定要解耦合
- 視頻分層
- 播放器內(nèi)核
- 可以切換ExoPlayer空执、MediaPlayer浪箭,IjkPlayer,聲網(wǎng)視頻播放器辨绊,這里使用工廠模式Factory + AbstractVideoPlayer + 各個(gè)實(shí)現(xiàn)AbstractVideoPlayer抽象類的播放器類
- 定義抽象的播放器奶栖,主要包含視頻初始化,設(shè)置,狀態(tài)設(shè)置宣鄙,以及播放監(jiān)聽袍镀。由于每個(gè)內(nèi)核播放器api可能不一樣,所以這里需要實(shí)現(xiàn)AbstractVideoPlayer抽象類的播放器類冻晤,方便后期統(tǒng)一調(diào)用
- 為了方便創(chuàng)建不同內(nèi)核player苇羡,所以需要?jiǎng)?chuàng)建一個(gè)PlayerFactory,定義一個(gè)createPlayer創(chuàng)建播放器的抽象方法鼻弧,然后各個(gè)內(nèi)核都實(shí)現(xiàn)它设江,各自創(chuàng)建自己的播放器
- VideoPlayer播放器
- 可以自由切換視頻內(nèi)核,Player+Controller攘轩。player負(fù)責(zé)播放的邏輯叉存,Controller負(fù)責(zé)視圖相關(guān)的邏輯,兩者之間用接口進(jìn)行通信
- 針對(duì)Controller度帮,需要定義一個(gè)接口歼捏,主要負(fù)責(zé)視圖UI處理邏輯,支持添加各種自定義視圖View【統(tǒng)一實(shí)現(xiàn)自定義接口Control】够傍,每個(gè)view盡量保證功能單一性甫菠,最后通過(guò)addView形式添加進(jìn)來(lái)
- 針對(duì)Player,需要定義一個(gè)接口冕屯,主要負(fù)責(zé)視頻播放處理邏輯寂诱,比如視頻播放,暫停安聘,設(shè)置播放進(jìn)度痰洒,設(shè)置視頻鏈接,切換播放模式等操作浴韭。需要注意把Controller設(shè)置到Player里面丘喻,兩者之間通過(guò)接口交互
- UI控制器視圖
- 定義一個(gè)BaseVideoController類,這個(gè)主要是集成各種事件的處理邏輯念颈,比如播放器狀態(tài)改變泉粉,控制視圖隱藏和顯示,播放進(jìn)度改變榴芳,鎖定狀態(tài)改變嗡靡,設(shè)備方向監(jiān)聽等等操作
- 定義一個(gè)view的接口InterControlView,在這里類里定義綁定視圖窟感,視圖隱藏和顯示讨彼,播放狀態(tài),播放模式柿祈,播放進(jìn)度哈误,鎖屏等操作哩至。這個(gè)每個(gè)實(shí)現(xiàn)類則都可以拿到這些屬性呢
- 在BaseVideoController中使用LinkedHashMap保存每個(gè)自定義view視圖,添加則put進(jìn)來(lái)后然后通過(guò)addView將視圖添加到該控制器中蜜自,這樣非常方便添加自定義視圖
- 播放器切換狀態(tài)需要改變Controller視圖菩貌,比如視頻異常則需要顯示異常視圖view,則它們之間的交互是通過(guò)ControlWrapper(同時(shí)實(shí)現(xiàn)Controller接口和Player接口)實(shí)現(xiàn)
- 播放器內(nèi)核
04.視頻播放器如何使用
4.1 關(guān)于gradle引用說(shuō)明
- 如下所示
//視頻UI層袁辈,必須要有 implementation 'cn.yc:VideoPlayer:3.0.1' //視頻緩存菜谣,如果不需要?jiǎng)t可以不依賴 implementation 'cn.yc:VideoCache:3.0.0' //視頻內(nèi)核層,必須有 implementation 'cn.yc:VideoKernel:3.0.1'
4.2 在xml中添加布局
- 注意晚缩,在實(shí)際開發(fā)中尾膊,由于Android手機(jī)碎片化比較嚴(yán)重,分辨率太多了荞彼,建議靈活設(shè)置布局的寬高比為4:3或者16:9或者你認(rèn)為合適的冈敛,可以用代碼設(shè)置。
- 如果寬高比變形鸣皂,則會(huì)有黑邊
<org.yczbj.ycvideoplayerlib.player.VideoPlayer android:id="@+id/video_player" android:layout_width="match_parent" android:layout_height="240dp"/>
4.3 最簡(jiǎn)單的視頻播放器參數(shù)設(shè)定
- 如下所示
//創(chuàng)建基礎(chǔ)視頻播放器抓谴,一般播放器的功能 BasisVideoController controller = new BasisVideoController(this); //設(shè)置控制器 mVideoPlayer.setVideoController(controller); //設(shè)置視頻播放鏈接地址 mVideoPlayer.setUrl(url); //開始播放 mVideoPlayer.start();
4.4 注意問(wèn)題
- 如果是全屏播放浆洗,則需要在清單文件中設(shè)置當(dāng)前activity的屬性值
- android:configChanges 保證了在全屏的時(shí)候橫豎屏切換不會(huì)執(zhí)行Activity的相關(guān)生命周期吸占,打斷視頻的播放
- android:screenOrientation 固定了屏幕的初始方向
- 這兩個(gè)變量控制全屏后和退出全屏的屏幕方向
<activity android:name=".VideoActivity" android:configChanges="orientation|keyboardHidden|screenSize" android:screenOrientation="portrait"/>
- 如何一進(jìn)入頁(yè)面就開始播放視頻,稍微延時(shí)一下即可
- 代碼如下所示撰洗,注意避免直接start()荆陆,因?yàn)橛锌赡芤曨l還沒(méi)有初始化完成……
mVideoPlayer.postDelayed(new Runnable() { @Override public void run() { mVideoPlayer.start(); } },300);
- 代碼如下所示撰洗,注意避免直接start()荆陆,因?yàn)橛锌赡芤曨l還沒(méi)有初始化完成……
05.播放器詳細(xì)Api文檔
- 01.最簡(jiǎn)單的播放
- 02.如何切換視頻內(nèi)核
- 03.切換視頻模式
- 04.切換視頻清晰度
- 05.視頻播放監(jiān)聽
- 06.列表中播放處理
- 07.懸浮窗口播放
- 08.其他重要功能Api
- 09.播放多個(gè)視頻
- 10.VideoPlayer相關(guān)Api
- 11.Controller相關(guān)Api
- 12.仿快手播放視頻
- 具體看這篇文檔:視頻播放器Api說(shuō)明
06.播放器封裝思路
6.1視頻層級(jí)示例圖
image
6.2 視頻播放器流程圖
- 待完善
6.3 視頻播放器lib庫(kù)
image
6.4 視頻內(nèi)核lib庫(kù)介紹
image
image
6.5視頻播放器UI庫(kù)介紹
image
07.播放器示例展示圖
image
image
image
image
image
image
image
image
image
image
08.添加自定義視圖
- 比如滩届,現(xiàn)在有個(gè)業(yè)務(wù)需求,需要在視頻播放器剛開始添加一個(gè)廣告視圖被啼,等待廣告倒計(jì)時(shí)120秒后帜消,直接進(jìn)入播放視頻邏輯。相信這個(gè)業(yè)務(wù)場(chǎng)景很常見(jiàn)浓体,大家都碰到過(guò)泡挺,使用該播放器就特別簡(jiǎn)單,代碼如下所示:
- 首先創(chuàng)建一個(gè)自定義view命浴,需要實(shí)現(xiàn)InterControlView接口娄猫,重寫該接口中所有抽象方法,這里省略了很多代碼生闲,具體看demo媳溺。
public class AdControlView extends FrameLayout implements InterControlView, View.OnClickListener { private ControlWrapper mControlWrapper; public AdControlView(@NonNull Context context) { super(context); init(context); } private void init(Context context){ LayoutInflater.from(getContext()).inflate(R.layout.layout_ad_control_view, this, true); } /** * 播放狀態(tài) * -1 播放錯(cuò)誤 * 0 播放未開始 * 1 播放準(zhǔn)備中 * 2 播放準(zhǔn)備就緒 * 3 正在播放 * 4 暫停播放 * 5 正在緩沖(播放器正在播放時(shí),緩沖區(qū)數(shù)據(jù)不足跪腹,進(jìn)行緩沖褂删,緩沖區(qū)數(shù)據(jù)足夠后恢復(fù)播放) * 6 暫停緩沖(播放器正在播放時(shí)飞醉,緩沖區(qū)數(shù)據(jù)不足冲茸,進(jìn)行緩沖屯阀,此時(shí)暫停播放器,繼續(xù)緩沖轴术,緩沖區(qū)數(shù)據(jù)足夠后恢復(fù)暫停 * 7 播放完成 * 8 開始播放中止 * @param playState 播放狀態(tài)难衰,主要是指播放器的各種狀態(tài) */ @Override public void onPlayStateChanged(int playState) { switch (playState) { case ConstantKeys.CurrentState.STATE_PLAYING: mControlWrapper.startProgress(); mPlayButton.setSelected(true); break; case ConstantKeys.CurrentState.STATE_PAUSED: mPlayButton.setSelected(false); break; } } /** * 播放模式 * 普通模式,小窗口模式逗栽,正常模式三種其中一種 * MODE_NORMAL 普通模式 * MODE_FULL_SCREEN 全屏模式 * MODE_TINY_WINDOW 小屏模式 * @param playerState 播放模式 */ @Override public void onPlayerStateChanged(int playerState) { switch (playerState) { case ConstantKeys.PlayMode.MODE_NORMAL: mBack.setVisibility(GONE); mFullScreen.setSelected(false); break; case ConstantKeys.PlayMode.MODE_FULL_SCREEN: mBack.setVisibility(VISIBLE); mFullScreen.setSelected(true); break; } //暫未實(shí)現(xiàn)全面屏適配邏輯盖袭,需要你自己補(bǔ)全 } }
- 然后該怎么使用這個(gè)自定義view呢?很簡(jiǎn)單彼宠,在之前基礎(chǔ)上鳄虱,通過(guò)控制器對(duì)象add進(jìn)來(lái)即可,代碼如下所示
controller = new BasisVideoController(this); AdControlView adControlView = new AdControlView(this); adControlView.setListener(new AdControlView.AdControlListener() { @Override public void onAdClick() { BaseToast.showRoundRectToast( "廣告點(diǎn)擊跳轉(zhuǎn)"); } @Override public void onSkipAd() { playVideo(); } }); controller.addControlComponent(adControlView); //設(shè)置控制器 mVideoPlayer.setController(controller); mVideoPlayer.setUrl(proxyUrl); mVideoPlayer.start();
09.視頻播放器優(yōu)化處理
9.1 如何兼容不同內(nèi)核播放器
- 提問(wèn):針對(duì)不同內(nèi)核播放器凭峡,比如谷歌的ExoPlayer拙已,B站的IjkPlayer,還有原生的MediaPlayer摧冀,有些api不一樣倍踪,那使用的時(shí)候如何統(tǒng)一api呢?
- 比如說(shuō)索昂,ijk和exo的視頻播放listener監(jiān)聽api就完全不同建车,這個(gè)時(shí)候需要做兼容處理
- 定義接口,然后各個(gè)不同內(nèi)核播放器實(shí)現(xiàn)接口椒惨,重寫抽象方法缤至。調(diào)用的時(shí)候,獲取接口對(duì)象調(diào)用api框产,這樣就可以統(tǒng)一Api
- 定義一個(gè)接口凄杯,這個(gè)接口有什么呢?這個(gè)接口定義通用視頻播放器方法秉宿,比如常見(jiàn)的有:視頻初始化戒突,設(shè)置url,加載描睦,以及播放狀態(tài)膊存,簡(jiǎn)單來(lái)說(shuō)可以分為三個(gè)部分。
- 第一部分:視頻初始化實(shí)例對(duì)象方法忱叭,主要包括:initPlayer初始化視頻隔崎,setDataSource設(shè)置視頻播放器地址,setSurface設(shè)置視頻播放器渲染view韵丑,prepareAsync開始準(zhǔn)備播放操作
- 第二部分:視頻播放器狀態(tài)方法爵卒,主要包括:播放,暫停撵彻,恢復(fù)钓株,重制实牡,設(shè)置進(jìn)度,釋放資源轴合,獲取進(jìn)度创坞,設(shè)置速度,設(shè)置音量
- 第三部分:player綁定view后受葛,需要監(jiān)聽播放狀態(tài)题涨,比如播放異常,播放完成总滩,播放準(zhǔn)備纲堵,播放size變化,還有播放準(zhǔn)備
- 首先定義一個(gè)工廠抽象類闰渔,然后不同的內(nèi)核播放器分別創(chuàng)建其具體的工廠實(shí)現(xiàn)具體類
- PlayerFactory:抽象工廠婉支,擔(dān)任這個(gè)角色的是工廠方法模式的核心,任何在模式中創(chuàng)建對(duì)象的工廠類必須實(shí)現(xiàn)這個(gè)接口
- ExoPlayerFactory:具體工廠澜建,具體工廠角色含有與業(yè)務(wù)密切相關(guān)的邏輯向挖,并且受到使用者的調(diào)用以創(chuàng)建具體產(chǎn)品對(duì)象。
- 如何使用炕舵,分為三步何之,具體操作如下所示
- 1.先調(diào)用具體工廠對(duì)象中的方法createPlayer方法;2.根據(jù)傳入產(chǎn)品類型參數(shù)獲得具體的產(chǎn)品對(duì)象咽筋;3.返回產(chǎn)品對(duì)象并使用溶推。
- 簡(jiǎn)而言之,創(chuàng)建對(duì)象的時(shí)候只需要傳遞類型type奸攻,而不需要對(duì)應(yīng)的工廠蒜危,即可創(chuàng)建具體的產(chǎn)品對(duì)象
- 這種創(chuàng)建對(duì)象最大優(yōu)點(diǎn)
- 工廠方法用來(lái)創(chuàng)建所需要的產(chǎn)品,同時(shí)隱藏了哪種具體產(chǎn)品類將被實(shí)例化這一細(xì)節(jié)睹耐,用戶只需要關(guān)心所需產(chǎn)品對(duì)應(yīng)的工廠辐赞,無(wú)須關(guān)心創(chuàng)建細(xì)節(jié),甚至無(wú)須知道具體產(chǎn)品類的類名硝训。
- 加入新的產(chǎn)品時(shí)响委,比如后期新加一個(gè)阿里播放器內(nèi)核,這個(gè)時(shí)候就只需要添加一個(gè)具體工廠和具體產(chǎn)品就可以窖梁。系統(tǒng)的可擴(kuò)展性也就變得非常好赘风,完全符合“開閉原則”
9.2 播放器UI抽取封裝優(yōu)化
- 發(fā)展中遇到的問(wèn)題
- 播放器可支持多種場(chǎng)景下的播放,多個(gè)產(chǎn)品會(huì)用到同一個(gè)播放器纵刘,這樣就會(huì)帶來(lái)一個(gè)問(wèn)題邀窃,一個(gè)播放業(yè)務(wù)播放器狀態(tài)發(fā)生變化,其他播放業(yè)務(wù)必須同步更新播放狀態(tài)假哎,各個(gè)播放業(yè)務(wù)之間互相交叉瞬捕,隨著播放業(yè)務(wù)的增多敲茄,開發(fā)和維護(hù)成本會(huì)急劇增加, 導(dǎo)致后續(xù)開發(fā)不可持續(xù)。
- UI難以自定義或者修改麻煩
- 比如常見(jiàn)的視頻播放器山析,會(huì)把視頻各種視圖寫到xml中,這種方式在后期代碼會(huì)很大掏父,而且改動(dòng)一個(gè)小的布局笋轨,則會(huì)影響大。這樣到后期往往只敢加代碼赊淑,而不敢刪除代碼……
- 有時(shí)候難以適應(yīng)新的場(chǎng)景爵政,比如添加一個(gè)播放廣告,老師開課陶缺,或者視頻引導(dǎo)業(yè)務(wù)需求钾挟,則需要到播放器中寫一堆業(yè)務(wù)代碼。迭代到后期饱岸,違背了開閉原則掺出,視頻播放器需要做到和業(yè)務(wù)分離
- 視頻播放器結(jié)構(gòu)需要清晰
- 也就是說(shuō)視頻player和ui操作柔和到了一起,尤其是兩者之間的交互苫费。比如播放中需要更新UI進(jìn)度條汤锨,播放異常需要顯示異常UI,都比較難處理播放器狀態(tài)變化更新UI操作
- 這個(gè)是指該視頻播放器能否看了文檔后快速上手百框,知道封裝的大概流程闲礼。方便后期他人修改和維護(hù),因此需要將視頻播放器功能分離铐维。比如切換內(nèi)核+視頻播放器(player+controller+view)
- 一定要解耦合柬泽,播放器player與視頻UI解耦:支持添加自定義視頻視圖,比如支持添加自定義廣告嫁蛇,新手引導(dǎo)锨并,或者視頻播放異常等視圖,這個(gè)需要較強(qiáng)的拓展性
- 適合多種業(yè)務(wù)場(chǎng)景
- 比如適合播放單個(gè)視頻睬棚,多個(gè)視頻琳疏,以及列表視頻,或者類似抖音那種一個(gè)頁(yè)面一個(gè)視頻闸拿,還有小窗口播放視頻空盼。也就是適合大多數(shù)業(yè)務(wù)場(chǎng)景
- 方便播放業(yè)務(wù)發(fā)生變化
- 播放狀態(tài)變化是導(dǎo)致不同播放業(yè)務(wù)場(chǎng)景之間交叉同步,解除播放業(yè)務(wù)對(duì)播放器的直接操控新荤,采用接口監(jiān)聽進(jìn)行解耦揽趾。比如:player+controller+interface
- 關(guān)于視頻播放器
- 定義一個(gè)視頻播放器InterVideoPlayer接口,操作視頻播放苛骨,暫停篱瞎,緩沖苟呐,進(jìn)度設(shè)置,設(shè)置播放模式等多種操作俐筋。
- 然后寫一個(gè)播放器接口的具體實(shí)現(xiàn)類牵素,在這個(gè)里面拿到內(nèi)核播放器player,然后做相關(guān)的實(shí)現(xiàn)操作澄者。
- 關(guān)于視頻視圖View
- 定義一個(gè)視圖InterVideoController接口笆呆,主要負(fù)責(zé)視圖顯示/隱藏,播放進(jìn)度粱挡,鎖屏赠幕,狀態(tài)欄等操作。
- 然后寫一個(gè)播放器視圖接口的具體實(shí)現(xiàn)類询筏,在這里里面inflate視圖操作榕堰,然后接口方法實(shí)現(xiàn),為了方便后期開發(fā)者自定義view嫌套,因此需要addView操作逆屡,將添加進(jìn)來(lái)的視圖用map集合裝起來(lái)。
- 播放器player和controller交互
- 在player中創(chuàng)建BaseVideoController對(duì)象踱讨,這個(gè)時(shí)候需要把controller添加到播放器中康二,這個(gè)時(shí)候有兩個(gè)要點(diǎn)特別重要,需要把播放器狀態(tài)監(jiān)聽勇蝙,和播放模式監(jiān)聽傳遞給控制器
- setPlayState設(shè)置視頻播放器播放邏輯狀態(tài)沫勿,主要是播放緩沖,加載味混,播放中产雹,暫停,錯(cuò)誤翁锡,完成蔓挖,異常,播放進(jìn)度等多個(gè)狀態(tài)馆衔,方便控制器做UI更新操作
- setPlayerState設(shè)置視頻播放切換模式狀態(tài)瘟判,主要是普通模式,小窗口模式角溃,正常模式三種其中一種拷获,方便控制器做UI更新
- 播放器player和view交互
- 這塊非常關(guān)鍵,舉個(gè)例子减细,視頻播放失敗需要顯示控制層的異常視圖View匆瓜;播放視頻初始化需要顯示loading,然后更新UI播放進(jìn)度條等。都是播放器和視圖層交互
- 可以定義一個(gè)類驮吱,同時(shí)實(shí)現(xiàn)InterVideoPlayer接口和InterVideoController接口茧妒,這個(gè)時(shí)候會(huì)重新這兩個(gè)接口所有的方法。此類的目的是為了在InterControlView接口實(shí)現(xiàn)類中既能調(diào)用VideoPlayer的api又能調(diào)用BaseVideoController的api
- 如何添加自定義播放器視圖
- 添加了自定義播放器視圖左冬,比如添加視頻廣告桐筏,可以選擇跳過(guò),選擇播放暫停拇砰。那這個(gè)視圖view梅忌,肯定是需要操作player或者獲取player的狀態(tài)的。這個(gè)時(shí)候就需要暴露監(jiān)聽視頻播放的狀態(tài)接口監(jiān)聽
- 首先定義一個(gè)InterControlView接口毕匀,也就是說(shuō)所有自定義視頻視圖view需要實(shí)現(xiàn)這個(gè)接口,該接口中的核心方法有:綁定視圖到播放器癌别,視圖顯示隱藏變化監(jiān)聽皂岔,播放狀態(tài)監(jiān)聽展姐,播放模式監(jiān)聽教馆,進(jìn)度監(jiān)聽板鬓,鎖屏監(jiān)聽等
- 在BaseVideoController中的狀態(tài)監(jiān)聽中,通過(guò)InterControlView接口對(duì)象就可以把播放器的狀態(tài)傳遞到子類中
9.4 代碼方面優(yōu)化措施
-
如果是在Activity中的話,建議設(shè)置下面這段代碼
@Override protected void onResume() { super.onResume(); if (mVideoPlayer != null) { //從后臺(tái)切換到前臺(tái)绵患,當(dāng)視頻暫停時(shí)或者緩沖暫停時(shí)掘殴,調(diào)用該方法重新開啟視頻播放 mVideoPlayer.resume(); } } @Override protected void onPause() { super.onPause(); if (mVideoPlayer != null) { //從前臺(tái)切到后臺(tái),當(dāng)視頻正在播放或者正在緩沖時(shí)套菜,調(diào)用該方法暫停視頻 mVideoPlayer.pause(); } } @Override protected void onDestroy() { super.onDestroy(); if (mVideoPlayer != null) { //銷毀頁(yè)面戏溺,釋放托享,內(nèi)部的播放器被釋放掉,同時(shí)如果在全屏闰围、小窗口模式下都會(huì)退出 mVideoPlayer.release(); } } @Override public void onBackPressed() { //處理返回鍵邏輯止潘;如果是全屏,則退出全屏辫诅;如果是小窗口凭戴,則退出小窗口 if (mVideoPlayer == null || !mVideoPlayer.onBackPressed()) { super.onBackPressed(); } }
10.播放器問(wèn)題記錄說(shuō)明
11.性能優(yōu)化和庫(kù)大小
12.視頻緩存原理介紹
- 網(wǎng)絡(luò)上比較好的項(xiàng)目:https://github.com/danikula/AndroidVideoCache
- 網(wǎng)絡(luò)用的HttpURLConnection,文件緩存處理炕矮,文件最大限度策略么夫,回調(diào)監(jiān)聽處理,斷點(diǎn)續(xù)傳,代理服務(wù)等肤视。
- 但是存在一些問(wèn)題档痪,比如如下所示
- 文件的緩存超過(guò)限制后沒(méi)有按照l(shuí)ru算法刪除,
- 處理返回給播放器的http響應(yīng)頭消息邢滑,響應(yīng)頭消息的獲取處理改為head請(qǐng)求(需服務(wù)器支持)
- 替換網(wǎng)絡(luò)庫(kù)為okHttp(因?yàn)榇蟛糠值捻?xiàng)目都是以okHttp為網(wǎng)絡(luò)請(qǐng)求庫(kù)的)腐螟,但是這個(gè)改動(dòng)性比較大
- 然后看一下怎么使用,超級(jí)簡(jiǎn)單。傳入視頻url鏈接乐纸,返回一個(gè)代理鏈接衬廷,然后就可以呢
HttpProxyCacheServer cacheServer = ProxyVideoCacheManager.getProxy(this); String proxyUrl = cacheServer.getProxyUrl(URL_AD); mVideoPlayer.setUrl(proxyUrl); public static HttpProxyCacheServer getProxy(Context context) { return sharedProxy == null ? (sharedProxy = newProxy(context)) : sharedProxy; } private static HttpProxyCacheServer newProxy(Context context) { return new HttpProxyCacheServer.Builder(context) .maxCacheSize(512 * 1024 * 1024) // 512MB for cache //緩存路徑,不設(shè)置默認(rèn)在sd_card/Android/data/[app_package_name]/cache中 //.cacheDirectory() .build(); }
- 大概的原理
- 原始的方式是直接塞播放地址給播放器汽绢,它就可以直接播放÷鸢希現(xiàn)在我們要在中間加一層本地代理,播放器播放的時(shí)候(獲取數(shù)據(jù))是通過(guò)我們的本地代理的地址來(lái)播放的宁昭,這樣我們就可以很好的在中間層(本地代理層)做一些處理跌宛,比如:文件緩存,預(yù)緩存(秒開處理)积仗,監(jiān)控等疆拘。
- 原理詳細(xì)一點(diǎn)來(lái)說(shuō)
- 1.采用了本地代理服務(wù)的方式,通過(guò)原始url給播放器返回一個(gè)本地代理的一個(gè)url 寂曹,代理URL類似:http://127.0.0.1:port/視頻url哎迄;(port端口為系統(tǒng)隨機(jī)分配的有效端口,真實(shí)url是為了真正的下載)稀颁,然后播放器播放的時(shí)候請(qǐng)求到了你本地的代理上了芬失。
- 2.本地代理采用ServerSocket監(jiān)聽127.0.0.1的有效端口楣黍,這個(gè)時(shí)候手機(jī)就是一個(gè)服務(wù)器了匾灶,客戶端就是socket,也就是播放器租漂。
- 3.讀取客戶端就是socket來(lái)讀取數(shù)據(jù)(http協(xié)議請(qǐng)求)解析http協(xié)議阶女。
- 4.根據(jù)url檢查視頻文件是否存在,讀取文件數(shù)據(jù)給播放器哩治,也就是往socket里寫入數(shù)據(jù)(socket通信)秃踩。同時(shí)如果沒(méi)有下載完成會(huì)進(jìn)行斷點(diǎn)下載,當(dāng)然弱網(wǎng)的話數(shù)據(jù)需要生產(chǎn)消費(fèi)同步處理。
- 如何實(shí)現(xiàn)預(yù)加載
- 其實(shí)預(yù)加載的思路很簡(jiǎn)單业筏,在進(jìn)行一個(gè)播放視頻后憔杨,再返回接下來(lái)需要預(yù)加載的視頻url,啟用線程去請(qǐng)求下載數(shù)據(jù)
- 開啟一個(gè)線程去請(qǐng)求并預(yù)加載一部分的數(shù)據(jù)蒜胖,可能需要預(yù)加載的數(shù)據(jù)大于>1消别,利用隊(duì)列先進(jìn)入的先進(jìn)行加載,因此可以采用LinkedHashMap保存正在預(yù)加載的task台谢。
- 在開始預(yù)加載的時(shí)候寻狂,判斷該播放地址是否已經(jīng)預(yù)加載,如果不是那么創(chuàng)建一個(gè)線程task朋沮,并且把它放到map集合中蛇券。然后執(zhí)行預(yù)加載邏輯,也就是執(zhí)行HttpURLConnection請(qǐng)求
- 提供取消對(duì)應(yīng)url加載的任務(wù),因?yàn)橛锌赡茉搖rl不需要再進(jìn)行預(yù)加載了纠亚,比如參考抖音塘慕,當(dāng)用戶瞬間下滑幾個(gè)視頻,那么很多視頻就需要跳過(guò)了不需要再進(jìn)行預(yù)加載
- 具體直接看項(xiàng)目代碼:VideoCache緩沖模塊
13.查看視頻播放器日志
- 統(tǒng)一管理視頻播放器封裝庫(kù)日志菜枷,方便后期排查問(wèn)題
- 比如苍糠,視頻內(nèi)核,日志過(guò)濾則是:aaa
- 比如啤誊,視頻player岳瞭,日志過(guò)濾則是:bbb
- 比如,緩存模塊蚊锹,日志過(guò)濾則是:VideoCache
14.該庫(kù)異常code說(shuō)明
- 針對(duì)視頻封裝庫(kù)瞳筏,統(tǒng)一處理拋出的異常,為了方便開發(fā)者快速知道異常的來(lái)由牡昆,則可以查詢約定的code碼姚炕。
- 這個(gè)在sdk中特別常見(jiàn),因此該庫(kù)一定程度是借鑒騰訊播放器……