需求
- 播放暂停切换
- 播放进度条反馈和控制 (拖拽 点击) 播放时间 00:00/05:30
- 音量调控 反馈 一键静音/取消静音
- 全屏播放 循环模式
- 播放状态反馈 未播放 已播放 播放完毕
- 本地存储播放时间 自动续播
准备
[
{
方法:[ play(),pause()],
属性: [paused],
element:[#contr-play]
},{
方法:[ mousedown,pause,mousemove,mouseup,play],
属性: [currentTime],
element:[player-progress,progress-line,progress-unit,player-time,player-current]
},{
方法:[ mousedown,mousemove,mouseup],
属性: [volume,muted],
element:[]
},{
}
]
* video设置currentTime的时候 会触发 canplay事件
* 拖拽进度条之后 播放视频
点击进度条某个位置 直接开始跳转播放
click {
mousedown
mouseup
}
效果
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/1d80f2965b4a4163ae7837e7ba416b47.png)
实现
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>video 播放器</title>
<style>
@font-face {
font-family: "iconfont";
src: url('fonts/iconfont.eot?t=1600426546624');
src: url('fonts/iconfont.eot?t=1600426546624#iefix') format('embedded-opentype'),
url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAS4AAsAAAAACZwAAARrAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDSAqGEIUYATYCJAMYCw4ABCAFhG0HWBtOCFGULspqsq+wKWO+GKJwXogXwkCjQQ+Evn05o/Tmdn/CWtgG7CKF5wDBE+3X3uzO7ldJh0qDrg1PXronIomYrETRBCXeT3b2b3lEpbkvLFIehM2H9pLZzSaenZwqqfxQaVQqGrOyuvDwtnv/7k9So5BJKoGZTRUuRKGkK0x2QTiRPI44CCLOEosr0+0G0l/4RJO7B8EAIYJA47YGzrlqkEjLIJhA8XigAzpJpmAVUP1xr+qf5x8s61myQaI496BcKFwuUIHGXmQtSLmCWgewTbzfuh4CYqPWYKb61l6Ip/TdBMhpmTrEJ+y0ghKhyRccm8gHgrDcszcAePB+Xn5wJB4MV9FvdLjVuaGyp3ePG401m+oojA7nBdiTwIJlgELeQv8FLsgv48TyX+BtA1IhQ/tqFrOS69292J+EWg+L1PiXB46lDIEMQJ9S5K7OHaDCkkeGYlixMFTCisJwDGwY/N9TXpbZD9gzAPwBiGwk6QcOfgcvpWOqmuni+nDbJCffPri99vX5c4JDgObuNOtT9Qobn1udt1pvXyqbSOFmnLnrUp1xT05in3ORNC6+nT8KbDu2cILh7o5rl+7K1J35e9b7WiHOI4LWDRwQhtIW7srqIIzWBM4jQrddyno3JkG8WNmroq/UENHQrkXGZmh3yKq0M7oO1tmbuCt311QH7T+SszNH3zmavW3H1gfaO3fpCqqAIVBbk3Rl6+lFYfQpjxD/1LCiaKujjpQD+89yHz9y0JxlpFBTnnAd1a2FWEvuByU7/O7KqNeXCjfdL1cSSuJRmNo0Bpn8r4CxZCUHHcjbCN6cUle4Ls4hLHIutpit6Y6v6lp58hMru1btw3RqwOWJcnTx2zmdm0nVEhLUh2y1sY2ZRsiMJ91hePo8SkrtC2z+X28ZH25J8x/lc11WCMukL2WvGeIKj5MKnvzeVZWYhp/oVg54db9lpL7krXtXOz+MYaIn3rxmf0cKGU2aLN3uOQqu3RZ9sPmavnmV/5xqhowU6qgjOVx+IowrAqhXhNR4GWQA/BR+4datAgD7sL8Y/reDi/+X/Q6P6ylpaaV/QlEAPuYvOLy+4WiCbQB/FQONFf8pRcM5VVS6w6c02xpCgLf/sbIGxMYS/JPCtVdDBTsQeUXIKQBGYASs0DSq5DI4iVXwQmsQW9JwcqJBcCQjYNEUAaJ0BozcM1ilm6iSL8HpfQGvDAexo8g6Z2I2VmINwTrFbtQ/jCxXKGiYLprot3ZjM+rXSVzl0TRMbCuFVElJlxpwEJMhDrAHTAelBjJIKIDqiZthvz+EwiTkxS4qeSgNF8uyUfRFkisUgERbCExHYW5Iv2GIxSUkyHBWphIz398NM0X56UjFTFlUgxE2a+2ISqI0QBv0wUYzl/IS2wCTA0UZEAMREoDUE17Mb+RDkHDxVl6YCyXxdMiGFZPJRUZTvTS9MXCXVwEx/aN8GLGi4sRLgLv1qI3jhqJBT1QPxg9bQb+lB834SGLzsBU0xU6/PowJAAA=') format('woff2'),
url('fonts/iconfont.woff?t=1600426546624') format('woff'),
url('fonts/iconfont.ttf?t=1600426546624') format('truetype'),
url('fonts/iconfont.svg?t=1600426546624#iconfont') format('svg');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 32px;
color: #ccc;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
cursor: pointer;
}
.icon-pause:before {
content: "\e7d2";
}
.icon-loop:before {
content: "\e6a9";
}
.icon-volume:before {
content: "\e63a";
}
.icon-fullscreen:before {
content: "\e640";
}
.icon-player:before {
content: "\e62b";
}
div {
box-sizing: border-box;
}
video {
display: block;
width: 100%;
max-height: 100%;
}
.player {
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
width: 638px;
height: 493px;
margin: 100px auto;
background-color: #000;
}
.player .hint {
position: absolute;
top: 0;
width: 100%;
height: 30px;
color: #fff;
text-align: center;
line-height: 30px;
font-size: 20px;
letter-spacing: .2em;
}
.player-screen {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
display: flex;
justify-content: center;
}
.player-controls {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
position: absolute;
bottom: 0;
width: 100%;
height: 60px;
}
.player-progress {
position: relative;
width: 100%;
height: 4px;
background-color: #ccc;
}
.player-progress:hover .progress-unit {
display: block;
}
.player-progress .progress-line {
position: relative;
width: 0;
height: 4px;
background-color: rgb(18, 177, 226);
}
.player-progress .progress-unit {
display: none;
position: absolute;
right: -18px;
top: 0;
bottom: 0;
margin: auto;
width: 18px;
height: 18px;
border-radius: 50%;
background-color: rgb(18, 177, 226);
cursor: pointer;
}
.player-time {
position: absolute;
right: 0;
top: -35px;
color: #fff;
}
.player-controls .player-operator {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 4px 20px;
}
.operator-right {
display: flex;
align-items: center;
}
.operator-right i {
padding: 0 6px;
}
.is-loop {
color: pink;
}
.is-muted {
color: red;
}
.volume-range {
width: 100px;
height: 5px;
}
</style>
</head>
<body>
<div id="my-player" class="player">
<p class="hint">暂未播放影片</p>
<div class="player-screen">
<video src="video/mp4.mp4">
<source src="video/mp4.mp4" type="video/mp4">
</video>
</div>
<div class="player-controls">
<div class="player-progress">
<p class="player-time"><span class="player-current">00:00:00</span> / <span class="player-count">00:00:00</span>
</p>
<div class="progress-line">
<div class="progress-unit"></div>
</div>
</div>
<div class="player-operator">
<i id="contr-play" class="iconfont icon-player icon-pause"></i>
<div class="operator-right">
<input type="range" value="0.1" id="volume-range" class="volume-range">
<i id="contr-volume" class=" iconfont icon-volume"></i>
<i id="contr-loop" class=" iconfont icon-loop"></i>
<i id="contr-full" class=" iconfont icon-fullscreen"></i>
</div>
</div>
</div>
</div>
<script src="js/common.js"></script>
<script src="js/Storage.js"></script>
<script>
class MyVideo {
static progressMaxWidth = $('.player-progress').offsetWidth;
constructor(player = $('video')) {
this.player = player;
this.canPlay = false;
this.storage = new Storage('local');
this.$ele = {
$playBtn: $('#contr-play'),
$progress: $('.progress-line'),
$progressUnit: $('.progress-unit'),
$currentTime: $('.player-current'),
$countTime: $('.player-count'),
$fullScreen: $('#contr-full'),
$loop: $('#contr-loop'),
$volumeBtn: $('#contr-volume'),
$volumeRange: $('#volume-range'),
}
this.progress = {
isDown: false,
startX: 0,
width: 0,
};
this.eventListener();
}
set isPaused(val) {
this._paused = val;
if (val) {
this.myPlay();
return false;
}
this.myPause();
}
get isPaused() {
return this._paused;
}
set volume(val) {
this._volume = val;
this.player.volume = val;
this.$ele.$volumeRange.value = val * 100;
}
get volume() {
return this._volume;
}
initPlayer() {
if (this.canPlay) {
return false;
}
this.volume = 0.1;
this.player.volume = this.volume;
this.canPlay = true;
this.checkPlayHistory();
this.setCountTimeText()
this.setCurrentText();
}
checkPlayHistory() {
let current = ~~this.storage.getStorage(this.player.src);
if (!current) {
return false;
}
this.setCurrentTime(null, current);
}
eventListener() {
const dragMap = {
'mousedown': (e) => {
const target = e.target;
if (e.target.className === 'progress-unit') {
if (this.progress.isDown) {
return false;
}
this.progress.startX = e.clientX;
this.progress.isDown = true;
this.progress.width = this.$ele.$progress.offsetWidth;
this.isPaused = false;
return false;
}
if (/progress/g.test(e.target.className)) {
let _x = e.clientX - getPosition($('.player-progress')).left;
let ratio = _x / MyVideo.progressMaxWidth;
let time = ratio * this.player.duration;
this.setCurrentTime(null, time);
this.isPaused = true;
}
},
'mousemove': (e) => {
if (!this.progress.isDown) {
return false;
}
let _x = e.clientX - this.progress.startX;
let width = _x + this.progress.width;
let ratio = width / MyVideo.progressMaxWidth;
let time = ratio * this.player.duration;
this.setCurrentTime(null, time);
},
'mouseup': (e) => {
if (!this.progress.isDown) {
return false;
}
this.isPaused = true;
this.progress.isDown = false;
}
}
this.$ele.$playBtn.addEventListener('click', () => {
this.isPaused = this.player.paused;
if (!this.canPlay) {
return false;
}
}, false);
this.player.addEventListener('canplay', this.initPlayer.bind(this));
this.player.addEventListener('timeupdate', this.setCurrentTime.bind(this));
this.$ele.$fullScreen.addEventListener('click', this.changeFullScreen.bind(this));
this.$ele.$loop.addEventListener('click', this.changeLoop.bind(this));
this.$ele.$volumeRange.addEventListener('change', this.changeVolume.bind(this), false);
this.$ele.$volumeBtn.addEventListener('click', this.chageMuted.bind(this), false);
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
this.isPaused = false;
this.setVideoInfo();
}
});
const drag = (e) => {
let type = e.type;
if (dragMap.hasOwnProperty(type)) {
dragMap[type](e);
}
}
document.addEventListener('mousedown', drag, false);
document.addEventListener('mousemove', drag, false);
document.addEventListener('mouseup', drag, false);
}
setProgress(done) {
let ratio = this.player.currentTime / this.player.duration * 100;
if (done) {
this.progress.width = ratio * MyVideo.progressMaxWidth;
return false;
}
this.$ele.$progress.style.width = ratio + '%';
}
setCurrentTime(e, time) {
if (time) {
this.player.currentTime = time;
}
this.setProgress();
this.setCurrentText();
}
setCurrentText() {
this.$ele.$currentTime.innerText = this.getCurrentTime();
}
setCountTimeText() {
this.$ele.$countTime.innerText = this.getCountTime();
}
getCountTime() {
return MyVideo.fromatTime(this.player.duration);
}
getCurrentTime() {
return MyVideo.fromatTime(this.player.currentTime);
}
static fromatTime(time) {
let [s, m, h] = [~~(time % 60), ~~(time / 60), ~~(time / 3600)];
[s, m, h] = [s, m, h].map(padLeft);
return `${h}:${m}:${s}`;
}
myPlay() {
this.$ele.$playBtn.classList.remove('icon-player');
this.player.play();
}
myPause() {
this.$ele.$playBtn.classList.add('icon-player');
this.player.pause();
}
changeFullScreen() {
if (!isFullScreen()) {
openFullScreen(this.player);
return false;
}
closeFullScreen();
}
changeLoop(e) {
this.player.loop = !this.player.loop;
e.target.classList.toggle('is-loop');
}
changeVolume(e) {
let ratio = Number(e.target.value) / 100;
this.volume = ratio;
}
chageMuted(e) {
this.player.muted = !this.player.muted;
e.target.classList.toggle('is-muted');
}
setVideoInfo() {
if (!this.canPlay) {
return false;
}
this.storage.setStorage({
[this.player.src]: this.player.currentTime
})
}
}
function padLeft(num) {
return String(num)[1] && String(num) || '0' + num;
}
const myPlay = new MyVideo();
</script>
</body>
</html>