3.3 范例——制作视频播放器APP
上面的示例已经向读者展示HTML 5带来的video元素在浏览器视频上的突破,摆脱了传统浏览器播放视频过度依赖于第三方插件的局面。虽然video非常美好,但是各种浏览器对视频控件的实现不同,在原有的面板增加自定义功能更是难上加难。本例将初步实现一个自定义的播放器,给各位读者提供一个思路。
本例播放器将实现3个基本功能:播放、暂停、全屏。使用Chrome浏览器打开网页文件,运行结果如图3.5所示。点击视频中央的播放按钮,运行结果如图3.6所示。
图3.5 使用Chrome打开网页文件
图3.6 点击视频中央的播放按钮
此时,中央的播放按钮被隐藏,紧随着出现视频底部工具条。该工具条并非浏览器原生视频工具条,而是通过HTML进行模拟定制。如图3.6所示,工具条上出现了左下角暂停按钮和右下角全屏按钮。
提示
除了例子实现的3个功能外,后续例子将会在本例实现的基础上进行加强,读者可以继续阅读后续示例,感受自定义的魅力。
3.3.1 普通视频播放器
利用编辑器打开“3-4.普通视频播放器.html”文件,代码如下:
【代码3-4】
01 <! DOCTYPE HTML> 02 <html> 03 <head> 04 <style>/* ......此处样式忽略,读者可以查看对应源代码 */</style> 05 <script src="../js/jquery-1.8.3.js"></script> 06 </head> 07 <body> 08 <header><h2>做一个自己的视频播放器</h2></header> 09 <div class="video_box"> 10 <video src="../res/BigBuck.webm" width="480" height="320" controls></video> 11 </div> 12 </body> 13 <script> 14 (function () { 15 var CONTROLS_HTML = '......省略’; // 播放工具条HTML,此处省略 16 function VideoControl(ele) { // 播放工具条类 17 this.video = $(ele); 18 this.init(); 19 }; 20 VideoControl.prototype = { // 原型方法 21 init: function () { 22 // 移除video原本的controls属性,去除浏览器默认工具条 23 this.video.removeAttr('controls'); 24 this._render(); 25 this._bind(); 26 }, 27 _render: function () { // 用于生成工具条html结构 28 var wraper = this.wraper = $(document.createElement('div')); 29 wraper.html(CONTROLS_HTML); 30 this.video.parent().append(wraper); // 将工具类插入文档 31 }, 32 _bind: function () { // 给工具条的元素绑定事件 33 var self = this, 34 video = self.video.get(0), // 获取对应的原生元素 35 wraper = self.wraper, 36 control_btn = wraper.find('div.control_btn'); 37 // 用jQuery的delegate方法委托特制元素监听click事件 38 wraper.delegate('div[data-type]', 'click', function (e) { 39 var data_type = $(this).attr('data-type'); // 获取按钮自定义操作类型属性 40 switch (data_type) { 41 case 'go': // 初始屏中间大按钮 42 wraper.find('div.play_button').hide(); 43 wraper.find('div.play_controls').show(); 44 video.play(); // 播放视频 45 break; 46 case 'play': // 自定义工具条播放键 47 control_btn.toggle(); 48 video.play(); 49 break; 50 case 'pause': // 自定义工具条暂停键 51 control_btn.toggle(); // 暂停视频 52 video.pause(); 53 break; 54 case 'fullscreen': // 自定义工具条全屏键 55 self._fullScreen(video); // 调用实例的全屏方法 56 break; 57 }; 58 }); 59 }, 60 _fullScreen: function (video) { // 全屏方法 61 var prefixs = 'Webkit Moz O ms Khtml'.split(' '), // 各种浏览器全屏方法前缀 62 parent = video, prefix; 63 // 循环各浏览器前缀名,找寻符合的方法并执行 64 for (var i = 0, l = prefixs.length; i < l; i++) { 65 prefix = prefixs[i].toLowerCase(); 66 67 if (parent[prefix + 'EnterFullScreen']) { // 兼容不同浏览器全屏方法 68 parent[prefix + 'EnterFullScreen'](); 69 break; 70 } else if (parent[prefix + 'RequestFullScreen']) { 71 parent[prefix + 'RequestFullScreen'](); // 如果存在该方法即执行 72 break; 73 }; 74 }; 75 } 76 }; 77 new VideoControl(document.querySelector('video')); // 实例化自定义工具条类 78 })(); 79 </script> 80 </html>
代码第16~19行定义了工具类构造函数。函数接收1个参数,该参数需要加入自定义工具条的video元素。
代码第20~76行在VideoControl的prototype原型上增加如下4个方法:
● init:初始化函数。
● _render:生成工具条HTML结构。
● _bind:在工具条元素上绑定事件。
● _requestFullscreen:video元素全屏方法。
提示
prototype属性是JavaScript面向对象编程的基础,如果对其还不是很了解,可以参考http://msdn.microsoft.com/zh-cn/magazine/cc163419.aspx。
_render方法将字符模板CONTROLS_HTML插入对应的video父节点中,用于构建工具条DOM结构。
_bind方法在工具条的外围容器增加事件委托,监听元素类型为符合选择器“div[datatype]”的click事件。data-type是自定义的元素属性,表示播放按钮的类型。本例中共有4种类型,具体如下:
● go:初始状态中央的大三角按钮事件类型。
● play:工具条播放按钮事件类型。
● pause:工具条暂停按钮事件类型。
● fullscreen:工具条全屏按钮事件类型。
在各种浏览器上,video元素的播放和暂停方法的名称都相同,分别为play和pause。全屏方法由于还处于草案阶段,因此需要加上对应浏览器的前缀名,代码第60~75行就是为了解决这个问题而设置的。兼容方案可以参考代码,这里不做过多说明。
细心的读者会发现,Chrome浏览器下调用video全屏方法后,不论video是否带有controls属性,都会出现播放器的默认工具条,这显然不是当初想看到的,读者不妨先想想,稍后会给出解决方案。
提示
本次代码分析主要针对示例的脚本逻辑,样式说明可以参考源码中的注释。
3.3.2 添加视频进度条
本例将完成给自定义播放器添加进度条的工作。一共会添加两种进度条,分别为下载进度条和播放进度条。使用Chrome浏览器打开网页文件,点击屏幕中央的三角播放按钮,播放器底部出现自定义工具条,同时进度条慢慢地向右伸长,运行效果如图3.7所示。
图3.7 点击播放按钮
图3.7中的蓝色进度条表示播放时间,灰色条表示视频下载进度。将鼠标悬浮于进度条之上,此时进度条上方会出现对应的时间提示框,效果如图3.8所示。
图3.8 鼠标悬浮于进度条出现时间提示
本例的代码构建在“3-5.添加视频进度条.html”的功能基础之上。下面对增添的代码部分做一个分析。
字符串模板CONTROLS_HTML增加进度条HTML结构,代码如下:
'<div class="control progress_control">' + // 进度条外围层 '<div class="progress_bar_bg"></div>' + // 进度条背景层 '<div class="progress_bar_buffered"></div>' + // 下载进度条层 '<div class="progress_bar_played"></div>' + // 播放进度条层 '<div class="progress_bar_time">' + // 悬浮提示外围层 '<div class="progress_bar_time_line"></div>' + '<div class="progress_bar_time_txt">00:00</div>' + // 悬浮时间提示 '</div>' + '</div>' +
提示
HTML模板对应的CSS可以参考源码。
视频工具类VideoControl的prototype原型上增加_progress和_bartime方法。其中_progress方法用于控制下载进度条的移动,代码如下:
_progress: function () { // 控制下载进度条 var self = this, video = self.video, // 实例上的video属性 video_ele = video.get(0), // 视频的原生元素 progress_bar = self.wraper.find('div.progress_control'), // 进度条外围元素 progress_bar_buffered = self.wraper.find('div.progress_bar_buffered'); // 下载进度条元素 video.on({ 'progress': function (e) { // 监听video的下载进度事件 if (this.buffered && this.buffered.length) { // 判断是否开始接收数据 var percent = video_ele.buffered.end(0) / video_ele.duration; // 下载数据相对总时间百分比 progress_bar_buffered.width(percent * progress_bar.width()); // 设置下载进度条长度 }; } }); },
video的buffered属性返回1个TimeRanges对象。TimeRanges对象表示音视频的已缓冲部分,对象具有1个length属性,表示音视频中已缓冲范围的数量,同时还具有两个方法,即start和end,语法如下:
video. buffered.start(index); // 获得某个已缓冲范围的开始位置 video. buffered. end (index); // 获得某个已缓冲范围的结束位置
_bartime方法用于实现鼠标悬浮进度条的时间提示,代码如下:
_bartime: function () { // 鼠标悬浮进度条的时间提示 var wraper = this.wraper, progress_control = wraper.find('div.progress_control'), // 进度条外围元素 progress_bar_bg = wraper.find('div.progress_bar_bg'), // 进度条背景层 progress_bar_time = wraper.find('div.progress_bar_time'), // 时间悬浮提示层外框 progress_bar_time_txt = wraper.find('div.progress_bar_time_txt'), video_ele = this.video.get(0); // 悬浮提示时间元素 progress_bar_bg.on({ // 绑定进度条背景层 'mousemove': function (e) { var offsetX = e.clientX - progress_bar_bg.offset().left, // 相对于进度条横轴距离 percent = offsetX / progress_bar_bg.width(); progress_bar_time.css('left', offsetX + 6); // 设置时间浮动框位置 progress_bar_time_txt.html(timeFormat((percent * video_ele.duration)|| 0)); }, 'mouseenter': function (){ progress_bar_time.show(); }, // 移入显示时间提醒 'mouseleave': function (){ progress_bar_time.hide(); }// 移出隐藏时间提醒 }); }
最后,完成播放进度条的动态更新功能,修改原型方法_timeupdate,代码如下:
this.video.on({ 'timeupdate': function () { // 视频播放位置变动时触发 currentTime.html(timeFormat(video.currentTime)); // 当前播放时间动态更新 var percent = video.currentTime / video.duration; // 播放时间占总时间百分比 progress_bar_played.width(percent * progress_bar.width()); // 设置播放进度条宽度 }, 'loadedmetadata': function () { // 视频元数据加载完毕后触发 duration.html(timeFormat(video.duration)); } });
在上面的代码中,将之前监听video的play事件换为loadedmetadata事件,是为了保证视频的元数据下载完毕后再设置视频总耗时,避免在play事件触发时获取video的duration属性为空。
提示
视频的元素据包含时长、尺寸(仅视频)以及文本轨道等信息。
3.3.3 添加视频快进慢进按钮
HTML 5的video元素几乎带来了所有传统播放器都具备的功能,本例将在自定义播放器上加入慢进和快进按钮。使用Chrome浏览器打开网页文件,点击屏幕中央的三角播放按钮,播放器底部出现自定义工具条,同时下方工具条左端出现快慢进按钮,运行效果如图3.9所示。
图3.9 点击播放按钮
本例的代码构建在“3-6.添加视频快进慢进按钮.html”的功能基础之上。下面对增添的代码部分做一个分析。
字符模板增加快进和慢进HTML结构,代码如下:
<div class="control backward_control" data-type="backward" title="慢退 "><div></div><div></div></div> <div class="control forward_control" data-type="forward" title="快进 "><div></div><div></div></div>
提示
backward_control和forward_control样式类,读者可以参考下载资源源码。
在HTML结构中,自定义data-type属性表示对应按钮元素执行的方法名。快进和慢进在委托方法中对应的方法名为“backward”和“forward”,方法执行脚本如下:
case 'backward': // 自定义工具条慢退 self._playbackRate(-0.1); // 给实例方法_ playbackRate传入负数播放速度 break; case 'forward': // 自定义工具条快进 self._playbackRate(0.1); break;
如上代码所示,快慢进执行相同的实例方法_playbackRate,该方法接收1个数字参数,表示增减播放速度,_playbackRate代码如下:
_playbackRate: function (rate) { this.video.get(0).playbackRate += rate; }
playbackRate属性表示视频的播放速度,默认值为1,数值越小播放速度越慢,反之亦然。
3.3.4 处理带字幕的视频
本例将给读者介绍的是在HTML 5视频中添加字幕。听起来这像是一项非常复杂的工作,不过HTML 5已经将字幕文件进行抽象独立,同时非常简单的就能在任意视频中添加字幕。本节示例不能直接用浏览器打开文件,否则Chrome下会报出“Cross-origin text track load denied by Cross-Origin Resource Sharing policy.”的错误信息,表示字幕文件加载违反了跨域资源共享策略。所以,需要将文件部署在Web服务器上,如Apache、Nginx、IIS等。
部署完毕后,用Chrome打开对应网址,点击屏幕中央的三角播放按钮,视频的下方出现字幕,效果如图3.10所示。
图3.10 点击播放按钮,视频下方出现字幕
本例的代码构建在“3-7.处理带字幕的视频.html”的基础上,没有额外的脚本改动。在HTML中增加字幕结构,代码如下:
<video width="480" height="320" controls poster="../images/BigBuck.png" preload="none"> <source src="../res/BigBuck.webm" type="video/mp4"> <track label="English subtitles" kind="captions" srclang="en" src="../res/BigBuck.vtt" default> </vidde>
track标签为视频规定外部文本轨道,标签带有多种新属性,具体如下:
● default:表示该轨道是默认的。
● kind:表示轨道文本类型,如:captions、chapters、descriptions、metadata、subtitles。
● label:轨道的标签或标题。
● src:轨道的URL。
● srclang:轨道的语言,若kind属性值是“subtitle”,则该属性是必需的。
本例track标签的轨道文件为1个WebVTT文件。WebVTT文件是一个简单的纯文本,打开“../res/BigBuck.vtt”,代码如下:
WEBVTT // 文本轨道文件开头,必填 00:00.000--> 00:01.000 <c>字幕出现---1</c> // 表示可以带CSS的文本 00:01.000--> 00:02.000 <i>字幕出现---2</i> // 斜体文本 00:02.000--> 00:03.000 <b>字幕出现---3</b> // 粗体文本 00:03.000--> 00:04.000 <u>字幕出现---4</u> // 带下划线文本 00:04.000--> 00:05.000 <v.loud>字幕出现---5 // 声音文本加样式 00:05.000--> 00:06.000 <v Man>字幕出现---6 // 声音文本加人物名
在WebVTT文本中,所有c标签都可以带CSS样式,比如<c.demoClass>。还有一种v标签,也可以带样式,同时还可以加入人名。本例给v标签加上2种样式类,如下代码所示:
::cue(.loud) { font-size: 2em; color:Red; } ::cue(v[voice="Man"]) { color: green }
该类声音文本样式需要以字符串“::cue”开头,cue表示指定文本和视频文件中字幕的时间定位。小括号中间的内容如同CSS,表示选择器的名称。其中的“.load”如同一般CSS,表示文本中的样式类。其中,“v[voice="Man"]”表示对应人物音轨文字的样式类。
提示
WebVTT还有更多更奇怪的设置,详情可以参考网址http://dev.w3.org/html5/webvtt/。