H5纯原生播放器 【学习video】

article/2025/9/21 20:02:31

这是一个纯原生的H5播放器,尽管网上有很多第三方库,但是基础打的牢固,一定会帮你走的更远。

大厂也非常重视基础,再说了那些第三方库也是基础一点点搭起来的,所以有兴趣学习的同学可以下载来学习。

代码中的细节我都写了注释了。

也非常欢迎大家进行拓展,有兴趣拓展功能,可以另起一个分支,最好写个文档,说明一下添加了哪些功能。

这是用H5的video标签做的视频播放器

只是学习用的,所以已有的功能可能存在问题,当然功能肯定是不完备的。

但作为学习H5的video,应该是可以的。

index.html是我一开始写播放器的代码

core是我把代码分开的后文件夹。

写好的功能有

  • 播放和暂停
  • 进度条动画,进度条拖拽
  • 播放时间
  • 音量调控
  • 倍数播放
  • 全屏

当然并不是这样就OK了,大家可以在已有的基础上进行添加,一方面可以锻炼自己的思维,另一方面可以磨练自己的代码阅读能力。

我想到的功能

  • 播放状态下,当鼠标悬停太久或移出播放器一段时间,控制器应该消失。
  • 网页全屏
  • 清晰度调节
  • 点击进度条跳转
  • 弹幕
  • 键盘事件,用键盘调整进度和音量

gitee

效果
在这里插入图片描述

如果不想下载文件的话,可以看核心代码
核心代码

// 传入视频资源路径,返回一个videoWrapperDOM
function createVideo({videoUrl,width,height,dragSvg = ""
}) {const videoWrapperDOM = initWrapperDOM(width, height);const video = document.createElement('video');video.classList.add("own-video")video.src = videoUrl;videoWrapperDOM.appendChild(video);function initWrapperDOM(width, height) { // 初始化外层DOMconst videoWrapperDOM = document.createElement("div");videoWrapperDOM.classList.add("own-video-wrapper")videoWrapperDOM.style.width = width + "px";videoWrapperDOM.style.height = height + "px";return videoWrapperDOM;}let timeFrameId = null; // 播放时间展示动画const videoControl = {paused: true,//那个video自带的那个还不是好用videoDOM: null,play(playBtn) {this.videoDOM.play();updateTime();playBtn.classList.remove("pause");playBtn.classList.add("play");this.paused = false;},pause(playBtn) {this.videoDOM.pause();cancelAnimationFrame(timeFrameId);playBtn.classList.remove("play");playBtn.classList.add("pause");this.paused = true;},setVolume(v) {v = v < 0 ? 0 : v;this.videoDOM.volume = v;},w: 960, // 最开始播放器的大小h: 540,setVideoDOM(video, w, h) {// 看似很奇怪// 这个应该分一个模块,然后私有化,对外暴露方法,但作为练习我就没那么做了this.videoDOM = video;this.w = w;this.h = h;this.setVolume(.5);},setFullScreen(isFullScreen) {if (isFullScreen) {this.videoDOM.parentNode.classList.add("video-full-screen");} else {this.videoDOM.parentNode.classList.remove("video-full-screen");}}}videoControl.setVideoDOM(video, width, height);let playBtn = document.createElement("div");function createPlayBtn(video) {// 播放按钮playBtn.classList.add("btn", "play-btn", "pause");playBtn.addEventListener("click", function () {if (videoControl.paused) {// 视频状态暂停播放console.log("paused");videoControl.play(this);} else {console.log("play");videoControl.pause(this);}})return playBtn;}const current = document.createElement("span"); // 当前播放时间const total = document.createElement("span"); // 总时间function updateTime() {const totalTime = parseTime(video.duration);const currentTime = parseTime(video.currentTime);current.innerText = currentTime;total.innerText = totalTime;timeFrameId = requestAnimationFrame(updateTime);}function createTimeDisplay(video) {// 01:00 / 5:20const timeDisplayWrapper = document.createElement("div");timeDisplayWrapper.classList.add("time-wrapper")timeDisplayWrapper.appendChild(current);timeDisplayWrapper.appendChild(total);video.addEventListener("canplaythrough", function () {// 视频加载完成更新最新的播放时间const totalTime = parseTime(video.duration);const currentTime = parseTime(video.currentTime);current.innerText = currentTime;total.innerText = totalTime;})return timeDisplayWrapper}function exitFullscreen() {if (document.exitFullscreen) {document.exitFullscreen();} else if (document.msExitFullscreen) {document.msExitFullscreen();} else if (document.mozCancelFullScreen) {document.mozCancelFullScreen();} else if (document.webkitExitFullscreen) {document.webkitExitFullscreen();}}function createFullScreen() {const fullScreenWrapper = document.createElement("div");fullScreenWrapper.classList.add("full-screen-wrapper");const arr = ["全屏", "小屏"];fullScreenWrapper.innerText = arr[0];let status = "normal"; // 浏览器的一个状态 full normaldocument.addEventListener("fullscreenchange", function () {console.log(document.fullscreenElement);if (document.fullscreenElement !== null) {// 进入全屏模式status = "full";} else {status = 'normal';exitFullscreen();videoControl.setFullScreen(false);fullScreenWrapper.innerText = arr[0];}});fullScreenWrapper.addEventListener("click", function () {if (status === 'normal') {document.documentElement.requestFullscreen();videoControl.setFullScreen(true);this.innerText = arr[1];}if (status === 'full') {exitFullscreen();videoControl.setFullScreen(false);this.innerText = arr[0];}})return fullScreenWrapper;}function createVolumeWrapper(video) {const volumeWrapper = document.createElement('div');const volumeIco = document.createElement("div");const volumeBarWrapper = document.createElement('div');volumeWrapper.classList.add("volume-wrapper");volumeIco.classList.add("volume-ico");volumeBarWrapper.classList.add("volume-bar-wrapper");const bgBar = document.createElement("div");bgBar.classList.add("volume-bg-bar");const innerBar = document.createElement("div");innerBar.classList.add("volume-inner-bar");const circle = document.createElement("span");circle.classList.add("volume-circle");volumeBarWrapper.appendChild(bgBar);bgBar.appendChild(innerBar);bgBar.appendChild(circle);volumeWrapper.appendChild(volumeIco);volumeWrapper.appendChild(volumeBarWrapper);let clicked = false;let clientY, clickY;let barHeight = 0;circle.addEventListener("mousedown", function (e) {barHeight = bgBar.clientHeight; // 最大拖拽范围// 在父元素display:none,尺寸不准确clicked = true; // 代表可以进行拖拽了clickY = e.pageYclientY = innerBar.clientHeight;})volumeWrapper.addEventListener("mousemove", function (e) {if (!clicked) {return; // 点击之后才能进行拖拽}const moveY = clickY - e.pageY; // 这条公式要注意一下// 因为这个相比进度条拖拽它就是一个单向动画,只需要计算出当前的音量大小就好了let h = clientY + moveY;h = h < 0 ? 0 : h; // 边界处理h = h > barHeight ? barHeight : h;innerBar.style.height = h + "px";circle.style.bottom = h + "px";const v = h / barHeight; // 音量 // 为了可阅读性,我尽量没有都挤在一行写,这是一个人的职业素养videoControl.setVolume(v);})const stopDragVolumeBar = function () {if (clicked) {// 正在拖拽clicked = false; // 取消拖拽状态}}document.addEventListener("mouseup", stopDragVolumeBar)volumeWrapper.addEventListener("mouseleave", stopDragVolumeBar);return volumeWrapper;}function createDragBar(video, dragSvg) {// 播放进度条const barWrapper = document.createElement("div");barWrapper.classList.add("bar-wrapper");const innerBar = document.createElement("div");innerBar.classList.add("inner-bar");barWrapper.appendChild(innerBar);const circle = document.createElement("span");circle.classList.add("circle");if (dragSvg) {// 自定义svg拖拽图标circle.innerHTML = dragSvg;circle.classList.add("svg-circle")} else {circle.classList.add("normal");}window.circle = circle;barWrapper.appendChild(circle);let frameId = null; // 拖拽的时候需要取消动画// 拖拽功能// 我需要知道总的进度条的长度// 还需要知道拖动的距离// 拖动的距离 / 总的进度条长度 = currentTime / totalTimelet totalWidth = 0;let totalTime = 0;video.addEventListener("canplaythrough", function () {// 视频资源加载完成totalTime = this.duration;let clicked = false;let clientX, clickX;circle.addEventListener("mousedown", function (e) {// 如果获取元素属性为最开始,那么全屏缩放会出现BUGtotalWidth = barWrapper.offsetWidth; // 为了适应元素的最新大小// 暂停播放// 点击时记住点击瞬间的位置cancelAnimationFrame(frameId); // 取消播放动画 优化性能减少不必要的麻烦// 不然你操控不了元素videoControl.pause(playBtn);clientX = this.offsetLeft;clickX = e.pageX;clicked = true; // 进入拖拽状态})document.addEventListener("mousemove", function (e) {if (!clicked) {return; // 非拖拽状态}cancelAnimationFrame(frameId);let moveX = e.pageX - clickX;let per = (clientX + moveX) / totalWidth;per = per < 0 ? 0 : per; // 做边界处理per = per > 1 ? 1 : per;per = per * 100 + "%";circle.style.left = per;innerBar.style.width = per;})document.addEventListener("mouseup", function (e) {if (clicked) {clicked = false;let moveX = e.pageX - clickX;let per = (clientX + moveX) / totalWidth;video.currentTime = getCurrentTime(video, per);per = per > 1 ? 1 : per;per = per < 0 ? 0 : per;per = per * 100 + "%";circle.style.left = per;innerBar.style.width = per;videoControl.play(playBtn);barAnimate();}})})function barAnimate() {const per = getPercentage(video) * 100 + "%";circle.style.left = per;innerBar.style.width = per;frameId = requestAnimationFrame(barAnimate);}barAnimate();return barWrapper;}function createPlayRate(video) {// 倍速播放const rateWrapper = document.createElement("div");rateWrapper.classList.add("rate-wrapper");const currentRateWrapper = document.createElement('div');currentRateWrapper.innerText = "1X";const ul = document.createElement("ul");ul.classList.add("rateSelectorWrapper");ul.innerHTML = `<li>2X</li><li>1.5X</li><li>1X</li><li>.5X</li>    `ul.addEventListener("click", function (e) {if (e.target.tagName === "LI") {currentRateWrapper.innerText = e.target.innerText;const rate = parseFloat(e.target.innerText);video.playbackRate = rate;}})rateWrapper.appendChild(currentRateWrapper);rateWrapper.appendChild(ul);return rateWrapper;}function initBar() {const bar = document.createElement("div");bar.classList.add("own-video-control-bar");return bar;}const bar = initBar();const timeDisplayWrapper = createTimeDisplay(video);const dragBar = createDragBar(video, dragSvg);const rateWrapper = createPlayRate(video);// 为了将右边的按钮聚起来而已,我觉得那样会更好看const container = document.createElement("div");container.classList.add("video-btn-container");container.appendChild(rateWrapper);container.appendChild(createVolumeWrapper(video));container.appendChild(createFullScreen());bar.appendChild(createPlayBtn(video));bar.appendChild(timeDisplayWrapper);bar.appendChild(dragBar);bar.appendChild(container);videoWrapperDOM.appendChild(bar);return videoWrapperDOM;
}

http://chatgpt.dhexx.cn/article/UOZMimuW.shtml

相关文章

MuiPlayer - 一款优秀的 H5 视频播放器框架

Mui Player Gitee | Docs | 中文文档 介绍 MuiPlayer 是一款 HTML5 视频播放插件&#xff0c;其默认配置了精美可操作的的播放控件&#xff0c;涉及了常用的播放场景&#xff0c;例如全屏播放、播放快进、循环播放、音量调节、视频解码等功能。 支持 mp4、m3u8、flv 等多…

[h5]一个基于HTML5实现的视频播放器代码详解

什么是 HTML5&#xff1f; HTML5 是最新的 HTML 标准。 HTML5 是专门为承载丰富的 web 内容而设计的&#xff0c;并且无需额外插件。 HTML5 拥有新的语义、图形以及多媒体元素。 HTML5 提供的新元素和新的 API 简化了 web 应用程序的搭建。 HTML5 是跨平台的&#xff0c;被…

一个牛逼的开源 H5 视频播放器

【公众号回复 “1024”&#xff0c;免费领取程序员赚钱实操经验】 大家好&#xff0c;我是章鱼猫。今天给大家分享的这个开源项目&#xff0c;对于前端开发者来讲非常非常的有用。因为它是一个 H5 的视频播放器。 字节跳动出品&#xff0c;必属精品啊&#xff01;尤其是我感觉在…

H5移动端页面使用DPlayer视频播放器

背景&#xff1a; 如果使用原生video标签&#xff0c;那在不同类型手机浏览器上样式都不一样&#xff0c;而且播放表现不一样&#xff0c;比如使用css隐藏播放按钮在PC端有效&#xff0c;在手机端就无效&#xff0c;故我们选择引入第三方成熟的播放器。 需求&#xff1a; 1、自…

13款用于Web的流行HTML5视频播放器

​​​​​​当视频流媒体席卷通信世界&#xff0c;为了保持和提升用户增长&#xff0c;内容创造者和流媒体服务提供者需要提供高质量的用户体验。而想要实现这一点&#xff0c;他们都需要在自己的网站上内嵌HTML5视频播放器。 在本文中&#xff0c;我们将来了解一下现在市面上…

分位数回归的实现方法

目录 分位数回归简介实现方法参考文献 分位数回归简介 简介参照可参照参考文献【1】。如下图&#xff0c;散点图代表我们所需分析数据&#xff0c;若用简单的参数方程拟合&#xff0c;即只利用期望值&#xff0c;会损失很多数据特征。因此分位数回归就可以乘风破浪了。 分位数…

分位数回归(Quantile Regression)代码解析

实验代码 本文采用python sklearn库中&#xff0c;作为quantile regression的示例代码。以下为详细解析&#xff1a; import numpy as np import matplotlib.pyplot as pltfrom sklearn.ensemble import GradientBoostingRegressor %matplotlib inline np.random.seed(1) #设…

python中的分位数回归(初探)

分位数回归 参考文献 Python statsmodels 介绍 - 树懒学堂 (shulanxt.com) Quantile Regression - IBM Documentation https://www.cnblogs.com/TMesh/p/11737368.html 传统的线性回归模型 其的求解方式是一个最小二乘法&#xff0c;保证观测值与你的被估值的差的平方和应…

分位数回归 Quantile Regression,python 代码

偶尔在机器学习的论文中了解到了分位数回归&#xff0c;发现这个方法应用也满广的。 文章目录 1. 分位数回归的数学原理2. 分位数回归的求解原理3 python 分位数回归 1. 分位数回归的数学原理 一般的回归方法是最小二乘法&#xff0c;即最小化误差的平方和&#xff1a; min ⁡…

实证操作:R语言实现分位数回归的介绍

“分位数回归是估计一组回归变量X与被解释变量Y的分位数之间线性关系的建模方法。从最小二乘法可以看出,传统回归中最小化残差的平方极易受极端值的影响,而且属于均值回归,这种方法不能得到不同分布下的数据关系 导入程序包与数据 分位数回归不考虑同方差、正态分布的假设,…

分位数回归

分位数&#xff08;Quantile&#xff09;&#xff0c;亦称分位点&#xff0c;是指将一个随机变量的概率分布范围分为几个等份的数值点&#xff0c;常用的有中位数&#xff08;即二分位数&#xff09;、四分位数、百分位数等。 任意一个累计分布函数 F ( x ) F(x) F(x) &#…

多元线性模型的分位数回归

多元线性模型的分位数回归 一、为什么要使用分位数回归&#xff1f;二、分位数回归基本模型三、分位数回归估计--线性规划3.1损失函数3.2目标函数3.3线性规划3.4回归算法 四、实际案例分析与python编程计算4.1引入数据集4.2计算 β ^ \widehat{\beta} β ​ 五、参考文献 一、为…

R语言的分位数回归

回归是科研中最常见的统计学研究方法之一&#xff0c;在研究变量间关系方面有着极其广泛的应用。由于其基本假设的限制&#xff0c;包括线性回归及广义线性回归在内的各种常见的回归方法都有三个重大缺陷&#xff1a;(1)对于异常值非常敏感&#xff0c;极少量的异常值可能导致结…

分位数回归--基于R

分位数回归 分位数回归是估计一组回归变量X与被解释变量Y的分位数之间线性关系的建模方法。以往的回归模型实际上是研究被解释变量的条件期望。而人们也关心解释变量与被解释变量分布的中位数、分位数呈何种关系。它最早由Koenker和Bassett(1978)提出。OLS回归估计量的计算是基…

基于R语言的分位数回归(quantile regression)

分位数回归&#xff08;quantile regression&#xff09; 这一讲&#xff0c;我们谈谈分位数回归的知识&#xff0c;我想大家传统回归都经常见到。分位数回归可能大家见的少一些&#xff0c;其实这个方法也很早了&#xff0c;大概78年代就有了&#xff0c;但是那个时候这个理论…

分位数回归和stata

分位数回归与stata 找了半天也没找到我想看的那种完整点的stata教程&#xff0c;只好自己写一个了… 参考教材&#xff1a;《用STATA学微观计量经济学》&《高级计量经济学及STATA应用_第2版》 第一部分 纯理论 更加详细的百度吧&#xff0c;很长很长&#xff0c;这里主要是…

分位数回归的求解

分位数回归 分位数回归实际上是一种特殊的 ℓ 1 \ell_1 ℓ1​回归问题&#xff0c;特别地&#xff0c;当所求分位数 τ 0.5 \tau0.5 τ0.5时就是中位数回归。 1 线性规划 1.1 将分位数回归看做是线性规划问题来求解 一般的&#xff0c;线性回归问题可以写为 ℓ p \ell_p ℓ…

分位数回归模型学习笔记

我读硕士老师给我的第一篇论文就是一个分位数回归的文章&#xff0c;当时觉得这个模型很简单&#xff0c;我很快就用R的示例文件写了一个例子&#xff0c;但是&#xff0c;在后面的研究中&#xff0c;我越来越觉得&#xff0c;这个模型没有我想的那么简单&#xff0c;而且有着非…

Python统计学11——分位数回归

分位数回归也是数理统计里面经典的模型&#xff0c;他相对于在最小二乘模型上进行了改进&#xff0c;虽然本身还是线性的参数模型&#xff0c;但对损失函数进行了改进。我们都知道最小二乘的损失函数是均方误差最小&#xff0c;分位数的损失函数是&#xff1a; 可以看到分位数损…

分位数回归(quantile regression)R实现

分位数回归&#xff08;quantile regression&#xff09;R实现 一、基本介绍二、使用分位数回归的原因三、R 语言实现分位数回归 一、基本介绍 回归分析的主要目的&#xff1a;实证检验理论分析中因变量与自变量之间的关系。传统的均值回归&#xff0c;主要使用因变量的条件均…