JustYeh的前端博客

折腾一个html5播放器

2017-09-27

video

现在的网页中有很多场景中会涉及到视频播放,个人认为原生的控件已经做得很好了。但是老有人觉得原生的就是丑的,有什么办法呢,('_')。本文利用html5 video的相关api,尝试折腾出一个“美化”过的的视频播放器。

代码地址https://github.com/justyeh/y-video-player

构建HTML结构

<div class="y-video-box">
        <video id="y-video" src="http://www.runoob.com/try/demo_source/movie.mp4" poster=""></video>
        <div class="control-box">
            <div class="play-control">
                <i class="fa fa-play"></i>
                <i class="fa fa-pause"></i>
            </div>
            <div class="time">
                <span class="time-current">00:00</span> /
                <span class="time-duration">00:00</span>
            </div>
            <div class="process">
                <div class="process-buffered"></div>
                <div class="process-played">
                    <i class="drag-bar"></i>
                </div>
            </div>
            <div class="volume">
                <i class="fa fa-volume-up"></i>
                <i class="fa fa-volume-off"></i>
                <div class="volume-control">
                    <div class="volume-process">
                        <div class="volume-now">
                            <i class="drag-bar"></i>
                        </div>
                    </div>
                </div>
            </div>
            <div class="rate">
                <div>1.0x</div>
                <ul>
                    <li data-val="2">2.0x</li>
                    <li data-val="1.5">1.5x</li>
                    <li data-val="1" class="active">1.0x</li>
                </ul>
            </div>
            <div class="fullscreen fullscreened">
                <i class="fa fa-expand"></i>
                <i class="fa fa-compress"></i>
            </div>
        </div>
    </div>
</div>

播放控制

控制视频的播放暂停主要是使用play()pause()两个方法,先判断视频是否处于播放状态,然后切换状态就可以了。

let $videoBox = $(".y-video-box");
let $videoElement = $('#y-video');
let videoElement = $videoElement[0];
let $controlBox = $(".control-box");

let $playControl = $(".play-control");
$playControl.click(function (e) {
    let isPlaying = videoElement.currentTime > 0 && !videoElement.paused && !videoElement.ended && videoElement.readyState > 2;
    if (!isPlaying) {
        videoElement.play();
        $playControl.addClass("playing")
    } else {
        videoElement.pause();
        $playControl.removeClass("playing")
    }
    return false;
})

获取视频当前事件/总时长

当指定的视频的元数据已加载时,会发生loadedmetadata事件。借此我们可以获取视频的时长、尺寸以及文本轨道。

/*获取总时长*/
let $timeDuration = $(".time-duration")
$videoElement.on('loadedmetadata', function () {
    $timeDuration.text(numberToTime(videoElement.duration));
});

当视频的播放位置发生改变时会触发timeupdate事件,配合currentTime(设置或返回视频播放的当前位置) 属性,就可以获取视频的播放进度。

let $timeCurrent = $(".time-current");
$videoElement.on('timeupdate', function () {
    let maxduration = videoElement.duration;
    let currentPos = videoElement.currentTime;
    /*视频进度+时长*/
    $timeCurrent.text(numberToTime(currentPos));

});

durationcurrentTime属性返回的是包含小数位的秒数据位,借助下面的工具函数将数据进行格式化,便于显示。

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;
}

进度条

进度条有两个,一个是当前进度,一个是缓冲进度。

设置的方法都是获取相关时间点,然后时间点/总时长*100%,然后将得到的值设置为div的width属性即可。

  • 借助上面的timeupdate事件,利用currentTime属性获取当前播放位置。
  • 利用buffered属性获取TimeRanges对象,取TimeRanges对象里面的end(index),从而获得某个已缓冲范围的结束位置。
let $processPlayed = $(".process-played"),
    $processBuffered = $(".process-buffered");
$videoElement.on('timeupdate', function () {
    let maxduration = videoElement.duration;
    let currentPos = videoElement.currentTime;
    let currentBuffer = videoElement.buffered.end(0);

    /*播放进度条*/
    $processPlayed.css('width', 100 * currentPos / maxduration + '%');
    /*缓冲进度条*/
    $processBuffered.css('width', 100 * currentBuffer / maxduration + '%')
});

声音控制

实现点击声音图标,切换静音功能。

let $volumeControl = $(".volume");
$volumeControl.find("i").click(function () {
    $volumeControl.toggleClass("muted")
    if ($volumeControl.hasClass("muted")) {
        videoElement.muted = true;
        $volumeNow.css('height', '0%');
    } else {
        videoElement.muted = false;
        $volumeNow.css('height', videoElement.volume * 100 + '%');
    }
});

进度/音量的点击与拖拽

点击与拖拽控制是一个难点,具体的思路是根据鼠标在进度条上的位置/进度条的width(声音是height),获取一个比例,通过这个比例来进行赋值。

进行更新之前要先判断是否处于拖拽状态。

let timeDrag = false,
    volumeDrag = false;
$(document).mouseup(function (e) {
    if (timeDrag) {
        timeDrag = false;
        updateVideoTime(e.pageX);
    }
    if (volumeDrag) {
        volumeDrag = false;
        updateVideoVolume(e.pageY);
    }
});
$(document).mousemove(function (e) {
    if (timeDrag) {
        updateVideoTime(e.pageX);
    }
    if (volumeDrag) {
        updateVideoVolume(e.pageY);
    }
});

时间进度条

直接点击进度条

let $timeProcess = $(".process");
$timeProcess.mousedown(function (e) {
    updateVideoTime(e.pageX);
});

拖拽进度条

$timeProcess.find(".drag-bar").mousedown(function (e) {
    timeDrag = true
});
let updateVideoTime = function (x) {
    //超出临界值后拖拽无效
    if (x < $timeProcess.offset().left || x > $timeProcess.offset().left + $timeProcess.width()) {
        return
    }
    let maxduration = videoElement.duration; //视频总时长
    let position = x - $timeProcess.offset().left; //变化量
    let percentage = position / $timeProcess.width();
    //超出范围的修正
    if (percentage > 1) {
        percentage = 100;
    }
    if (percentage < 0) {
        percentage = 0;
    }
    console.log(maxduration * percentage)
    //更新进度条和当前时间
    videoElement.currentTime = maxduration * percentage;
    //$processPlayed.css('width', (percentage * 100)+'%');
}

声音进度条

初始化精度条的位置

let $volumeProcess = $(".volume-process"),
    $volumeNow = $(".volume-now");
$volumeNow.css('height', videoElement.volume * 100 + '%');

直接点击进度条

$volumeProcess.mousedown(function (e) {
    updateVideoVolume(e.pageY);
});

拖拽进度条

$volumeProcess.find(".drag-bar").mousedown(function (e) {
    volumeDrag = true
});

let updateVideoVolume = function (y) {
    //超出临界值后拖拽无效
    if (y < $volumeProcess.offset().top || y > $volumeProcess.offset().top + $volumeProcess.height()) {
        return
    }
    let position = y - $volumeProcess.offset().top; //变化量
    let percentage = 1 - (position / $volumeProcess.height());
    /*0~0.05取个过渡,都认为是0*/
    if (percentage < 0.05) {
        $volumeControl.addClass("muted")
        percentage = 0
    } else {
        $volumeControl.removeClass("muted")
    }
    console.log(percentage)
    //更新进度条和当前时间
    videoElement.volume = percentage;
    $volumeNow.css('height', percentage * 100 + '%');
}

视频播放速率

播放速率的控制比较简单,使用playbackRate这个属性就可以完成。

let $videoRate = $(".rate");
$videoRate.find("li").click(function () {
    if ($(this).hasClass("active")) {
        return
    }
    $(this).addClass("active").siblings().removeClass("active");
    $videoRate.find("div").text($(this).text())
    videoElement.playbackRate = parseFloat($(this).data("val"))
})

全屏控制

全屏的兼容处理起来比较麻烦,参考下面的代码:

let $fullScreen = $(".fullscreen");
$fullScreen.click(function () {
    $(this).toggleClass("fullscreened");
    $videoBox.toggleClass("fullscreened");
    if ($(this).hasClass("fullscreened")) {
        fullScreenOff()
    } else {
        fullScreenOn(videoElement)
    }
})

切换到全屏

function fullScreenOn(element) {
    if (element.requestFullscreen) {
        element.requestFullscreen();
    } else if (element.mozRequestFullScreen) {
        //firefox会用<video>元素的父元素去调用全屏
        $videoBox[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;*/
    }
    $controlBox.css('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;
    }
}

总结

  • 在chrome中,使用本地视频时,设置currentTime会出现视频重新播放的问题,将视频改为http(s)地址就好了。

  • 时长(声音)进度条上的拖拽点现在还有一点问题,为了美观将它的left设置成了负值(声音为top),拖拽有可能造成超出临界值,这造成了拖拽的一些bug,目前还有待解决。

参考文章:http://cheri.love/post/xue-xi-lei/h5-liu-lan-qi-zi-ding-yi-yong-hu-kong-jian