现在的网页中有很多场景中会涉及到视频播放,个人认为原生的控件已经做得很好了。但是老有人觉得原生的就是丑的,有什么办法呢,('_')。本文利用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));
});
duration
和currentTime
属性返回的是包含小数位的秒数据位,借助下面的工具函数将数据进行格式化,便于显示。
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