頁(yè)視頻播放器選型與實(shí)戰(zhàn):ckplay多格式播放代碼拆解)
簡(jiǎn)介這是一款面向網(wǎng)頁(yè)開(kāi)發(fā)者的輕量級(jí)視頻播放組件基于ckplayer構(gòu)建主打免費(fèi)、小巧與高度可定制適合需要在網(wǎng)站中嵌入點(diǎn)播、直播或直播回看功能的開(kāi)發(fā)者使用。壓縮包共27個(gè)文件約3.47MB包含9個(gè)html示例頁(yè)、4個(gè)js腳本、4個(gè)xml配置、3個(gè)png圖片以及swf、srt、vtt、json等資源分別用于頁(yè)面演示、播放邏輯、界面與語(yǔ)言配置、字幕與廣告數(shù)據(jù)。功能上支持m3u8、mp4、flv、f4v、swf及jpg、png、gif等圖片格式播放PC端內(nèi)置m3u8普通加密與私有加密播放并支持清晰度自動(dòng)列表、彈幕、字幕、自定義按鈕與JavaScript交互。廣告體系覆蓋前置、暫停、插入、結(jié)尾及角標(biāo)橫幅等多種類(lèi)型。目前已有2140人學(xué)習(xí)下載讀者可借助示例頁(yè)面快速理解調(diào)用方式按需定制界面風(fēng)格與加密策略搭建完整的網(wǎng)頁(yè)視頻播放方案。1. 網(wǎng)頁(yè)視頻播放器選型為什么 ckplay 這類(lèi)方案值得拆一遍做在線(xiàn)教育、短視頻站或者企業(yè)內(nèi)訓(xùn)平臺(tái)的朋友大概率都遇到過(guò)同一個(gè)需求網(wǎng)頁(yè)里要能播 m3u8也要能播 mp4偶爾還得兼容 flv、f4v 這些老格式圖片資源 jpg、png、gif 甚至 swf 也得能預(yù)覽。瀏覽器原生video標(biāo)簽只認(rèn) mp4、webm、ogg 這幾種遇到 m3u8 切片流直接罷工flv 更是碰都不碰。這時(shí)候要么上商業(yè)播放器 SDK要么自己拼一套開(kāi)源方案。ckplay 這類(lèi)網(wǎng)頁(yè)視頻播放軟件代碼就是沖著這個(gè)場(chǎng)景來(lái)的——一份前端代碼包把多種格式的播放能力封裝好拿過(guò)來(lái)改改配置就能嵌進(jìn)自己的頁(yè)面。它適合誰(shuí)適合不想從零造輪子、又不想被商業(yè)授權(quán)綁死的中小團(tuán)隊(duì)前端也適合想研究播放器內(nèi)核怎么處理多格式切換的開(kāi)發(fā)者。下面我按實(shí)際拆包復(fù)現(xiàn)的順序把這份代碼的用法、參數(shù)和坑講清楚。2. 拆開(kāi)代碼包目錄結(jié)構(gòu)、依賴(lài)與格式支持邊界2.1 先看目錄別急著改代碼拿到一份網(wǎng)頁(yè)播放器代碼第一件事不是打開(kāi) index.html 就改而是先摸清目錄結(jié)構(gòu)。常見(jiàn)的 ckplay 類(lèi)代碼包根目錄下一般會(huì)有這么幾塊目錄/文件作用是否要改index.html演示入口含播放器容器和初始化腳本參考不直接用于生產(chǎn)js/ckplay.js播放器核心邏輯格式判斷與調(diào)度核心謹(jǐn)慎改js/hls.min.jsm3u8 解析依賴(lài)一般不動(dòng)js/flv.min.jsflv/f4v 解析依賴(lài)一般不動(dòng)css/ckplay.css播放器皮膚與控制條樣式按需改demo/各格式示例頁(yè)面測(cè)試用這個(gè)結(jié)構(gòu)說(shuō)明一件事m3u8 和 flv 不是播放器自己實(shí)現(xiàn)的而是靠 hls.js 和 flv.js 這兩個(gè)庫(kù)兜底。ckplay 的價(jià)值在于把「根據(jù)擴(kuò)展名或 MIME 自動(dòng)選內(nèi)核」這件事封裝了你不用自己寫(xiě)一堆 if-else 去判斷該加載哪個(gè)庫(kù)。2.2 格式支持的真實(shí)邊界標(biāo)題里列了 m3u8、mp4、flv、jpg、jpeg、png、gif、swf、f4v 一大串但它們的支持程度完全不是一個(gè)級(jí)別拆之前心里要有數(shù)mp4瀏覽器原生支持走video直接播最穩(wěn)。m3u8靠 hls.js需要瀏覽器支持 MediaSource Extensions現(xiàn)代瀏覽器基本都行但 Safari 原生就支持 m3u8反而不用 hls.js代碼里通常要做分支。flv / f4v靠 flv.js同樣依賴(lài) MSE且 flv.js 對(duì)編碼有要求必須是 H.264 AAC遇到 H.265 的 flv 直接黑屏。jpg / jpeg / png / gif這些是圖片播放器里通常是當(dāng)「圖片預(yù)覽」處理用img渲染不是視頻解碼。swfFlash 已經(jīng)徹底退出歷史舞臺(tái)現(xiàn)代瀏覽器不再支持。代碼里保留 swf 多半是歷史遺留實(shí)際跑起來(lái)會(huì)提示不支持別在這上面浪費(fèi)時(shí)間。提示如果你的源站 flv 是 H.265 編碼別指望 flv.js 能救轉(zhuǎn)碼成 H.264 或者換 fmp4 才是正路。2.3 依賴(lài)加載順序不能亂hls.js 和 flv.js 必須在 ckplay.js 之前加載否則初始化時(shí)會(huì)報(bào)Hls is not defined或flvjs is not defined。常見(jiàn)做法是在head里按順序引入!-- 先加載解析庫(kù)再加載播放器核心 -- script srcjs/hls.min.js/script script srcjs/flv.min.js/script script srcjs/ckplay.js/script順序反了播放器初始化時(shí)找不到全局對(duì)象控制臺(tái)直接拋錯(cuò)頁(yè)面一片空白。這個(gè)坑我見(jiàn)過(guò)不止一次尤其是用打包工具動(dòng)態(tài) import 的時(shí)候容易把順序打亂。3. 初始化與參數(shù)配置把播放器嵌進(jìn)自己的頁(yè)面3.1 最小可用初始化ckplay 類(lèi)播放器一般提供一個(gè)全局構(gòu)造函數(shù)傳入容器選擇器和配置對(duì)象。最小可用寫(xiě)法大概是這樣// 容器必須先在 DOM 里存在 var player new CkPlayer({ container: #videoBox, // 播放器掛載的容器選擇器 url: ./media/demo.m3u8, // 視頻地址支持相對(duì)或絕對(duì)路徑 autoplay: false, // 是否自動(dòng)播放建議 false muted: false, // 是否靜音自動(dòng)播放時(shí)通常要 true loop: false, // 是否循環(huán) poster: ./images/cover.jpg,// 封面圖 width: 100%, // 播放器寬度 height: 480px, // 播放器高度 controls: true // 是否顯示控制條 });邏輯說(shuō)明構(gòu)造函數(shù)內(nèi)部會(huì)根據(jù)url的擴(kuò)展名判斷走哪條解碼路徑。.m3u8走 hls.js.flv/.f4v走 flv.js.mp4直接賦給video的 src圖片格式則替換成img渲染。參數(shù)里autoplay和muted是一對(duì)現(xiàn)代瀏覽器策略要求自動(dòng)播放必須靜音所以如果你要自動(dòng)播放兩個(gè)都得配合設(shè)置。3.2 關(guān)鍵參數(shù)逐個(gè)說(shuō)參數(shù)類(lèi)型默認(rèn)值說(shuō)明containerstring無(wú)必填容器選擇器urlstring無(wú)必填媒體地址autoplaybooleanfalse自動(dòng)播放受瀏覽器策略限制mutedbooleanfalse靜音自動(dòng)播放時(shí)建議 trueloopbooleanfalse循環(huán)播放posterstring封面圖地址width/heightstring100% / auto尺寸建議用 CSS 控制controlsbooleantrue是否顯示控制條typestringauto強(qiáng)制指定格式auto 為自動(dòng)判斷type這個(gè)參數(shù)容易被忽略。自動(dòng)判斷靠擴(kuò)展名但如果你的 URL 帶查詢(xún)參數(shù)比如video.m3u8?tokenxxx擴(kuò)展名解析可能失敗這時(shí)候手動(dòng)指定type: m3u8更穩(wěn)妥。3.3 動(dòng)態(tài)切換視頻源實(shí)際項(xiàng)目里經(jīng)常要在不刷新頁(yè)面的情況下?lián)Q視頻播放器一般提供switchUrl或load方法// 切換前先銷(xiāo)毀舊實(shí)例避免內(nèi)存泄漏 player.switchUrl(./media/next.flv, flv);邏輯說(shuō)明切換時(shí)內(nèi)部會(huì)先停掉當(dāng)前解碼器釋放 MSE 的 SourceBuffer再根據(jù)新格式重新初始化。如果不先停舊實(shí)例多個(gè)解碼器同時(shí)占用資源頁(yè)面會(huì)越來(lái)越卡這是血淚經(jīng)驗(yàn)。參數(shù)第二個(gè)是格式類(lèi)型不傳則自動(dòng)判斷。4. 多格式播放的坑m3u8 跨域、flv 編碼與圖片預(yù)覽4.1 m3u8 跨域與 CORSm3u8 本質(zhì)是一系列 ts 切片瀏覽器通過(guò) XHR 拉取。如果 m3u8 和 ts 文件不在同源服務(wù)端必須返回正確的 CORS 頭# Nginx 配置示例給 m3u8 和 ts 加上跨域頭 location ~* \.(m3u8|ts)$ { add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods GET, OPTIONS; add_header Access-Control-Allow-Headers Range; }邏輯說(shuō)明hls.js 拉取切片時(shí)會(huì)帶 Range 頭做分段請(qǐng)求所以Access-Control-Allow-Headers里必須包含 Range否則預(yù)檢請(qǐng)求失敗播放器報(bào)CORS error但控制臺(tái)不一定給明確提示容易誤判成網(wǎng)絡(luò)問(wèn)題。4.2 flv 編碼不匹配的黑屏flv.js 只支持 H.264 視頻 AAC 音頻。如果源站 flv 是 H.265 或者音頻是 MP3播放器會(huì)加載成功但畫(huà)面黑屏、無(wú)聲音。排查方法是看 flv.js 的日志// 開(kāi)啟 flv.js 調(diào)試日志 flvjs.LoggingControl.enableAll true;邏輯說(shuō)明開(kāi)啟后控制臺(tái)會(huì)打印媒體信息能看到實(shí)際的編碼格式。如果 codec 不是 avc1 開(kāi)頭基本可以判定編碼不支持需要服務(wù)端轉(zhuǎn)碼。4.3 圖片格式的預(yù)覽處理jpg、png、gif 這些在播放器里不是「播放」而是「展示」。代碼內(nèi)部通常判斷擴(kuò)展名后把video替換成img。這里有個(gè)坑gif 動(dòng)圖如果被當(dāng)成視頻處理會(huì)只顯示第一幀。正確做法是識(shí)別到 gif 直接走img讓瀏覽器自己處理動(dòng)畫(huà)。注意swf 格式在現(xiàn)代瀏覽器已經(jīng)無(wú)法播放代碼里即便保留分支實(shí)際也會(huì)走 fallback 提示。如果你的資源里有 swf建議提前轉(zhuǎn)成 mp4 或 gif。5. 避坑與排查五個(gè)真實(shí)翻車(chē)記錄5.1 現(xiàn)象m3u8 能播但一直卡頓原因切片時(shí)長(zhǎng)過(guò)短或碼率過(guò)高h(yuǎn)ls.js 緩沖跟不上。常見(jiàn)于把 2 秒切片配 4Mbps 碼率的場(chǎng)景。解決服務(wù)端把切片時(shí)長(zhǎng)調(diào)到 6 到 10 秒碼率按目標(biāo)分辨率控制在合理范圍1080p 建議 3 到 5 Mbps。5.2 現(xiàn)象flv 播放幾秒后卡死原因flv.js 的 MSE 緩沖區(qū)滿(mǎn)了沒(méi)有及時(shí)清理多見(jiàn)于直播流場(chǎng)景。解決在配置里開(kāi)啟enableStashBuffer: false或者監(jiān)聽(tīng)statistics_info事件手動(dòng)控制緩沖。5.3 現(xiàn)象自動(dòng)播放沒(méi)反應(yīng)原因?yàn)g覽器自動(dòng)播放策略要求靜音autoplay: true但muted: false會(huì)被攔截。解決兩個(gè)參數(shù)同時(shí)設(shè)置或者監(jiān)聽(tīng)用戶(hù)交互后再調(diào)用play()。5.4 現(xiàn)象切換視頻后舊聲音還在原因舊實(shí)例沒(méi)有銷(xiāo)毀音頻軌道仍在播放。解決切換前調(diào)用player.destroy()或player.pause()確認(rèn)舊實(shí)例釋放后再加載新源。5.5 現(xiàn)象移動(dòng)端全屏按鈕失效原因iOS Safari 對(duì)全屏 API 支持有限r(nóng)equestFullscreen在 iPhone 上不生效。解決iOS 上依賴(lài)原生video的全屏行為不要自己實(shí)現(xiàn)全屏按鈕或者用webkitEnterFullscreen做兼容。6. 進(jìn)階技巧自定義控制條與格式探測(cè)6.1 用 canPlayType 做格式預(yù)判在初始化之前可以先探測(cè)瀏覽器能力避免加載了庫(kù)卻播不了// 探測(cè)瀏覽器對(duì)特定格式的支持 var video document.createElement(video); var canMp4 video.canPlayType(video/mp4; codecsavc1.42E01E); var canHls video.canPlayType(application/vnd.apple.mpegurl); console.log(mp4:, canMp4, hls native:, canHls);邏輯說(shuō)明canPlayType返回probably、maybe或空字符串。如果 hls 原生支持Safari就不用加載 hls.js直接給 video.src 賦值 m3u8 地址即可省一個(gè)庫(kù)的加載。這個(gè)判斷在移動(dòng)端尤其有用能減少不必要的資源請(qǐng)求。6.2 自定義控制條的事件綁定ckplay 默認(rèn)控制條夠用但品牌定制往往要換。自定義控制條的核心是綁定播放器的狀態(tài)事件事件名觸發(fā)時(shí)機(jī)常用場(chǎng)景play開(kāi)始播放切換按鈕圖標(biāo)pause暫停切換按鈕圖標(biāo)timeupdate播放進(jìn)度變化更新進(jìn)度條ended播放結(jié)束顯示重播按鈕error出錯(cuò)展示錯(cuò)誤提示綁定方式// 監(jiān)聽(tīng)播放器事件驅(qū)動(dòng)自定義 UI player.on(timeupdate, function(data) { // data.currentTime 當(dāng)前時(shí)間data.duration 總時(shí)長(zhǎng) var percent (data.currentTime / data.duration) * 100; document.querySelector(.progress-bar).style.width percent %; });邏輯說(shuō)明timeupdate觸發(fā)頻率約每秒 4 次足夠驅(qū)動(dòng)進(jìn)度條但不要在里面做重計(jì)算否則影響播放流暢度。進(jìn)度條拖動(dòng)則調(diào)用player.seek(seconds)。6.3 一個(gè)我踩過(guò)的坑早期我圖省事把 hls.js 和 flv.js 都用 CDN 引入結(jié)果某次 CDN 抽風(fēng)播放器直接白屏用戶(hù)投訴一片。從那以后我每次集成播放器都強(qiáng)制把依賴(lài)庫(kù)下載到本地并且在初始化前加一段能力探測(cè)確認(rèn)庫(kù)加載成功再 new 播放器。這個(gè)習(xí)慣幫我省了至少三次線(xiàn)上事故。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取