说起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和华为机器上测试,其他浏览器和其他机型必定也有不同的兼容问题,希望大家有经验的也可以来互相补充下,一同进步。后续,我也会继续关注这个问题,继续补充。