参考
HTML5视频的那些事儿
H5-Video 能做的事&存在的坑
浅谈video标签在移动端的运用
视频播放--踩坑小计
h5 video 移动端填坑记
微信H5 Video 开发小结
H5 video使用总结
移动端 HTML5 <video> 视频播放优化实践
一、基本属性
<video width="658" height="444"
src="http://www.youname.com/images/first.mp4"
poster="http://www.youname.com/images/first.png"
autoplay="autoplay"
preload="none"
loop="loop"
controls="controls"></video>
1.src属性和poster属性
你能想象src属性是用来干啥的。跟<img>标签的一样,这个属性用于指定视频的地址。而poster属性用于指定一张图片,在当前视频数据无效时显示(预览图)。视频数据无效可能是视频正在加载,可能是视频地址错误等等。
2.preload属性
这个属性也能通过名字了解用处,此属性用于定义视频是否预加载。属性有三个可选择的值:none、metadata、auto。如果不使用此属性,默认为auto。
- none:不进行预加载。使用此属性值,可能是页面制作者认为用户不期望此视频,或者减少HTTP请求。
- metadata:部分预加载。使用此属性值,代表页面制作者认为用户不期望此视频,但为用户提供一些元数据(包括尺寸,第一帧,曲目列表,持续时间等等)。
- auto:全部预加载。
3. autoplay属性
又是一个看名字知道用处的属性。Autoplay属性用于设置视频是否自动播放,是一个布尔属性。当出现时,表示自动播放,去掉是表示不自动播放。但是在ios上无法执行自动播放......(添加muted
可以自动播放)需要用户执行play方法。(具体可参考视频播放--踩坑小计)
4.loop属性
一目了然,loop属性用于指定视频是否循环播放,同样是一个布尔属性。
5.controls属性
设置或返回音频/视频是否显示控制条(比如播放/暂停等)
6.playsinline webkit-playsinline
视频在移动端播放时会自动全屏,而这个属性就是为了阻止全屏动作的,添加-webkit-前缀增加在ios safari上的兼容性。(只用写上属性名就行,和autoplay,loop之类的类似)
7.muted
如果出现该属性,视频的音频输出为静音。
8.通用的video事件
play:视频开始播放触发的事件(触发此事件,但是视频不一定可以播放)
playing:视频可以播放触发的事件
timeupdate:音频/视频(audio/video)的播放位置发生改变时触发
pause:视频停止播放触发的事件
ended:视频播放结束或中断触发的事件
更多属性和事件,参考总结H5 video 方法 属性 事件
二、兼容性
1.视频格式
当前,video 元素支持三种视频格式:
- Ogg = 带有 Theora 视频编码和 Vorbis 音频编码的 Ogg 文件
- MPEG4 = 带有 H.264 视频编码和 AAC 音频编码的 MPEG 4 文件
- WebM = 带有 VP8 视频编码和 Vorbis 音频编码的 WebM 文件
浏览器对视频格式的支持各不相同,小一点的浏览器厂商比如firefox和opera不愿支持商业的视频格式(mp4),因为需要支付专利费,而大一点的厂商如微软苹果等,不愿支持开源的格式,因为可能有专利问题。为了解决兼容性的问题,HTML5也给出了解决办法,那就是source标签。
<video controls autoplay>
<source src="media/butterfly.mp4" type="video/mp4">
<source src="media/butterfly.webm" type="video/webm">
<source src="media/butterfly.ogv" type="video/ogg">
<p>当前环境不支持video标签。</p>
</video>
浏览器会最先尝试播放第一个视频,如果发现不支持会播放第二个,依次类推直到找到一个可以播放的,或者全部能播放。。
2.字幕
字幕也是一个复杂的问题,简单的一个字幕就可能有下面的需求:格式,换行,颜色,卡拉OK等。所以现存的字幕格式就有50多种。使用字幕的方式和使用source的方式类似,同时可指定多个字幕文件,用来指代不同语言的字幕,用户可以自己选择想要的字幕。
<video controls loop autoplay>
<source src="media/butterfly.mp4" type="video/mp4">
<source src="media/butterfly.webm" type="video/webm">
<track src="media/butterfly.vtt" srclang="en" kind="subtitles" label="English" default>
<track src="media/butterfly_fr.vtt" srclang="fr" kind="subtitles" label="French">
</video>
vtt格式如下所示,标记了每个字幕开始出现的时间和消失的时间。
WEBVTT
00:00:01.000 --> 00:00:03.000
Butterflies are lovely.
00:00:04.000 --> 00:00:08.000
Don't you think?
3.HTMLVideoElement的canPlayType API进行当前环境的格式兼容判断
let videoEl = document.createElement("video");
// 是否支持 MP4
videoEl.canPlayType('video/mp4') !== '';
// 是否支持 MP4 & 特定编码的
videoEl.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"') !== '';
// 是否支持 webm & 特定编码的
videoEl.canPlayType('video/webm; codecs="vp8, vorbis"') !== '';
// 是否支持 ogg & 特定编码的
videoEl.canPlayType('video/ogg; codecs="theora, vorbis"') !== '';
// 是否支持 HLS 的 m3u8
videoEl.canPlayType('application/vnd.apple.mpegURL') !== '';
// 是否支持 HLS 的 TS 切片
videoEl.canPlayType('video/mp2t; codecs="avc1.42E01E,mp4a.40.2"') !== '';
使用canPlayType方法传入要检测的媒体类型或具体编码格式,我们将可能得到'maybe'、'probably'、''三个String值中的一个,当得到空字串的时候,可以确定为不支持。
4.各环境中Video UI不一致
我们拿同一个页面,放一个“大白兔”的示例视频,在不同的浏览器环境下观察截图:
怎么样,意不意外?惊不惊喜?同一个浏览器不同的版本都可能大相径庭,当你的用户给你反馈播放器上一些奇奇怪怪按钮看不懂事干嘛的、一些奇特的功能用着不爽的时候,你能确定他说的与你看到的是一个东西吗?
我们针对不同的环境,必须做一些符合它的要求的配置,以期望能尽量达成我们想要的目的。但是不得不说的是,标准API里的controls、poster、autoplay也都未必有效(只是想隐藏控制条都得想各种纠结的办法--裁剪出镜、更或者一些宣传短片干脆做成序列帧...),前面说的通过source来设置不同媒体源的方案,部分浏览器也可能是不认识的。
W3C标准和MDN告诉我们可以通过VideoElement.error取得媒体播放异常的参考信息,但是实践告诉我们,很多环境下是压根没有这么个东东,或者参考信息不够详尽靠谱。
通过MediaEvents&API 检测,会发现各环境中播放进度变化、事件触发频率不同、部分事件触发时相应状态值未必可靠、部分场景缺少事件(全屏状态变化、系统播放器劫持)、seek时不一定触发play...
例如ios部分环境监听canplay和canplaythrough(是否已缓冲了足够的数据可以流畅播放),当加载时是不会触发的,即使preload="auto"也没用,但在pc的chrome调试器下和android下,是会在加载阶段就触发,ios需要播放后才会触发。
部分环境中视频从开始播到能展现画面会有短暂的黑屏(处理视频源数据的时间),为了避免这个黑屏,可以在视频上加个浮层或设置容器背景并在播放前隐藏video,然后监听相关事件,开始播放或认为有画面的时候再切换到Video界面。
所以总体来看,并没有可靠统一的规范。
5.状态处理容易存在冲突
假如我们将原生video元素视作一个数据状态储存容器,那么可以看到以上四种需求需要如下权限:
对于有播放状态控制的一些控件,会同时需要状态的读、写;对于展示式需求,例如无交互的弹幕、打赏礼品、特效挂件等,只需要读状态 --- 例如暂停则停止;更高级的,例如片头、片尾、插片广告,我们不但要知道播放状态、时机,甚至要在广告播放期间阻止一切默认行为,直到允许用户跳过等,这就不只是需要完整的读写权限,还要具备阻截别人读写的功能...
当业务逻辑日积月累状态操作的场景越来越繁杂,缺乏合理的状态管理机制,势必会导致状态混乱、冲突的产生。
6.交互与层级管理的矛盾
UI插件的需求在业务中也是纷繁复杂的,比如通常的弹幕插件是不需要交互,只要滚动展示内容,这时候我们点击弹幕跟点击播放器本身一样要触发暂停或播放行为。突然有一天需要夹杂展示点击后跳转详情的广告内容,这时需要交互行为,这也就与前面的需求相矛盾了。
再加上一些动态变更层级的UI组件---比如右键菜单、popup配置浮层... 这也需要有一套可靠灵活的层级管理机制,来支撑这些可能出现的需求。
7.视频层级
在实际中,经常会有产品同学过来说,我要在视频上加下按钮,加下信息之类。嗯,理想很美好,但是现实很骨感。至今,除了在IOS的微信上可以做到这种效果之外,其余的主流浏览器都不支持。在这些浏览器里面,视频的层级是最高的。
三、Laya中使用video
1.添加原生的video
class DOM_Video {
constructor() {
Laya.init(800, 600);
Laya.stage.bgColor = "#FFFFFF";
Laya.stage.alignH = Laya.Stage.ALIGN_CENTER;
Laya.stage.alignV = Laya.Stage.ALIGN_MIDDLE;
// 创建Video元素
var videoElement:any = Laya.Browser.createElement("video");
Laya.Browser.document.body.appendChild(videoElement);
// 设置Video元素地样式和属性
videoElement.style.zInddex = Laya.Render.canvas.style.zIndex + 1;
videoElement.src = "../../res/av/mov_bbb.mp4";
videoElement.controls = true;
// 阻止IOS视频全屏
videoElement.setAttribute("webkit-playsinline", true);
videoElement.setAttribute("playsinline", true);
// 设置画布上的对齐参照物
var reference:Laya.Sprite = new Laya.Sprite();
Laya.stage.addChild(reference);
reference.pos(100, 100);
reference.size(600, 400);
reference.graphics.drawRect(0, 0, reference.width, reference.height, "#CCCCCC");
// 每次舞台尺寸变更时,都会调用Utils.fitDOMElementInArea
//设置Video的位置,对齐的位置和refence重合
Laya.stage.on(Laya.Event.RESIZE, this, Laya.Utils.fitDOMElementInArea,
[videoElement, reference, 0, 0, reference.width, reference.height]);
}
}
new DOM_Video();
2.视频(Video)播放问题
LayaAir关于视频的实现和原生不一样,我们的视频是绘制到了canvas上,可以控制显示区域的大小,原生是直接使用的html5标签,如果你的项目不存在像游戏里的交互功能的话,建议你可以直接使用原生video标签来实现。两者都会存在兼容性问题,layaAir的可能在某些浏览器无法播放,原生的无法控制播放区域的大小,关于这个问题还请开发者自行舍取。
参考Video.as:
public class Video extends Sprite
...
public function Video(width:int = 320, height:int = 240)
{
super();
if (Render.isWebGL)
htmlVideo = new WebGLVideo();
else
htmlVideo = new HtmlVideo();
videoElement = htmlVideo.getVideo();
videoElement.layaTarget = this;
internalTexture = new Texture(htmlVideo);
...
/**
* 开始播放视频。
*/
public function play():void
{
videoElement.play();
Laya.timer.frameLoop(1, this, renderCanvas);
}
private function renderCanvas():void
{
if (readyState === 0)
return;
if (Render.isWebGL)
htmlVideo['updateTexture']();
this.graphics.clear();
this.graphics.drawTexture(internalTexture, 0, 0, this.width, this.height);
}
override public function size(width:Number, height:Number):Sprite
{
super.size(width, height)
videoElement.width = width / Browser.pixelRatio;
if (paused) renderCanvas();
return this;
}
从截取的部分代码中,可以看出是在帧事件中使用graphics.drawTexture把原生的video画面绘制到了canvas上。
3.解决fitDOMElementInArea在ios下不能正常显示
Utils.fitDOMElementInArea=function(dom,coordinateSpace,x,y,width,height){
if (!dom._fitLayaAirInitialized){
dom._fitLayaAirInitialized=true;
dom.style.transformOrigin=dom.style.webKittransformOrigin="left top";
修改为:dom.style.transformOrigin=dom.style.webkitTransformOrigin="left top";
dom.style.position="absolute"
};