目前幾乎全部的視頻點播網站所有采用HTTP協議傳輸數據。由於相對於諸如RTMP等協議來講,HTTP協議是無狀態的,數據傳輸完畢就斷開鏈接,這樣服務器就能夠騰出資源來服務更多的用戶。而RTMP則會在用戶播放期間一直維護一個鏈接,這樣服務器的負載就很是有限。並且HTTP服務器,CDN等都已是很是成熟的技術,成本低性能好。另外HTTP的請求能夠直接使用瀏覽器Cookie,容易和網站業務打通。最後,HTTP還能使用瀏覽器緩存,這算優勢也算缺點,優勢是請求一樣的資源能夠直接從緩存中取,缺點是安全性差了點。nginx
HTTP擁有更好的性能,可是無法傳輸太實時性的東西,不然性能還不如RTMP,好比視頻聊天,直播這些。apache
有時候咱們被訪問的視頻可能須要作一些限制,好比防盜鏈,視頻收費等等。若是採用HTTP協議的話,傳統的鑑權方式就足夠,Cookie裏帶token什麼的判斷是否有權限訪問視頻資源,細節我就不說了。惟一的問題是一旦用戶有權限訪問視頻,就有可能把視頻下載下來用做他用。瀏覽器
爲了讓HTTP能服務更多的用戶,同時維護更少的鏈接,咱們須要傳輸儘快完畢。但這是咱們分段的理由嗎?不是,由於不管分段不分段,一個用戶加載徹底部的視頻數據對服務器佔用時間是必定的(假設傳輸速度必定),甚至會多佔用不少建立鏈接和銷燬鏈接的資源。緩存
可是咱們看到各大視頻網站實際上都是有對視頻分段的,這裏我就談談視頻分段的好處。安全
咱們在Flash端如何播放視頻很大程度上受NetStream提供的功能所限。因此這裏大體介紹下NetStream提供的功能和一些限制,這也是爲何後面程序要這麼設計的緣由。服務器
直接播放單個視頻文件的方式我就不說了,我這裏介紹的是如何像播一個完整文件同樣播放通過分段的視頻。這個方案有些許瑕疵,後續的方案都是基於這個方案進行優化的。網絡
服務器咱們採用上面提到的第一種最簡單的靜態分段。而且在視頻開始播放前咱們會拿到一個包含視頻分段的開始時間,結束時間,以及分段地址的列表,還有個總的視頻metadata信息。app
當視頻列表加載完畢後就能夠開始依次經過NetStream加載播放各個視頻分片了,每一個分片用一個NetStream實例控制。如圖所示。性能
咱們能夠設定一個最大緩衝距離,結合當前播放進度,算出一個容許緩衝位置,在這個容許緩衝位置以內的切片均可以依次開始加載,開始加載的時候暫停住不播放。當一個切片開始加載以後是不會中止的,因此實際緩衝進度可能會大於容許緩衝位置。優化
當一個切片播放完畢以後不要急着把它關掉,它可能須要留着供後續的seek使用。緊接着,咱們把下一個分片執行resume方法來讓他播放。這樣多個分片按照順序播放,對外界來講就像播放一個完整的視頻同樣。
這種結構下,若外界須要對視頻進行seek操做,能夠分三種狀況:
因此咱們能夠看到,靜態分片方式的在seek的處理仍是仍是有不少不足的,對未加載部份內容的seek都不能作到很是精確。不過若是將切片切得比較短小的話這個問題能夠有所改善,可是還會帶來另外的問題,這個問題我後面講。另外咱們能夠再靜態分片的基礎上引入了start參數,也就是上文提到的「靜態分片+start參數」類型服務器。
引入了start參數後對上面的二、3兩種seek狀況進行了改進:
如此以來在任何狀況下seek均可以精確到關鍵幀,缺點是把正在加載的切片關掉會形成數據浪費。從切片中間開始加載也會形成一個切片內容不完整。下次seek的時候若是不巧是在這個切片start位置以前,就須要從新加載該切片。這些都會形成數據浪費。好在通常用戶不會吃飽了沒事兒seek來seek去。
從上文的幾個策略能夠看出,若是視頻分得越短小,不管對seek的精確度,仍是數據浪費狀況都是有好處的,可是這帶來的一個問題是須要實例化更多的NetStream來維護切片。另外對於時長較長的視頻來講,NetStream的數量也會變得不少。但實際上NetStream能同時開啓的鏈接數量是有限的,這不是內存問題,而是Flash提供的鏈接數有限。超過了這個限制NetStream就沒辦法正常工做了,並且也不報錯。這個限制在不一樣瀏覽器下還不同,我懷疑這和瀏覽器底層有關。
因此爲了限制NetStream的數量,咱們須要設計一個NetStream鏈接池來管理全部的NetStream。鏈接池上限不能小於最大緩衝舉例可能加載的最多分片數,不然邏輯上就是有問題滴。
咱們能夠從鏈接池中取得一個新的NetStream來使用(這個NetStream多是別的NetStream關閉後的,不過你能夠把它當新的用)當鏈接池數量滿的時候,他就會自動把一些老的處於鏈接狀態的NetStream關閉掉。這個淘汰原則是基於空間局部性原理的,也就是說和當前播放頭位置距離最遠的切片應該首先被關掉(處於最大緩衝距離以內的切片不能關閉)。由於根據機率統計發現大部分的seek都出如今播放頭附近(可能爲了找什麼情節)。
經過多個NetStream切換的方式播放視頻,在切換的時候會出現不明顯的爆音,可是仔細聽仍是可以發現。這也是我在上文中提到的文件分割得過短小出現的另一個問題,爆音太頻繁了,可能影響視頻觀看。
因此要從根本上解決這個問題,咱們就要放棄NetStream切換的方式,轉用數據生成模式。數據生成模式能夠把請求的切片作得很小(但也不要過小,不然服務器性能下降)。切片作小的一個好處是請求更快的完成,那麼請求被打斷的概率就會下降,當請求完成以後,下次請求一樣的資源就能從瀏覽器緩衝中取。因此小切片更容易被緩存。而上文中的小切片產生的問題在這裏不復存在。如圖所示。
咱們根據播放頭的位置,日後加載分片數據,直到最大緩衝距離,這和前面提到的方式相似。然後咱們把這些加載的二進制數據保存在內存中。從播放後日後必定的距離(咱們稱做NS緩衝長度),若是有分片進入,那麼就把它appendBytes到用於播放的NetStream中。圖中所示的藍色部分就是保存在內存中的數據,它也有前面提到的鏈接池相似的淘汰機制用於控制內存總大小。被從內存中釋放掉的數據,咱們能夠在瀏覽器緩存中找到(由於已經加載過了),若是要使用的話,咱們能夠像請求服務端數據同樣的方式快速請求到這些數據(固然比從內存中慢一些)。圖中白色方框的是還未加載過的數據,他們在服務器上等待加載。如圖所示就是數據的三級查詢。
若是用戶進行seek:
若是分片數據較大,seek的位置在分片中間,那麼也能夠從分片中間開始加載,這樣能夠從邏輯上把一個分片分爲了兩個。
數據生成模式從本質上保證了播放質量,杜絕了數據浪費,保證了seek精確度,服務器實現上也異常簡單,真是視頻播放首選!
這種方式須要服務器作實時分片並分發到CDN。好比服務器從直播數據源裏把30秒的視頻數據打包成一個數據包分發到CDN上,因此理論上直播至少會延遲30秒。不過對於實時性不是特別強的直播,這種方式的負載能力會更好。
傳統的長鏈接方式直播,須要客戶端和服務器一直保持鏈接,服務器須要維護每一個客戶端的鏈接,但實際上傳輸30秒的視頻數據只須要1秒,因此若是採用HTTP的方式,由於傳輸完畢就能夠服務別人了,因此理論上維護鏈接的效率能夠提升30倍。
這裏咱們要求服務器提供一個視頻地址列表,列表裏提供了最新的N個視頻分片地址。這樣客戶端經過輪詢這個視頻列表就能讓客戶端和直播保持同步。
如圖所示客戶端維護着一個切片列表隊列,經過輪詢服務器,咱們把最新的視頻地址添加到隊列中,而播放模塊則從隊列中取出最老的切片地址加載播放。
若是用戶網絡較差,那麼播放就會卡頓,因此從隊列中取出切片地址的頻率就會下降,隊列會愈來愈長。隊列越長說明視頻播放的延遲越大。
因此當隊列長於某一個臨界值時(咱們設定的),咱們就把隊列清空到只剩一個最近的地址,直到下一次這個地址被取出時,才容許隊列繼續變長。這個隊列清空的操做其實是對因播放卡頓引發的延遲作了矯正,讓直播不要延遲得太厲害。