视频云web播放器样式和组件自定义


web播放器介绍

视频云提供的web播放器是以videojs为基础,加入了视频云自身的一些业务逻辑封装并针对性的优化而成。当前视频云直播流支持rtmp,http-flv,hls三种格式,点播支持mp4,http-flv,hls三种格式。

在pc端浏览器上,我们优先使用flash模式来播放直播和点播的各种视频格式,以提供更强大的流控制和更好的稳定性。在移动端浏览器上,使用html5模式,支持hls播放和点播mp4播放。 我们的sdk接口也支持设置techOrder:["html5","flash"]来在优先使用html5模式,值得一提的是html5模式下的hls播放并不稳定,依浏览器不同可能更容易出现播放问题。

在即将到来的下一个版本的web sdk中,我们将可能接入hls.js和flv.js。前者将提供html5模式下hls流更强大的稳定性,后者给咱们带来http-flv流在移动端浏览器播放的黑科技(喜大普奔,orz)。

近来收到很多客户自定义播放器皮肤或者组件的需求,但官网的帮助文档中缺乏对这一块儿的细致说明,本文由此而生,希望能借由本文加深大家对咱们web播放器的理解,同时能更容易的接入咱们sdk,满足各自的业务需求。

我们的播放器提供了一些默认的组件,包括posterImage,loadingSpinner,bigPlayButton,errorDisplay,controlBar等,通过名字,您应该已经能大概明白这些组件的提供功能,下面会做更详细的介绍。这些默认组件应该能满足大部分用户的基础需求,但是在很多场景下,咱们需要更深度的制定符合业务需求的样式和功能。接下来会介绍一些相关api,以及两个简单示例,由浅入深,都是干货。

播放相关的事件

很多播放组件的状态都和播放事件有关联,因此要开发播放组件,一定要先了解我们播放器能提供哪些播放相关的事件。我们知道html5 video标签能提供下面这些事件:

Html5.Events = [
  'loadstart',
  'suspend',
  'abort',
  'error',
  'emptied',
  'stalled',
  'loadedmetadata',
  'loadeddata',
  'canplay',
  'canplaythrough',
  'playing',
  'waiting',
  'seeking',
  'seeked',
  'ended',
  'durationchange',
  'timeupdate',
  'progress',
  'play',
  'pause',
  'ratechange',
  'volumechange'
];

这些事件的相关说明可以参见MDN

我们的播放器在播放时有flash和html5两种模式,html5模式使用video的这些原生事件,flash模式下则对这些事件做了模拟。因此我们可以使用下面的方式来监听这些事件:

var myPlayer = neplayer('my-video');
function onEnded(){
    console.log("ended");
    myPlayer.off("ended",onEnded);
    //do something
}
myPlayer.on("ended",onEnded);

播放器初始化的说明

我们使用neplayer(id,options,cb)或者video标签中的data-setup这两种方式来初始化播放器。初始化过程除了播放相关的逻辑,还有就是默认播放组件的添加。

聪明的同学在使用chrome查看播放器元素的时候可以看到,我们给video标签外面包裹了一个div,这个div就是我们播放器的容器。容器下面的子元素首先就是咱们的播放器元素(html5模式下的video标签或者flash模式下的object),后面的子元素们就是我们的各种播放组件啦。

播放容器div会在不同状态下动态添加一些很有用的class,合理运用这些class,可以简便咱们的组件的开发,在下面罗列一些。

vjs-playing                 //播放状态
vjs-paused                  //暂停状态
vjs-waiting                 //播放buffer为空,等待数据中 (对应播放事件中的waiting)
vjs-seeking                 //seeking中 (对应播放事件中的seeking)
vjs-ended                   //播放结束
vjs-has-started             //播放已经开始了,默认组件的posterImage就是通过这个class来隐藏自己
vjs-live                    //当前播放的是直播流,controlBar组件中的liveDisplay子组件通过这个来显示自己
vjs-controls-disabled       //应该隐藏所有的控制组件时,例如有错误发生时
vjs-error                   //有错误时
vjs-user-inactive           //用户当前处于非活动状态
vjs-user-active             //用户处于活动状态, 活动状态是指用户最近是否有移动鼠标或者点击按钮等操作。
                            //这种活动状态在没有新操作情况下,默认维持两秒,然后就进入非活动状态。
                            //在非活动状态下,一般会隐藏控制条的显示,这时候就用到这两个class了
                            //活动状态维持的时间可以配置,配置选项中默认 inactivityTimeout:2000

chrome查看容器下dom结构截图:

默认组件的说明

前面提到,我们的播放器提供了一些默认的组件,下面对这些组件做些粗略的说明。

这几个是默认使用的播放控件,假如您嫌弃它们,可以在初始化选项中配置舍弃,例如您要实现自己的bigPlayButton,您可以配置{"bigPlayButton":false},这样默认的bigPlayButton就不会被添加到播放器中,然后您可以使用后面我们将会提到的添加播放组件相关api来添加自定义的大播放按钮。

controlBar组件的说明

controlBar组件是咱们播放器的默认控制条组件,它包含了一系列的子组件。

        'playToggle',  //播放暂停按钮
        'volumeMenuButton',//音量控制

        'currentTimeDisplay',//当前播放时间
        'timeDivider',      //  '/'
        'durationDisplay',  //总时间

        'progressControl',  //点播流时,播放进度条,seek控制
        'liveDisplay',      //直播流时,显示LIVE
        'remainingTimeDisplay',  //当前播放时间
        'playbackRateMenuButton', //播放速率
        'fullscreenToggle'  //全屏控制

从左到右,当前默认显示的子组件有 playToggle volumeMenuButton progressControl liveDisplay remainTimeDisplay,fullscreenToggle。要注意的是liveDisplay组件在播放直播流时才显示,下图为点播示例:

currentTimeDisplay,timeDivider,durationDisplay是相对于 remainingTimeDisplay的另一套组件,后者只显示当前播放时间,前者还显示总时间。若要显示成前者这种模式,即 '当前时间/总时间',可以在初始化播放器选项中配置:

var myPlayer = neplayer('my-video', {controlBar:{
    'currentTimeDisplay':true,
    'timeDivider':true,
    'durationDisplay':true,
    'remainingTimeDisplay':false
}}, function() {
    console.log('播放器初始化完成');
});

playbackRateMenuButton,设置播放速率的组件。当前只有html5模式下才支持设置播放速率。

var myPlayer = neplayer('my-video', {controlBar:{
    'playbackRateMenuButton':{
        'playbackRates': [0.1, 0.5, 1, 1.5, 2, 5]
    }
}}, function() {
    console.log('播放器初始化完成');
});

使用'当前时间/总时间'组件,并添加播放速率控制的效果图:

默认组件样式的修改

上面说到,每个组件和子组件都在播放容器中有对应的标签元素,这些元素上面都有各自的class类,因此要修改默认组件的样式,定义自己喜欢的皮肤将会灰常简单,您只需要写几句覆盖默认样式的css即可。 我们播放组件中的图标,使用的都是字体图标,意味着您可以随意修改图标的大小,颜色。来个栗子:

/* add css */
.vjs-control-bar{
    color:red;
    font-size:20px;
}

可以得到:

添加自定义组件

终于到本文的核心内容了,直接先上一盘栗子。 给咱们播放器的默认controlBar组件中加入一个新子组件,一个刷新的按钮,用来刷新播放器。 No more talk, show me the code !!!

var myPlayer;

//获取咱们组件的基类,所有组件都要继承自这个类。
var Component = neplayer.getComponent("Component");

//nePlayer.extend  继承基类
//第二个参数是一个对象,包含createEl方法,这个方法中需要返回一个html元素
//这个组件元素在后面将被添加到播放容器中
var RefreshComponent = neplayer.extend(Component,{
    createEl:function(){
        var button = document.createElement("button");
        button.innerHTML = "刷新";
        return button;
    }
});

//实例化这个组件
var refreshComponent = new RefreshComponent();

//组件基类实现了事件绑定相关的接口,可以使用on绑定事件,off解绑事件
//这里给组件绑定了click事件,当点击组件元素的时候会触发
refreshComponent.on("click",function(){
    console.log("click refresh button component");
    myPlayer.refresh();
});

//组件被销毁时触发,可以在这里做一些回收操作,防止内存泄露
refreshComponent.on("dispose",function(){
    console.log("dispose refresh button component");
});
//可以使用refreshComponent.dispose()来移除并销毁这个组件

//在初始化播放器完成的回调函数中,将组件添加到容器中
myPlayer = neplayer('my-video', {}, function() {
    console.log('播放器初始化完成');

    //myPlayer.corePlayer是播放器组件的基础容器,可以看做组件树的根节点
    //myPlayer.corePlayer.controlBar 拿到默认组件controlBar
    //使用组件的addChild方法来添加子组件
    //addChild第二个参数传空对象就好,第三个参数为一个数值,代表子组件元素添加到父组件所在的位置
    //第三个参数传空时表示添加到父组件的末尾,这里值为1表示为父组件孩子中的第二个节点
    myPlayer.corePlayer.controlBar.addChild(refreshComponent,{},1);
});

效果如下: (the button is so ugly,why not a icon? do not mind these detail,then we can be good friends. -_-)

栗子虽然简单,但是已经覆盖了自定义组件全部相关接口。 本来这里我还想搞一个多清晰度选择的menu,但是因为时间有限(其实是懒),所以就不弄了,但是真要做也大同小异。 下面再来一个自定义错误提示的栗子,然后完美收官。

/* css */
.my-error-display{
    position:absolute;
    font-size:20px;
    color:red;
    width:80%;
    top: 50%; left: 50%;
    transform: translate(-50%,-50%);
    border:1px solid white;
    padding:10px;
    display:none;
}
/*  前面提到,播放器出现错误时,我们的播放容器div会添加class vjs-error
 *  这里我们使用这个类来作为错误信息的显示前提 
 */
.vjs-error .my-error-display{
    display:block;
}


/* js */
//错误组件使用的元素
var errorElement = document.createElement("div");
//添加class   my-error-display
neplayer.addClass(errorElement,"my-error-display");

//继承组件
var Component = neplayer.getComponent("Component");
var ErrorComponent = neplayer.extend(Component,{});

//和上面的刷新按钮那个栗子比较,这里提供了第二种声明组件元素的时机,即在实例化组件的时候传入组件元素
var errorComponent = new ErrorComponent(null,{el:errorElement});

//注意初始化选项里面的 `errorDisplay:false` ,这个配置告诉播放器别添加默认的错误组件
myPlayer = neplayer('my-video', {errorDisplay:false}, function() {
    console.log('播放器初始化完成');
    //添加组件到 myPlayer.corePlayer根节点
    myPlayer.corePlayer.addChild(errorComponent,{});
});

//监听播放器错误事件,以展示错误消息
myPlayer.onError(function(error){

    errorElement.innerHTML = error.errMsg;
    //或者使用error.errCode
    //您可以利用errCode显示自定义的错误提示内容
    //
    //errorElement.innerHTML = error.errCode===2?'网络出错':error.errMsg;
    //
    //播放器常见的错误消息有
    //  1: 'You aborted the media playback',
    //  2: 'A network error caused the media download to fail part-way.',
    //  3: 'The media playback was aborted due to a corruption problem 
          or because the media used features your browser did not support.',
    //  4: 'The media could not be loaded, either because the server or network failed 
          or because the format is not supported.',
    //  5: 'The media is encrypted and we do not have the keys to decrypt it.',
    //  6: '请勿使用推流地址拉流'
    //  7: '拉流超时'
});




---------------------------------------- thanks -----------------------------------------