說起video,相信大家并不陌生。對于做過視頻方面的小伙伴,特指前端方面的小伙伴,對它更是愛恨交加。因為,video使我們很方便在移動端播放視頻,不必像PC端那樣需要安裝一個flash。
video很復雜嗎?不,它很簡單。要想使用它進行播放視頻,只要在html下寫出如下代碼:
<video src="videoUrl"></video>
但是,由于瀏覽器對于video有各種奇葩的設定,所以通常導致開發者無法按照自己的想法實現,所以只能夠盡量去適應瀏覽器,讓video的表現不會過于偏離自己的想法。
通用的video屬性
autoplay
:布爾屬性,指定后,視頻會馬上自動開始播放。
preload
:表示視頻預加載
controls
:表示是否出現控制條
loop
:表示是否循環播放
src
:指定播放的視頻源
width
:指定視頻寬度(通常在css中指定)
height
:指定視頻高度(通常在css中指定)
通用的video事件
play
:視頻開始播放觸發的事件(觸發此事件,但是視頻不一定可以播放)
playing
:視頻可以播放觸發的事件
timeupdate
:音頻/視頻(audio/video)的播放位置發生改變時觸發
pause
:視頻停止播放觸發的事件
ended
:視頻播放結束或中斷觸發的事件
以上的這些屬性和事件,只是屬于video標簽的常用的屬性,要想知道更多的,可以去MDN那里查看。
測試機器和瀏覽器說明
本文所有的測試都在iphone6s和華為上測試,測試的瀏覽器有safari、微信、QQ瀏覽器和UC瀏覽器。
視頻播放效果
在移動端實現的video效果,一是全屏播放,一是內聯播放。所謂內聯播放,就是指視頻能夠在你指定的位置播放。
視頻全屏播放的效果實現
全屏播放,可以分成兩種,一種是模擬全屏播放,也就是自己寫個遮罩層模擬全屏播放的效果。另外一種是使用系統(瀏覽器或平臺)內置的播放器。
如果是需要全屏播放的話,目前采取的策略有以下幾種:
- 簡單粗暴的方法:
/* html */
<video src="http://godsong.bs2dl.yy.com/dmZlNTY3Y2VjZWRlNDM3NGM4MzNkZGE3OGJmYTJhYTVkMTIwMjY0NDI1N21j"></video>
/* css */
video {
display: none;
}
var $video = document.getElementsByTagName("video")[0];
$video.play();
如上,這種方法只適用于IOS,使用這種方式,在微信和safari端,會調起IOS自帶的播放器,在UC和QQ瀏覽器上會調用內置的播放器。簡單粗暴,幾行代碼搞定。
- 模擬全屏播放的方法
<div class="liveplayer">
<video src="http://godsong.bs2dl.yy.com/dmZlNTY3Y2VjZWRlNDM3NGM4MzNkZGE3OGJmYTJhYTVkMTIwMjY0NDI1N21j"
controls>
</video>
</div>
<div class="btn">點擊播放</div>
.liveplayer {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #000;
display: none;
}
video {
display: none;
height: 100%;
width: 100%;
}
.btn {
border-radius: 10px;
background-color: #ce3249;
width: 100px;
text-align: center;
height: 50px;
line-height: 50px;
color: #fff;
}
var videoPlayer = (function() {
var $livePlayer = document.getElementsByClassName('liveplayer')[0];
var $video = document.getElementsByTagName('video')[0];
var $playBtn = document.getElementsByClassName('btn')[0];
$playBtn.addEventListener('click', function() {
$livePlayer.style.display = 'block';
$video.style.display = 'block';
$video.play();
})
})()
上面所說的全屏播放,實際是我們在頁面上寫了個遮罩層,模擬全屏。這種方式雖然麻煩點,但是適用性較廣。
兼容性
在UC瀏覽器里面,IOS使用瀏覽器自帶的播放器,始終處于全屏播放狀態。
在QQ瀏覽器上,IOS上的表現是彈出小窗播放,所以沒法做到全屏播放。
要使用播放器內聯播放來模擬全屏播放,如果在IOS下,需要加入playsinline
屬性,才能做到一開始就在我們指定的位置播放視頻,并且在IOS10以上才支持,以下的則是一開始默認全屏,點擊全屏退出后,暫停播放視頻。
視頻內聯播放
- 視頻內聯播放的效果實現,實則與上述的模擬全屏播放的原理是一樣的。
- 不一樣的是,全屏播放時,video的高寬可以是100%,視頻內容會根據視頻比例去適應。
- 但是內聯播放,你需要去定義視頻的大小,這就需要獲取到視頻的大小了(原始方法不靠譜,最好是后臺接口返回視頻比例或者根據其他方法獲取視頻比例)。
視頻事件的監聽和處理
視頻播放時,我們可以通過監聽事件,來進行一些其他效果的實現。
上述說到的視頻事件有:play
,playing
,timeupdate
,pause
,ended
首先我們來看看這些事件是在哪個階段觸發的。
- 點擊視頻播放
觸發play
事件,此時視頻不一定可以播放。 - 視頻可以開始播放
觸發playing
事件 - 視頻播放過程
觸發timeupdate
事件,video的播放位置改變 - 視頻播放暫停
觸發pause
事件 - 視頻播放結束
觸發ended
事件。
說明:- android中,UC瀏覽器,視頻播放結束時,會觸發
pause
和ended
事件。微信、QQ瀏覽器觸發ended
事件。 - IOS中,UC瀏覽器下,視頻播放結束,觸發
ended
事件,其余,微信、QQ瀏覽器、safari下觸發pause
和ended
事件。
- android中,UC瀏覽器,視頻播放結束時,會觸發
續播
續播的情況,可以分為兩種情況,一種是視頻播放過程中斷了,續播。
另一種是視頻播完了,續播。
視頻結束后續播
通過上述的video事件,我們可以通過監聽ended
事件來實現這種續播效果。做法就是在ended
事件,替換視頻的src,然后再進行播放。如下:
var $video = document.getElementsByTagName('video')[0];
var videoUrl = 'url'
function playVideo(src) {
$video.src = src;
$video.play();
}
$video.addEventListener('ended', function() {
playVideo(videoUrl);
})
兼容性
經過測試,在IOS和Android的UC瀏覽器、QQ瀏覽器、微信和safari均可以實現。
視頻中斷續播
這種情況多用于直播,直播時,視頻播放時依靠視頻流傳輸過來進行播放,一旦出現網絡問題或者其他問題導致視頻流斷流,就會出現視頻中斷的情況。這種續播情況,也是比較難實現的。接下來,我們就來以直播斷流為例,看怎么實現這種續播。
在此之前,我們先來看看video的幾個屬性。
- readyState
- 0 = HAVE_NOTHING - 沒有關于音頻/視頻是否就緒的信息
- 1 = HAVE_METADATA - 關于音頻/視頻就緒的元數據
- 2 = HAVE_CURRENT_DATA - 關于當前播放位置的數據是可用的,但沒有足夠的數據來播放下一幀/毫秒
- 3 = HAVE_FUTURE_DATA - 當前及至少下一幀的數據是可用的
- 4 = HAVE_ENOUGH_DATA - 可用數據足以開始播放
測試結果:
IOS:QQ瀏覽器的readyState始終是1,UC瀏覽器下始終是0。微信和safari在視頻可播放的情況下,readyState為3或者4。
Android:微信、UC瀏覽器和QQ瀏覽器在視頻可播放的情況下,readyState為4。
- networkState
- 0 = NETWORK_EMPTY - 音頻/視頻尚未初始化
- 1 = NETWORK_IDLE - 音頻/視頻是活動的且已選取資源,但并未使用網絡
- 2 = NETWORK_LOADING - 瀏覽器正在下載數據
- 3 = NETWORK_NO_SOURCE - 未找到音頻/視頻來源
測試結果:
IOS:QQ瀏覽器在視頻處于可播放狀態時,networkState = 1。safari瀏覽器中,networkState = 2。UC瀏覽器中,networkState=0。
Android: 各瀏覽器表現基本一致,視頻正常播放時,networkState = 2。
由上,我們可以得知,如果video的readyState = 3或4,networkState = 1 或 2時,視頻是正常播放的。反之,則可能發生視頻中斷或結束.
直播斷流表現:
- Android:沒有觸發事件,networkState和readyState也是正常。無法判斷出是不是斷流
- IOS:觸發ended事件
直播視頻播放不順暢
- Android: 沒有觸發事件,networkState和readyState也是正常。無法判斷出是不是斷流
- IOS:此時,readyState = 2
如果我們要處理直播斷流這種情況的話,可以如下這樣做。可以先想想,再看看代碼。
<div class="liveplayer">
<video
src="http://hls.yy.com/newlive/54880976_54880976.m3u8?org=yyweb&appid=0&uuid=b08c0aac2a694cc19c781d6c268ef1ea&t=1487732029&tk=b84984ce4059851b5d456c7ffe205511&uid=0&ex_audio=0&ex_coderate=700&ex_spkuid=0"
playsinline
controls>
</video>
</div>
<div class="debug">
</div>
<div class="loading">視頻正在加載中...</div>
<div class="btn">點擊播放</div>
.liveplayer {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #000;
display: none;
}
video {
display: none;
height: 50%;
width: 100%;
}
.debug {
z-index: 4;
overflow: scroll;
padding: 12px;
font-size: 12px;
line-height: 16px;
color: #000;
background-color: rgba(255,255,255,0.8);
position: fixed;
bottom: 0;
width: 100%;
top: 50%;
}
.btn {
border-radius: 10px;
background-color: #ce3249;
width: 100px;
text-align: center;
height: 50px;
line-height: 50px;
color: #fff;
}
.loading {
padding-top: 100px;
text-align: center;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
color: #fff;
background-color: #000;
z-index: 2;
display: none;
}
var videoPlayer = (function() {
var $livePlayer = document.getElementsByClassName('liveplayer')[0];
var $video = document.getElementsByTagName('video')[0];
var $playBtn = document.getElementsByClassName('btn')[0];
var $loading = document.getElementsByClassName('loading')[0];
var streamInterval = null;
$playBtn.addEventListener('click', function() {
showLoading();
$video.play();
})
function showLoading() {
$loading.style.display = 'block';
}
function hideLoading() {
$loading.style.display = 'none';
}
function showVideo() {
$livePlayer.style.display = 'block';
$video.style.display = 'block';
}
function hideVideo() {
$video.style.display = 'none';
$livePlayer.style.display = 'block';
}
$video.addEventListener('play', function() {
debug('trigger video play status');
debug('video readyState: '+$video.readyState);
debug('video networkState: '+$video.networkState);
showLoading();
})
$video.addEventListener('playing', function() {
debug('trigger video playing status');
debug('video readyState: '+$video.readyState);
debug('video networkState: '+$video.networkState);
debug($video.error && $video.error.code);
hideLoading();
showVideo();
checkVideoStream();
})
$video.addEventListener('timeupdate', function() {
debug('trigger video timeupdate status');
debug('video readyState: '+$video.readyState);
debug('video networkState: '+$video.networkState);
})
$video.addEventListener('pause', function() {
debug('trigger video pause status');
debug('video readyState: '+$video.readyState);
debug('video networkState: '+$video.networkState);
debug($video.error && $video.error.code);
debug('video duration: '+$video.duration);
})
$video.addEventListener('ended', function() {
debug('trigger video ended status');
debug('video duration: '+$video.duration);
debug($video.error.code);
showLoading();
hideVideo();
$video.play();
})
function debug(con) {
console.log(con);
var date = new Date();
var hour = date.getHours();
var minutes = date.getMinutes();
var seconds = date.getSeconds();
var $debug = document.getElementsByClassName('debug')[0];
var $beforeP = document.getElementsByTagName('p')[0];
var $p = document.createElement('p');
var $textNode = document.createTextNode('['+hour+':'+minutes+':'+seconds+']: '+con);
$p.appendChild($textNode);
if($beforeP) {
$debug.insertBefore($p, $beforeP);
}else {
$debug.appendChild($p);
}
}
function checkVideoStream() {
if(streamInterval) {
clearInterval(streamInterval);
}
streamInterval = setInterval(function() {
if($video.readyState == 2 || $video.readyState == 1) {
$video.play();
clearInterval(streamInterval);
return;
}
if($video.networkState == 3) {
$video.play();
clearInterval(streamInterval);
return;
}
})
}
}, 2000)()
主要思路是:點擊播放,展示loading,同時播放視頻,此時視頻層仍然是隱藏狀態。觸發playing
事件,隱藏loading,顯示video,并且啟動查詢視頻狀態的定時器streamInterval
。如果在播放過程中需要進行處理,則可以在timeupdate事件進行處理。當直播斷流時,如果沒有觸發任何事件,這時可以通過readyState或networkState來判斷是否斷流,如果觸發了ended
事件,這時顯示loading,隱藏video,播放video,重復之前的過程。
這種續播處理方式,IOS的適用性很高,但是android比較低。因為經過測試,在直播斷流這種場景下,android端的表現是,沒有觸發ended
事件,networkState和readyState也是正常,所以沒法判斷。
視頻層級
在實際中,經常會有產品同學過來說,我要在視頻上加下按鈕,加下信息之類。嗯,理想很美好,但是現實很骨感。至今,除了在IOS的微信上可以做到這種效果之外,其余的主流瀏覽器都不支持。在這些瀏覽器里面,視頻的層級是最高的。
后語
對于video,還有很多可以發掘的地方,比如制作進度條之類。并且兼容性方面我目前也只是測試了微信、QQ瀏覽器、UC瀏覽器和safari,機型也只是在iphone6s和華為機器上測試,其他瀏覽器和其他機型必定也有不同的兼容問題,希望大家有經驗的也可以來互相補充下,一同進步。后續,我也會繼續關注這個問題,繼續補充。