切圖網一個項目切圖中遇到的,網頁中嵌入視頻 理所當然用html5自帶的video標簽即可實現,也有比較主流的插件videojs,但是這個比較特別播放的視頻是m3u8格式的 (這種好像imac safari下用的多),這種情況需要添加額外的支持,好在hls.min.js可以實現。附代碼 親測可用
html5的video標簽實現對HLS(m3u8格式)的支持 親測可用
技術提升美好事物發生的概率。Technologically, for greater probability to be happy
只要在 HTML5 中使用過視頻播放的同學對 video 標簽一定不會陌生,不過很多同學只使用了 video 的基礎功能,實際上 video 擁有強大潛能的,只要姿勢正確就能讓其擁有超能力。不妨從下面幾個場景來逐漸了解下video 未曾被發掘的神秘空間:
點播領域里 mp4 是最普遍、兼容性最好的視頻容器,不過 mp4 也有它的局限性,比如常見的清晰度切換,我們是無法像youtube那樣做到無縫切換的。我們可以看下普通的mp4播放的網絡請求和youtube視頻播放的網絡請求的區別。
圖1.1 普通mp4的下載請求過程
圖1.2 Youtube視頻下載請求過程
這兩張圖不難看出,在默認情況下 mp4 使用一次 http 請求所有的視頻數據,Youtube 則分次請求。當然這個描述很不專業,但確實形象。造成這種差異的是 video 不支持流式的視頻數據,Youtube 采用的是流式的視頻容器 webm,而 mp4 是非流式的。那如何解釋清楚流式的視頻數據呢,從專業的角度三言兩語很難說清楚,但用大白話翻譯過來就是流式的視頻數據支持分段獨立播放,非流式的不可以。換句話說一個10M的視頻文件,流式的視頻可以把0~1M的數據請求回來單獨播放,但是非流式的不可以。
上面我們描述了視頻格式的不同,接下來我們要說的是第一張圖中的視頻加載是瀏覽器來控制的,通過給 video 的 src 屬性配置視頻地址,觸發播放之后瀏覽器就會開始下載了,JS干涉不了。而 Youtube 的視頻加載是通過JS來控制的,各位可以再次看下第二張圖的網絡請求類型:xhr,足以證明這一點。
上面兩點搞清楚之后我們就該說下清晰度切換的事情了。這個需求大家都不陌生,但是直接使用 mp4 格式做無縫清晰度切換,難度還挺大的。先解釋下“無縫清晰度切換”的概念:從播放一個分辨率的視頻到另一個分辨率且保證畫面、聲音不停頓的平滑切換過程。了解了這個概念,大家應該知道了用 video 無縫切換 mp4 有多難。一方面,video 是不支持流式的視頻格式的,一方面,video 的加載是不受JS控制的。通過切換 video 的 src 屬性,必然會導致畫面中斷、重新請求視頻數據等。有的同學想到說利用兩個 video 再結合 z-index 來搞,但是當你生成另一個video去加載視頻的時候,無法保證兩個畫面是嚴格一致的,即使將原來的畫面暫停到一個時刻,用另一個視頻通過 currentTime 屬性與之同步,切換仍然看到畫面閃爍,基本無法和 Youtube 無縫切換的體驗匹敵。而且還會造成更多流量的浪費,背后的原因大家可以研究下 mp4 容器和 webm 容器的異同,也可以看下視頻解碼相關的文章。
還有一種方法就是將 mp4 格式統統轉碼到流式的視頻格式比如 hls、webm 等。不過這種看上去可行的方式實際上會帶來很大的成本開銷,如將大量視頻做轉碼會消耗高昂的機器資源、雙倍存儲的費用、CDN的雙倍費用等等。其實我們也是在這種背景下研究出來新的技術問題解決清晰度無縫切換的。
首先,我們改變對 mp4 視頻的播放流程,不再直接使用 video 的 src 來播放,因為我們沒有任何可以操作的空間。video不僅支持 src 屬性還支持 Blob 對象,我們就是利用后者。播放的流程如下:
圖1.3 mp4 視頻新播放流程
然后在做清晰度切換的時候流程如下:
圖1.4 mp4視頻清晰度切換原理示意圖
圖1.5 mp4視頻清晰度切換流程示意圖
這個過程看上去比較繁瑣,但是所有的操作都是在瀏覽器端完成,也就是說都是JS來實現的。這樣之前說的所有成本問題都不存在,還能做到youtube相同體驗的無縫切換。如果大家也想使用這個功能不需要自己再去實現一遍上述流程,可以使用如下代碼:
如果對這段代碼有什么疑惑,或者想深入了解下它背后是如何實現的,可以參考 Github:https://github.com/bytedance/xgplayer 或 閱讀原文:https://techblog.toutiao.com/
使用 video 的同學基本上都是這樣用的,如下:
2.利用source標簽
這樣就可以播放視頻了,不過前面我們講過這樣使用 video ,視頻的加載是受瀏覽器控制的,可以看下瀏覽器在視頻剛開始播放的時候下載了多少數據:
圖2.1 video默認下載截圖
我隨便找了個視頻,大家看下視頻總長度是 02:08,在播放到 00:05 的時候,瀏覽器已經下載到 01:30 了,如果用戶終止觀看,下載的視頻就這樣被浪費掉了。當然,如果不斷的 seek 也會造成較多的流量浪費。按照我們之前的統計在短視頻領域,用戶 seek 的頻率在 80%,所以這部分流量是可以節省掉的。具體原理如下:
圖2.2 播放器加載視頻原理
具體實現代碼如下:
這樣就實現了視頻在播放過程中永遠只預加載10秒的數據,進而保證節省流量。
擴展鏈接,了解超能力西瓜視頻是怎樣煉成的。
HLS 全稱是 HTTP Live Streaming, 是一個由 Apple 公司實現的基于 HTTP 的媒體流傳輸協議. 他跟 DASH 協議的原理非常類似. 通過將整條流切割成一個小的可以通過 HTTP 下載的媒體文件, 然后提供一個配套的媒體列表文件, 提供給客戶端, 讓客戶端順序地拉取這些媒體文件播放, 來實現看上去是在播放一條流的效果.
由于傳輸層協議只需要標準的 HTTP 協議, HLS 可以方便的透過防火墻或者代理服務器, 而且可以很方便的利用 CDN 進行分發加速, 并且客戶端實現起來也很方便.
HLS 目前廣泛地應用于點播和直播領域.
在 HTML5 頁面上使用 HLS 非常簡單:
直接:
<video src="example.m3u8" controls></video>
或者:
<video controls>
<source src="example.m3u8">
</source></video>
下面, 我將會概括性地介紹 HLS 協議的方方面面(并且包括 AES 加密部分的內容), 配合 HLS 的 RFC 食用效果更佳.
hls_arch 上面是 HLS 整體架構圖, 可以看出, 總共有三個部分: Server, CDN, Client.
其實, HLS 協議的主要內容是關于 M3U8 這個文本協議的, 其實生成與解析都非常簡單. 為了更加直接地說明這一點, 我下面舉兩個簡單的例子:
簡單的 Media Playlist:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:8
#EXT-X-MEDIA-SEQUENCE:2680
#EXTINF:7.975,
https://priv.example.com/fileSequence2680.ts
#EXTINF:7.941,
https://priv.example.com/fileSequence2681.ts
#EXTINF:7.975,
https://priv.example.com/fileSequence2682.ts
包含多種比特率的 Master Playlist:
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1280000
http://example.com/low.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2560000
http://example.com/mid.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=7680000
http://example.com/hi.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=65000,CODECS="mp4a.40.5"
http://example.com/audio-only.m3u8
即最常見的 TS 文件.
免費分享,2022最新最全學習提升資料包,資料內容包括《Andoird音視頻開發必備手冊+音視頻最新學習視頻+大廠面試真題+2022最新學習路線圖》點擊領取FFmpegWebRTCRTMPRTSPHLSRTP播放器-音視頻流媒體高級開發
音視頻學習交流扣裙788280672
即常提到的 fMP4.
Playlist 文件的格式是起源于 M3U, 并且繼承兩個 tag: EXTM3U 和 EXTINF 下面的 tags 通過 BNF-style 語法來指定.
Basic Tags 可以用在 Media Playlist 和 Master Playlist 里面.
每一個 Media Segment 通過一系列的 Media Segment tags 跟一個 URI 來指定. 有的 Media Segment tags 只應用與下一個 segment, 有的則是應用所有下面的 segments. 一個 Media Segment tag 只能出現在 Media Playlist 里面.
Media Playlist tags 描述 Media Playlist 的全局參數. 同樣地, Media Playlist tags 只能出現在 Media Playlist 里面.
Master Playlist tags 定義 Variant Streams, Renditions 和 其他顯示的全局參數. Master Playlist tags 只能出現在 Master Playlist 中.
這里的 tags 可以出現在 Media Playlist 或者 Master Playlist 中. 但是如果同時出現在同一個 Master Playlist 和 Media Playlist 中時, 必須為相同值.
以下流程僅供參考, 其實不同的播放器客戶端以及服務器端的拉取規則都有很多細節差異.
由于客戶端每次請求 TS 或 M3U8 有可能都是一個新的連接請求, 所以, 我們無法有效的標識客戶端, 一旦出現問題, 基本無法有效的定位問題, 所以, 一般工業級的服務器都會對傳統的 HLS 做一些改進.
這里主要介紹網宿的 Variant HLS 與又拍云的 HLS+.
首先, 我們可以下載一條網宿的 M3U8 文件:
wget http://bililive.kksmg.com/hls/stvd6edb9a6_45b34047833af658bf4945a8/playlist.m3u8
然后, 打開下載得到的 playlist 文件:
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=781000
http://bililive.kksmg.com/hls/stvd6edb9a6_45b34047833af658bf4945a8/playlist.m3u8?
wsSession=0105cb4e8fe63bccab511a4a-
149017212774715&wsIPSercert=b80d38c068c9e3634a7ebb2f2bbf9b89&wsMonitor=-1
可以看出這是一個 Master Playlist, 里面嵌套了一層 M3U8, 同時可以看出網宿采用 wsSession 來標識一條播放連接.
Variant HLS
首先, 我們可以下載一條又拍云的 M3U8 文件:
wget http://uplive.b0.upaiyun.com/live/loading.m3u8
然后, 打開下載得到的 playlist 文件:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-ALLOW-CACHE:YES
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-TARGETDURATION:1
#EXTINF:0.998, no desc
http://183.158.35.12:8080/uplive.b0.upaiyun.com/live/loading-0.ts?
shp_uuid=e4989f34fcab282e21ef1fd2980284cb&shp_ts=1490172420851&shp_cid=17906&shp_pid=3
370578&shp_sip0=127.0.0.1&shp_sip1=183.158.35.12&domain=uplive.b0.upaiyun.com&shp_seqno=0
可以看出又拍云的 HLS+ 也支持這種 Variant HLS 方式來標識一條 HLS 連接, 可以看出, 又拍云使用 uuid 來表示一條 HLS 連接.
首先, 以 HTTP 302 方式來請求播放地址.
? curl -v http://uplive.b0.upaiyun.com/live/loading.m3u8\?shp_identify\=302 -o playlist
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 183.158.35.59...
* TCP_NODELAY set
* Connected to uplive.b0.upaiyun.com (183.158.35.59) port 80 (#0)
> GET /live/loading.m3u8?shp_identify=302 HTTP/1.1
> Host: uplive.b0.upaiyun.com
> User-Agent: curl/7.51.0
> Accept: */*
>
< HTTP/1.1 302 Found
< Server: marco/0.26
< Date: Wed, 22 Mar 2017 08:54:11 GMT
< Content-Type: text/plain; charset=utf-8
< Content-Length: 259
< Connection: keep-alive
< Access-Control-Allow-Methods: GET
< Access-Control-Allow-Origin: *
< Location: http://183.158.35.19:8080/uplive.b0.upaiyun.com/live/loading.m3u8?
shp_uuid=2862b1b817a74cf719b1cd8f554616cd&shp_ts=1490172851450&shp_cid=59553&shp_pid=1
730488&shp_sip0=127.0.0.1&shp_sip1=183.158.35.19&domain=uplive.b0.upaiyun.com&shp_iden
tify=302
<
{ [259 bytes data]
* Curl_http_done: called premature == 0
100 259 100 259 0 0 4813 0 --:--:-- --:--:-- --:--:-- 4886
* Connection #0 to host uplive.b0.upaiyun.com left intact
打開 playlist 內容:
Redirect to http://183.158.35.19:8080/uplive.b0.upaiyun.com/live/loading.m3u8?
shp_uuid=2862b1b817a74cf719b1cd8f554616cd&shp_ts=1490172851450&shp_cid=59553&shp_pid=1
730488&shp_sip0=127.0.0.1&shp_sip1=183.158.35.19&domain=uplive.b0.upaiyun.com&shp_identify=302
在跳轉之后的地址存放真正的 playlist, 同時, 也能夠將 uuid 加入到了連接上.
總地來說, 不管通過哪種方式, 最終我們都能通過一個唯一的 id 來標識一條流, 這樣在排查問題時就可以根據這個 id 來定位播放過程中的問題.
HLS 理論延時 = 1 個切片的時長 + 0-1個 td (td 是 EXT-X-TARGETDURATION, 可簡單理解為播放器取片的間隔時間) + 0-n 個啟動切片(蘋果官方建議是請求到 3 個片之后才開始播放) + 播放器最開始請求的片的網絡延時(網絡連接耗時) 為了追求低延時效果, 可以將切片切的更小, 取片間隔做的更小, 播放器未取到 3 個片就啟動播放. 但是, 這些優化方式都會增加 HLS 不穩定和出現錯誤的風險.
1、我們知道HLS格式的視頻,只有安卓4.0以上才支持,目前基本4.0一下的機子基本可以考慮,不兼容了,所以為了減少工作量,就沒有打算使用三方的播放器,就繼續使用MediaPlayer來進行播放
2、HLS格式的視頻,他是通過一個m3u8文件,然后里面包含若干個TS文件片段,這里有個蘋果的官方的一個例子:
http://devimages.apple.com/iphone/samples/bipbop/gear1/prog_index.m3u8
畢竟這個是蘋果研發的所以看官方的格式肯定沒錯。
3、里面的內容
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10, no desc
fileSequence0.ts
#EXTINF:10, no desc
fileSequence1.ts
#EXTINF:10, no desc
fileSequence2.ts
#EXTINF:10, no desc
fileSequence3.ts
#EXTINF:10, no desc
fileSequence4.ts
#EXTINF:10, no desc
fileSequence5.ts
#EXTINF:10, no desc
fileSequence6.ts
#EXTINF:10, no desc
fileSequence7.ts
我們可以看到里面他又一個一個ts視頻片段,這個一個一個視頻片段就是我們需要的播放,那么他是如何被播放器識別播放的呢。
4、其實上面的這些關鍵的字段都是約定好的,所以在MediaPlayer這個類的native層,會去按照規定好的字段去解析這個m3u8文件,那么他在播放器是最終播放的地址是怎么樣的呢,是這樣的
http://devimages.apple.com/iphone/samples/bipbop/gear1/fileSequence0.ts
組拼起來的,你可以直接用這個地址播放看看。
5、實現這種未加密的緩存還是比較好實現的,大概可以分為這幾步。
看下加密的m3u8文件的格式
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-KEY:METHOD=AES-128,URI="http://xxxxxx:5555//test/1102/test/segments.key"
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-ALLOW-CACHE:YES
#EXT-X-TARGETDURATION:19
#EXTINF:13.966667,
http://xxxxxx:5555/test/1102/test/segments0.ts
#EXTINF:10.000000,
http://xxxxxx:5555/test/1102/test/segments1.ts
#EXTINF:10.000000,
http://xxxxxx:5555/test/1102/test/segments2.ts
#EXTINF:10.000000,
http://xxxxxx.cn:5555/test/1102/test/segments3.ts
#EXTINF:10.000000,
http://xxxxxxn.cn:5555/test/1102/test/segments4.ts
#EXTINF:7.033333,
http://xxxxxx:5555/test/1102/test/segments5.ts
#EXTINF:10.000000,
我們看到了多了個字段EXT-X-KEY,這個也是m3u8給規定好的加密字段,如果包含這個字段播放器就會先去請求這個key,然后拿這個這個key去訪問加密的TS視頻就可以播放了。 其實看到這我們就因該有思路怎么去做,加密的緩存播放了。
實現播放加密緩存的思路
推薦一個免費學習音視頻的地址,里面都是視頻教程干貨滿滿:【免費】FFmpeg/WebRTC/RTMP/NDK/Android音視頻流媒體高級開發-學習視頻教程-騰訊課堂
*請認真填寫需求信息,我們會在24小時內與您取得聯系。