H5 浏览器自定义用户控件

3,4,---
title: H5 浏览器自定义用户控件的实现
date: 2017-08-01 14:09:00
categories: 技术

tags: 前端

参考文章:
https://css-tricks.com/custom-controls-in-html5-video-full-screen/
https://blog.hellojs.org/creating-a-custom-html5-video-player-and-the-shadow-dom-a98f29261be4

原生用户控件

对于 <video>标签,有一个名为 'controls' 的属性,按如下写法就能给播放器增加原生的暂停/开始、进度条、音量、视频最大化这些基础功能。

    <video id="myVideo" controls ></video>

自定义用户控件

但是原生控件往往无法满足我们的一些需求,所以自定义用户控件还是很必要的。
首先声明一个变量,我们把 <video id="video"></video>做如下定义

let videoElement = document.getElementById('video');
let $videoElement = $('#video');

如何实现自定义控件,说起来也很简单,只需要借助几个 H5 播放器的事件和属性就可以了:

暂停和开始

暂停和开始是最基本的功能了,实现起来很简单
开始:

videoElement.play()

暂停:

videoElement.pause()

所以只需要在当前视频暂停的时候调用play()方法就可以继续播放,同理在视频正在播放的时候调用pause()就可以了。
提的一提的是『如何判断当前视频是否暂停』
如果仅用videoElement.paused来判断视频暂停可能会有如下报错:

The play() request was interrupted by a call to pause().

经过上网查询,最后使用了isPlaying 变量来判断视频是否暂停

let isPlaying = videoElement.currentTime > 0 && !videoElement.paused && !videoElement.ended && videoElement.readyState > 2;

最后暂停/开始事件的方法如下:

        let playpauseToggle = function(e){
            let isPlaying = videoElement.currentTime > 0 && !videoElement.paused && !videoElement.ended && videoElement.readyState > 2;
            if(!isPlaying) {
                videoElement.play();
            }
            else {
                videoElement.pause();
            }
            return false;
        }

视频当前时长/总时长

这个就很容易了,借助两个参数即可;
duration 属性可以获取视频总时长
currentTime 属性可以获取视频当前时长

videoElement.duration //视频总时长
videoElement.currentTime//视频当前时长

视频总时长在视频载入后获取一次即可,
视频当前时间则不同,每当视频进度更新一次后,视频当前时间就需要随之更新
loadedmetadata事件:获取视频元数据。
timeupdate事件:视频播放后,更新播放进度的事件。 会有明确的进度变化,可以获取到currentTime
值得一提的是,用durationcurrentTime 获取到的数据格式是保留了若干位小数,以秒为单位的一个值,通常需要根据需要格式化一下,我为了将『十分钟二十四秒』格式化为10:24这样的格式,创建了如下方法

    let numberToTime = function(number){
            number = parseInt(number,10);
            let minues = 0;
            let second = 0;
        
            minues = parseInt(number / 60, 10);
            second = number % 60

            if(minues<10){
                minues = '0'+ minues
            }

            if(second<10){
                second = '0'+ second
            }

            return minues + ':' + second;
        }

以及监听对应事件并更新当前时间的代码如下

    $videoElement.on('loadedmetadata', function() {
       $('.duration').text(numberToTime(videoElement.duration));
    });

    $videoElement.on('timeupdate', function() {
       $('.current').text(numberToTime(videoElement.currentTime));
    });
 <div class="progressTime">
   <span class="current">00:00</span> / <span class="duration">00:00</span>
</div>

进度条

没有进度条的视频不是好视频,实现一个进度条很重要。
声明一点:进度条这里的样式我使用了 bootstrap 的进度条样式
dom 结构如下:

<div class="progress">
  <div class="progress-bar progress-bar-primary" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">  </div>
  <div class="bufferBar progress-bar progress-bar-buffered"></div>
</div>

有两个条,一个是当前进度,一个是缓冲进度。
基本原理也很简单,获取 当前时间/总时长 * 100% 的值,然后把这个值赋给进度条对应 dom 的样式中的 'width',通过timeupdate事件不断更新即可。

    $videoElement.on('timeupdate', function() {
        let currentPos = videoElement.currentTime;
        let maxduration = videoElement.duration;
        let percentage = 100 * currentPos / maxduration;
        $('.progress-bar-primary').css('width', percentage + '%');
     })

缓冲进度条和上面的实现基本一致,对应上面currentPos值的是videoElement.buffered.end(0),缓冲进度条应该监听progress事件,而不是 timeupdate事件

progress事件: 当浏览器正在下载音频/视频时触发。
 $videoElement.on('progress', function() {
        let currentPos = videoElement.buffered.end(0);
        let maxduration = videoElement.duration;
        let percentage = 100 * currentPos / maxduration;
        $('.progress-bar-primary').css('width', percentage + '%');
     })

注意:此处只是讲述获取方法,具体样式和取值的变化要根据实际项目进行调整

进度条不但要能看进度,还要能拖拽进度
拖拽进度的思路是:根据鼠标在进度条上的mousedown事件和mouseup事件鼠标的 X 坐标量(含正负)变化来确定当前进度的变化程度。

具体代码如下:

let timeDrag = false;   /* Drag status */
$('.progress').mousedown(function(e) {
    timeDrag = true;
    updatebar(e.pageX);
});
$(document).mouseup(function(e) {
    if(timeDrag) {
        timeDrag = false;
        updatebar(e.pageX);
    }
});
$(document).mousemove(function(e) {
    if(timeDrag) {
        updatebar(e.pageX);
    }
});
 
let updatebar = function(x) {
    let progress = $('.progress');
    let maxduration = videoElement.duration; //视频总时长
    let position = x - progress.offset().left; //变化量
    let percentage = 100 * position / progress.width();
 
    //超出范围的修正
    if(percentage > 100) {
        percentage = 100;
    }
    if(percentage < 0) {
        percentage = 0;
    }
 
    //更新进度条和当前时间
    $('progress-bar').css('width', percentage+'%');
    videoElement.currentTime = maxduration * percentage / 100;
};

音量

音量的控制很容易,主要借助以下方法和属性,具体的交互可以随心所欲的实现。之前还有看过一篇文章是介绍丧心病狂的音量键的,可以学习学习[滑稽]:http://adquan.com/post-10-41045.html

videoElement.muted = true //静音
videoElement.muted = false //解除静音

videoElement.volume = 1 //设置音量为最大值
videoElement.volume = 0.5 //设置音量为50%
videoElement.volume = 0 //设置音量为最小值

倍速播放

倍速播放很容易,把交互稍微写好点就行,实现方式非常简单 用 playbackRate就行,和控制音量差不多。

videoElement.playbackRate = 0.5; 0.5倍速播放
videoElement.playbackRate = 1;正常播放
videoElement.playbackRate = 1.5; 1.5倍速播放
videoElement.playbackRate = 2; 2倍速播放

全屏

全屏是一个兼容起来有点麻烦的功能。
如果你要为 firefox 写用户控件的话,调用全屏 api 的就得是 <video><div class="controls"><div>的共同父元素,不然会发现全屏后,你的用户控件看不到了。(但然,firefox 原生的用户控件比 chrome 的好看一百倍,所以你要是用原生的用户控件,那直接 element.requestFullscreen()就行)
同时我们也记下这个 tips :能调用全屏 api 的不只是<video>

<div class="live__player">
    <video></video>
    <div class="controls"></div>
</div>
//全屏
function fullScreenOn(element) {
    if(element.requestFullscreen) {
        element.requestFullscreen();
    } else if(element.mozRequestFullScreen) {
        $('.live__player')[0].mozRequestFullScreen();
    } else if(element.msRequestFullscreen){ 
        element.msRequestFullscreen(); 
    } else if(element.oRequestFullscreen){
        element.oRequestFullscreen();
    } else if(element.webkitRequestFullscreen)
    {
        element.webkitRequestFullScreen();
    } else{

        var docHtml = document.documentElement;
        var docBody = document.body;
        var videobox = document.getElementById('sfLive');
        var cssText = 'width:100%;height:100%;overflow:hidden;';
        docHtml.style.cssText = cssText;
        docBody.style.cssText = cssText;
        videobox.style.cssText = cssText+';'+'margin:0px;padding:0px;';
        document.IsFullScreen = true;

    }
    $controls.css('z-index','2147483647');
    
}

此处传入的值就是videoElement
(videoElement = document.getElementById('video');)

另外一点,最后一行代码做的是:把我们自定义控件的 z-index 属性设置为浏览器最大值。原因是,webkit 内核的浏览器在全屏的时候,视频的 z-index 变为了浏览器允许的最大值,我们把自定义控件的 z-index 也设置为最大值,就能避免用户控件被视频遮住。

:-webkit-full-screen {
    z-index: 2147483647;//最大值
}

同样的,退出全屏模式的代码如下:

function fullScreenOff() {
    if (document.exitFullscreen) {
        document.exitFullscreen();
    } else if (document.msExitFullscreen) {
        document.msExitFullscreen();
    } else if (document.mozCancelFullScreen) {
        document.mozCancelFullScreen();
    } else if(document.oRequestFullscreen){
        document.oCancelFullScreen();
    } else if (document.webkitExitFullscreen){
        document.webkitExitFullscreen();
    } else {
        var docHtml = document.documentElement;
        var docBody = document.body;
        var videobox = document.getElementById('sfLive');
        docHtml.style.cssText = "";
        docBody.style.cssText = "";
        videobox.style.cssText = "";
        document.IsFullScreen = false;
    }

    $('.live__playcontrol').css('z-index','1');
}

检查视频是否全屏的代码如下:

let isFullScreen =  document.fullscreenElement ||
                    document.webkitFullscreenElement ||
                    document.mozFullScreenElement ||
                    document.msFullscreenElement

踩坑

  1. 视频开始/暂停的判断依据

    文中已写,代码如下
    js
    let isPlaying = videoElement.currentTime > 0 && !videoElement.paused && !videoElement.ended && videoElement.readyState > 2;

  2. firefox 全屏后,自定义的用户控件不见了

    文中已写,和其他浏览器不同,firefox 不是用 <video>元素的 dom 对象去调用requestFullscreen(),而是用 <video>元素的父元素去调。

  3. shadowDom 隐藏的问题

如果你发现,你明明把 <video>元素的属性controls去掉了,但是原生控件依然显示,那么只有用 CSS 去干掉它

video::-webkit-media-controls-enclosure {
  display:none !important;
}

可以在下列链接中查看 <video> 的属性和事件
HTML 5 视频/音频参考手册

Comments
Write a Comment