背景
有一个需求,就是给学生发布了视频作业,记录学生的实际观看进度,防止拖动观看作弊等,以下是使用原生属性实现这个功能的步骤和代码示例。
最终效果图
HTML 结构
首先,我们需要设置基本的 HTML 结构,包括一个视频元素video标签和一个<input type="range">滑块元素:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Video Progress with Range Input</title>
<style>
/* 自定义样式 */
input[type="range"]::-webkit-slider-runnable-track {
border: none;
border-radius: 5px;
width: 100%;
height: 10px;
}
input[type="range"] {
-webkit-appearance: none;
background: #dbeafe;
margin: 10px 0;
width: 100%;
border-radius: 6px;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
height: 20px;
width: 20px;
border-radius: 50%;
background: #117035;
cursor: pointer;
margin-top: -5px;
}
</style>
</head>
<body>
<video id="myVideo" width="100%" height="300" controls>
<source src="https://api.dogecloud.com/player/get.mp4?vcode=5ac682e6f8231991&userId=17&ext=.mp4" type="video/mp4" />
Your browser does not support HTML5 video.
</video>
<input type="range" min="0" max="1" value="0" step="any" id="rangeInput" onchange="inputChange(event)" />
<p>观看进度:<span id="watch"></span></p>
<script>
// JavaScript 代码将在这里
</script>
</body>
</html>
JavaScript 功能实现
在 JavaScript 部分,主要包含以下部分:
- 初始化视频的元数据:当视频的元数据加载完成时,我们设置滑块的最大值为视频的总时长,并初始化一个记录视频播放状态的对象,使视频和滑块事件相互绑定。
- 更新时间更新:在视频播放过程中,我们不断更新滑块的值,并更新进度条的颜色来反映视频的观看状态。
- 拖动范围输入:当用户拖动范围输入时,我们暂停视频并更新进度条的颜色。拖动结束后,视频会从新的位置开始播放。
- 计算观看进度:计算用户观看视频的进度,并显示在页面上。以下是 JavaScript 代码的详细实现:
以下是 JavaScript 代码的详细实现:
const BLUE = "#dbeafe";
const GREEN = "#117035";
let isDragging = false;
let section = {};
let myArray1 = [];
const video = document.getElementById("myVideo");
const rangeInput = document.getElementById("rangeInput");
const watch = document.getElementById("watch");
// 监听视频加载实践,初始化滑块范围和记录对象
video.addEventListener("loadedmetadata", function () {
console.log("视频duration:" + video.duration);
let num = Math.floor(video.duration);
rangeInput.max = num;
// 因为我这视频一般较短,所以采用每秒记录,可以适当调整,比如5秒记录一次
for (let i = 1; i <= num; i++) {
section[i] = false;
}
});
video.addEventListener("timeupdate", onTimeupdate);
async function onTimeupdate() {
let currentTime = video.currentTime;
let duration = video.duration;
let progress = currentTime;
rangeInput.value = progress;
if (!section[Math.round(currentTime)]) {
}
section[Math.round(currentTime)] = true;
await updateRangeColor();
}
video.addEventListener("play", function () {
console.log("视频正在播放,当前播放时间" + video.currentTime);
console.log(section);
});
video.addEventListener("pause", function () {
console.log(section);
});
// 监听滑块拖动
rangeInput.addEventListener("input", function () {
isDragging = true;
let progress = rangeInput.value;
let duration = video.duration;
let currentTime = progress;
video.currentTime = currentTime;
video.pause();
video.removeEventListener('timeupdate', onTimeupdate);
updateRangeColor();
});
// 监听滑块拖动结束
rangeInput.addEventListener("change", function () {
console.log("changechange");
isDragging = false;
video.addEventListener("timeupdate", onTimeupdate);
video.play();
});
function groupByFlag(obj) {
const result = [];
let startTime = 0;
for (let i = 1; i < Object.keys(obj).length; i++) {
if (obj[i] !== obj[i - 1]) {
result.push({ startTime, endTime: i - 1, flag: obj[i - 1] });
startTime = 0;
}
}
result.push({
startTime,
endTime: Object.keys(obj).length - 1,
flag: obj[Object.keys(obj).length - 1],
});
return result;
}
// 根据记录更细滑块的背景色,感觉有点问题,有css大佬可以指点下
function updateRangeColor() {
myArray1 = Object.keys(section).map(function (key) {
return section[key];
});
let myArray = groupByFlag(section).filter(v => v.flag !== undefined);
const colorSpan = myArray.length > 0 ? `linear-gradient(90deg,
${myArray.map((k, i) => {
if (k.flag) {
if (i === 0 && k.startTime === 0) {
return `${GREEN} 0%, ${GREEN} ${(k.endTime / (myArray1.length - 1)) * 100}%, ${BLUE} ${(k.endTime / (myArray1.length - 1)) * 100}%`;
}
return `${BLUE} ${(k.startTime / (myArray1.length - 1)) * 100}%, ${GREEN} ${ (k.startTime / (myArray1.length - 1)) * 100 }%, ${GREEN} ${(k.endTime / (myArray1.length - 1)) * 100}%, ${BLUE} ${ (k.endTime / (myArray1.length - 1)) * 100 }%`;
} else {
if (i === 0 && k.startTime === 0) {
return `${BLUE} 0%, ${BLUE} ${(k.endTime / (myArray1.length - 1)) * 100}%, ${GREEN} ${(k.endTime / (myArray1.length - 1)) * 100}%`;
}
return `${GREEN} ${(k.startTime / (myArray1.length - 1)) * 100}%, ${BLUE} ${ (k.startTime / (myArray1.length - 1)) * 100 }%, ${BLUE} ${(k.endTime / (myArray1.length - 1)) * 100}%, ${GREEN} ${ (k.endTime / (myArray1.length - 1)) * 100 }%`;
}
})}` : `linear-gradient(90deg, blue 100%)`;
rangeInput.style.background = colorSpan;
watch.innerText = watchProcess() * 100 + '%';
}
function watchProcess() {
let race = 0;
myArray1.forEach(item => {
if (item) race += 1;
})
return (race / (myArray1.length - 1)).toFixed(2);
}
function inputChange(e) {
console.log(e.target.value);
}
总结
上面实现了一个简单的基于原生video的具有动态颜色的进度条,可以有效地记录在视频中的观看进度。可以在上面的基础上拓展:
比如原生标签用其他的播放器代替(DPlayer等),可能相应的监听时间需要做修改。
比如将记录通过后台接口将记录写入在数据库,下次播放时读取记录继续播放等。