构建移动网站与APP:HTML 5移动开发入门与实战(跨平台移动开发丛书)
上QQ阅读APP看书,第一时间看更新

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/