移動端HTML5 視頻播放優化實踐[轉]

http://blog.csdn.net/u010918416/article/details/52705732

http://www.xuanfengge.com/html5-video-play.html

遇到的挑戰

移動端HTML5使用原生<video>標籤播放視頻,要作到兩個基本原則,速度快和體驗佳,先來分析一下這兩個問題。html

下載速度

以一個8s短視頻爲例,wifi環境下提供的高清視頻達到1000kbps,文件大小大約1MB;非wifi環境下提供的低碼率視頻是500kbps左右,文件大小大約500KB;參考QzoneTouch多普勒測速,2g網絡的平均速度是14KB/s,那麼下載一個低碼率視頻耗時35s;那麼要想流暢播放視頻,就須要一個加載等待的過程,這個過程要有明確的反饋,不能讓用戶有「壞掉了」的感受。html5

多普勒測速數據參考

# dns(s) conn(s) rtt(s) tran(kb/s)
2g 3.85785 2.33482 2.57478 14.0374
3g 1.60643 0.743109 0.608047 60.1967
wifi 0.986921 0.550208 0.444332 70.8728

用戶體驗

視頻是否能夠自動播放,是否能循環播放,是否能顯示下載進度,播放的時候如何隱藏控制條,暫停的時候又能顯示出來呢。這些問題看上去貌似簡單,可是因爲PC/iOS/Android這些不一樣平臺、不一樣的瀏覽器內核、甚至相同內核的不一樣版本,所實現的<video>屬性、方法和事件差別較大,解決兼容性問題又給開發形成了很大困擾。android

分析緣由

事件差別

下面是播放一個短視頻,在不一樣平臺觸發事件和獲取屬性的差別表現。ios

PC

# event readyState currentTime (s) buffered (s) duration (s) 視頻狀態
1 loadstart NOTHING 0 - - -
2 suspend NOTHING 0 - - -
3 play NOTHING 0 - - -
4 waiting NOTHING 0 - - -
5 durationchange METADATA 0 5.35 7.91 獲取到視頻長度
6 loadedmetadata METADATA 0 0.66 7.91 獲取到元數據
7 loadeddata ENOUGHDATA 0 0.66 7.91 -
8 canplay ENOUGH_DATA 0 0.66 7.91 -
9 playing ENOUGH_DATA 0 0.66 7.91 開始播放
10 canplaythrough ENOUGH_DATA 0 0.66 7.91 能夠流暢播放
11 progress ENOUGH_DATA 0.11 3.68 7.91 持續下載
12 timeupdate ENOUGH_DATA 0.14 4.44 7.91 播放進度變化
23 progress ENOUGH_DATA 1.77 7.91 7.91 下載完畢
24 suspend ENOUGH_DATA 1.77 7.91 7.91 -
25 timeupdate ENOUGH_DATA 1.9 7.91 7.91 繼續播放中
48 timeupdate ENOUGH_DATA 7.7 7.91 7.91 -
49 timeupdate ENOUGH_DATA 0 7.91 7.91 -
50 seeking METADATA 0 7.91 7.91 -
51 waiting METADATA 0 7.91 7.91 -
52 timeupdate ENOUGH_DATA 0 7.91 7.91 -
53 seeked ENOUGH_DATA 0 7.91 7.91 播放完畢進度回到起點
54 canplay ENOUGH_DATA 0 7.91 7.91 -
55 playing ENOUGH_DATA 0 7.91 7.91 循環播放
56 canplaythrough ENOUGH_DATA 0 7.91 7.91 -
57 timeupdate ENOUGH_DATA 0.19 7.91 7.91 -

iOS

# event readyState currentTime (s) buffered (s) duration (s) 視頻狀態
1 loadstart NOTHING 0 - - -
2 play NOTHING 0 - - -
3 waiting NOTHING 0 - - -
4 durationchange METADATA 0 - 7.91 獲取到視頻長度
5 loadedmetadata METADATA 0 - 7.91 獲取到元數據
6 loadeddata ENOUGHDATA 0 - 7.91 -
7 canplay ENOUGH_DATA 0 7.91 7.91 -
8 canplaythrough ENOUGH_DATA 0 7.91 7.91 能夠流暢播放
9 playing ENOUGH_DATA 0 7.91 7.91 開始播放
10 progress ENOUGH_DATA 0 7.91 7.91 下載完畢
11 suspend ENOUGH_DATA 0 7.91 7.91 -
12 timeupdate ENOUGH_DATA 0.02 7.91 7.91 播放進度變化
43 timeupdate ENOUGH_DATA 7.8 7.91 7.91 -
44 timeupdate ENOUGH_DATA 0 7.91 7.91 -
45 seeked ENOUGH_DATA 0 7.91 7.91 播放完畢進度回到起點
46 timeupdate ENOUGH_DATA 0.22 7.91 7.91 循環播放

Android

# event readyState currentTime (s) buffered (s) duration (s) 視頻狀態
1 loadstart NOTHING 0 - - -
2 play NOTHING 0 - - -
3 waiting NOTHING 0 0 - -
4 durationchange ENOUGH_DATA 0 0 0 -
5 durationchange ENOUGH_DATA 0 0 7.91 獲取到視頻長度
6 loadedmetadata ENOUGH_DATA 0 0 7.91 獲取到元數據
7 loadeddata ENOUGHDATA 0 0 7.91 -
8 canplay ENOUGH_DATA 0 0 7.91 -
9 canplaythrough ENOUGH_DATA 0 0 7.91 -
10 playing ENOUGH_DATA 0 0 7.91 -
11 timeupdate ENOUGH_DATA 0 0 7.91 -
12 progress ENOUGH_DATA 0 3.57 7.91 下載中
13 timeupdate ENOUGH_DATA 0.2 6.89 7.91 開始播放
14 progress ENOUGH_DATA 0 7.91 7.91 下載完畢
49 timeupdate ENOUGH_DATA 7.79 7.91 7.91 -
50 progress ENOUGH_DATA 7.87 7.91 7.91 -
51 timeupdate ENOUGH_DATA 0 7.91 7.91 -
52 seeking ENOUGH_DATA 0 7.91 7.91 播放完畢進度回到起點
53 timeupdate ENOUGH_DATA 0 7.91 7.91 -
54 seeked ENOUGH_DATA 0 7.91 7.91 循環播放失敗卡住了
55 progress ENOUGH_DATA 0 7.91 7.91 -
56 stalled ENOUGH_DATA 0 7.91 7.91 -

一些經常使用且須要重點關注的<video>事件

event iOS Android
****************** *********************************************** ***********************************************
play 只是要播放視頻,響應的是video.play()方法,並不表明已經開始播放 和iOS同樣,僅是響應video.play()方法
durationchange 會執行一次,必定會獲取到視頻的duration 可能會執行屢次,只有最後一次才能獲取到真實的duration,前面的duration都是0;但低版本Android可能獲取到的duration是0或1;(本文提到的低版本Android大部分是4.1如下)
canplay 能夠認爲是視頻元素沒有問題,能夠運行,沒有更多含義了,基本用不上 同iOS
canplaythrough 會有明確的緩衝,表示能夠流暢播放了; 沒有什麼用,視頻仍然會卡住,數據可能尚未開始加載;
playing 明確表示播放開始了; 依然沒有用,視頻可能並無開始播放;
progress 有明確的下載,能夠獲取到當前的buffer,而且所有下載完畢後不在觸發; 不必定有明確的數據下載,而且所有下載完畢後依然繼續觸發;
timeupdate 會有明確的進度變化,能夠獲取到currentTime; 進度不必定變化,currentTime可能老是0,可是第一次有currentTime變化的timeupdate事件必定表明了視頻開始播放了;
error iOS中會有明確的錯誤拋出; Android中某些瀏覽器會莫名其妙的拋出error;
stalled 網絡情況不佳,致使視頻下載中斷; 在沒有play以前,也可能會拋出該事件。

屬性差別

attributes iOS android
****************** *********************************************** ***********************************************
poster 封面圖片 支持,可是加載速度明顯比在<img>中要慢; 不必定支持(瀏覽器廠商的實現標準不統一);
preload 預加載 iPhone不支持; 可能支持;
autoplay 自動播放 iPhone Safari中不支持,但在webview中可能被開啓;iOS開發文檔明確說明蜂窩網絡下不容許autoplay; 可能支持;
loop 循環播放 支持 可能支持;
controls 控制條 支持,可是須要開始播放了才顯示 基本都支持顯示或者不顯示
width和height 必定給出明確的屬性設置,切不能爲0; 若是不設置,僅僅經過CSS樣式去控制視頻大小,可能會致使標籤失效。

其餘怪異bug和不友好表現

iOS android
********************************************************* *********************************************************
物理位置覆蓋在<video>區域上的元素,click和touch等事件會失效,好比一個<a>連接若是覆蓋在<video>上,那麼點擊後沒有任何效果。 -
iOS8.0+中,單頁面播放視頻超過16個,再播放的視頻所有MediaError解碼異常沒法播放。 -
iPhone的Safari會彈出一個全屏的播放器來播放視頻,iPad則支持內聯播放。iOS7+ 若是webview(好比微信)開啓了webview.allowsInlineMediaPlayback = YES;,能夠經過設置webkit-playsinline屬性支持內聯播放; 支持內聯播放,但某些廠商會用本身的播放器劫持原生的視頻播放;
下載視頻時,會先發送一個2字節的請求來獲取視頻元數據(好比時長),而後再不斷的發送分包續傳(206)請求來下載視頻,抓包顯示請求數和請求量至少有一倍的冗餘(x2),這個嚴重的bug在iOS8中有明顯的修復,可是分包的206請求仍然會有冗餘數據的下載,浪費了流量。 比iOS的處理方式好,沒有第一個2字節請求,沒有流量損耗;
- 低版本Android(<=4.0.4)中,<video>若是在有相對和決定定位的層中,可能會致使整個頁面錯位。
- 某些瀏覽器廠商會劫持<video>,用其「本身」的播放器來播放視頻,「破壞」了產品自己的播放體驗,那麼只能case by case的解決了。
加載視頻時沒有進度提示,視覺上看不出是播放完了仍是卡住了; 加載視頻時,大都會顯示一個自帶的loading UI(菊花)。

最佳實踐

視頻初始化

若是將一個<video>直接顯示在頁面中,那麼就會看到各類五花八門的播放器初始效果;web

700ca10a-5ffb-11e4-81d2-b11726e4ea38

 

這顯然不是一個好的視覺體驗,那麼一般的作法是製做一個模擬的視頻播放視圖,好比一個封面加一個播放按鈕。sublime-text

而真實的<video>視頻元素要隱藏起來,如何隱藏呢?最好不要用{display: none}或者{width:0;height:0;}的方式,由於這樣視頻元素會處於未激活的狀態,給後續的處理帶來麻煩。最佳的方式是將視頻設置成1×1像素大小,放在視覺邊緣的位置。瀏覽器

d3f7fdec-5ffe-11e4-8904-f8dbe720ed84

自動播放

autoplay的支持依賴內核和網絡情況,好比iPhone在蜂窩網絡下明確禁用了autoplay;微信

通過試驗,在沒有明確的用戶操做的狀況下,直接經過video.play()也是沒法激活播放的;網絡

而且在產品設計上,自動播放也不是一個舒服的用戶體驗,因此產品設計上儘可能避免使用自動播放。app

點擊播放

以前提到,視頻最好經過1px大小隱藏起來,那麼這時如何觸發播放呢?

通過試驗,當在明確的用戶操做(touch、click)時,經過這些用戶行爲事件的回調函數,用video.play()是能夠觸發視頻播放的,那麼可否在用戶操做後,再去同步的建立和播放視頻呢?答案是確定的,這無疑是一個視頻元素初始化的最佳實踐,可是有些差別須要注意。

iOS6+

能夠在用戶的touch時間中動態建立並播放視頻。

iOS < 6

能夠在用戶的touch時間中動態建立視頻,但不能播放;要再追加一個click事件來啓動播放;也就是說,給僞造的視頻播放按鈕同時綁定tap和click事件,在tap的時候建立,在以後300毫秒的click中去播放。

Android

大部分高版本Android能夠像iOS6+那樣去處理,可是低版本的不行,必需要經過click事件去傳遞video.play(),爲了保持兼容,最好是用幫tap和click兩個事件來分別完成視頻的初始化和播放。

咱們還發現,有些低版本Android中,沒法經過video.play()來播放視頻,必須有真實的用戶點擊視頻元素才能播放;這種狀況,有一個技巧就是在tap的時候初始化並放大視頻覆蓋在播放視圖中,讓300毫秒後的真實點擊行爲穿透點擊在視頻元素上來實現播放。

循環播放

若是視頻須要循環播放,那麼就增長loop屬性,是否能循環播放就看瀏覽器是否支持了,由於尚未找到hack技巧來強制循環播放;

即便,在不支持循環播放的Android中,經過監聽seeked事件知道了播放進度到了終點或起點暫停了,此時也沒法經過video.play()來讓視頻從新播放。

監控下載進度

如何獲取視頻時長和已經下載的時長?

 

progress事件表示視頻在加載,可是它的觸發頻率和時機並不規律,最佳作法是經過一個定時器去實時獲取end,當end >= duration時,表示已經下載完畢,再終止定時器。

 

所有下載後再播放

假設播放短視頻,若是網絡不佳,會形成播放斷斷續續,在iOS中這種停頓尚未一個明確的等待提示,這不是一個好的體驗,那麼是否能夠將視頻所有下載完畢再播放呢?

在iOS中,能夠在視頻剛開始下載的時候立刻暫停,此時下載還將繼續,能夠作一個loading的菊花告知視頻正在加載,而後等到視頻所有下載完再開始播放。

 

454dabb2-6014-11e4-9651-06136a5a7332

緩衝播放——邊下邊播時,選擇開始播放的最佳時間點

當視頻愈來愈長或者網絡慢時,等待視頻所有下載完再播放也不是好的體驗,最好能邊下邊播,緩衝到流暢狀態就開始播放,那何時播放纔是最佳時間點呢?

在iOS中,canplaythrough事件就是這個最佳時間點,它是經過動態計算緩衝量和下載速度得出的視頻能夠流暢播放的狀態反饋。

canplaythrough event: The user agent estimates that if playback were to be started now, the media resource could be rendered at the current playback rate all the way to its end without having to stop for further buffering.

 

8fd35acc-6016-11e4-8755-684ef2656ddf

注意:下載完再播放和緩衝播放只適用於iOS。

統計播放時間和播放次數

要統計實際的播放時間,要累加timeupdate事件變化的時間,再減去中間可能暫停的時間。

 

異常處理

對error事件作詳細的上報;

對stalled事件作統計上報,並提示用戶網絡慢等。

參考數據

微視觸屏版iOS視頻測速

網絡環境 視頻碼率 獲取到視頻時長 時間點(s) 開始流暢播放 時間點(s) 所有下載完畢 時間點(s) 視頻長度(s)
wifi 1000kbps 2.86 3.97 5.85 8.69
非wifi 500kbps 4.56 8 10.62 8.67

參考資料

相關文章
相關標籤/搜索