HTML <video> 元素用于在HTML或者XHTML文档中嵌入媒体播放器,用于支持文档内的视频播放。

  • currentTime:读取 CurentTime 返回一个双精度浮点值,指示以秒为单位的媒体的当前播放位置。如果video尚未开始播放,则会在开始播放后返回偏移量。通过 CurentTime 将当前播放位置设置为给定时间,会在加载媒体时将媒体查找到该位置(从指定的位置开始播放)。* duration(只读):一个双精度浮点值,它指示媒体的持续时间(总长度),以秒为单位,在媒体的时间线上。* volume:音量* playbackrate:播放速度* play():播放视频* pause():暂停视频更多属性:developer.mozilla.org/zh-CN/docs/…

实现思路:

1.播放 / 暂停

// 获取 dom 节点
const player = document.querySelector('.player')
const video = player.querySelector('.viewer')
const toggle = player.querySelector('.toggle')

当 video 标签和 控制按钮被点击时,切换播放状态

video.addEventListener('click', togglePlay)
toggle.addEventListener('click', togglePlay)

判断 video 的状态,如果当前是暂停,则调用 video 的 play。

// 切换播放状态
function togglePlay() {const method = video.paused ? 'play' : 'pause'video[method]()
}

2.切换播放状态图标,对 video 进行播放状态的监听

video.addEventListener('play', updatedToggle)
video.addEventListener('pause', updatedToggle)
// 切换播放状态图标
function updatedToggle() {const icon = video.paused ? '►' : '❚ ❚'toggle.textContent = icon
}

3.实现前进后退功能

获取页面上的两个前进后退按钮,为其添加 click 事件。

const skipButtons = player.querySelectorAll('[data-skip]')skipButtons.forEach(skipButton => skipButton.addEventListener('click', skip))

获取 skip 按钮上的自定义属性,改变 video 的 currentTime

function skip() {video.currentTime += parseFloat(this.dataset.skip)
}

4.改变播放速度和音量

获取页面上控制音量和播放速度的两个按钮,为其添加 change 事件和 mousemove 事件,实现点击或拖动改变的效果。

const ranges = player.querySelectorAll('.player__slider')ranges.forEach(range => range.addEventListener('change', handleRangeUpdate))
ranges.forEach(range => range.addEventListener('mousemove', handleRangeUpdate))// 改变播放速度和音量
function handleRangeUpdate() {video[this.name] = this.value
}

5.实现播放进度条的颜色移动

const progressBar = player.querySelector('.progress__filled')

监听 video 标签的 timeupdate

video.addEventListener('timeupdate', handleProgress)

用当前播放的时间 video.currentTime 除以视频的总时长 video.duration

// 改变进度条
function handleProgress() {const percent = (video.currentTime / video.duration) * 100;progressBar.style.flexBasis = `${percent}%`;
}

6.拖动进度条,视频切换到对应的播放时间

const progress = player.querySelector('.progress')

需要一个变量,控制切换是否生效

let mousedown = false

监听 progress 的 clickmousemovemousedownmouseup 四个事件

let mousedown = false
progress.addEventListener('click', scrub)
progress.addEventListener('mousemove', (e) => mousedown && scrub(e))
progress.addEventListener('mousedown', () => mousedown = true)
progress.addEventListener('mouseup', () => mousedown = false)

// 点击切换播放进度
function scrub(e) {const scrubTime = (e.offsetX / progress.offsetWidth) * video.durationvideo.currentTime = scrubTime
}
基本思路:

隐藏原生 video 控件,实现原生控件的功能。

<ControlsprogressEl={progressEl}volumeEl={volumeEl}controls={controls}isPlaying={isPlaying}volume={volume}currentTime={currentVideoTime}duration={videoDuration}muted={muted}markers={markers}onPlayClick={play}onPauseClick={pause}onProgressClick={handleProgressClick}onVolumeClick={handleVolumeClick}onMuteClick={handleMuteClick}onFullScreenClick={handleFullScreenClick}onMarkerClick={handleMarkerClick}
/>

传入的 controls 的是控件的数量,通过该数组来控制显示。

播放 / 暂停

{controls.includes('play') ? (<buttonclassName={isPlaying ? 'pause' : 'play'}onClick={isPlaying ? onPauseClick : onPlayClick}>{isPlaying ? 'Pause' : 'Play'}</button>
) : null}

显示播放时间

对传入的时间做一些格式化操作

const getTimeCode = (secs: number): string => {let secondsNumber = secs ? parseInt(String(secs), 10) : 0;let hours = Math.floor(secondsNumber / 3600);let minutes = Math.floor((secondsNumber - hours * 3600) / 60);let seconds = secondsNumber - hours * 3600 - minutes * 60;let hoursStr: string = String(hours);let minutesStr: string = String(minutes);let secondsStr: string = String(seconds);if (hours < 10) {hoursStr = '0' + hours;}if (minutes < 10) {minutesStr = '0' + minutes;}if (seconds < 10) {secondsStr = '0' + seconds;}return `${hoursStr !== '00' ? hoursStr + ':' : ''}${minutesStr}:${secondsStr}`;
};const durationTimeCode = getTimeCode(Math.ceil(duration));
const currentTimeCode =currentTime !== duration ? getTimeCode(currentTime) : durationTimeCode;
{controls.includes('time') ? (<div className="time">{currentTimeCode}/{durationTimeCode}</div>
) : null}

进度条组件,通过 ref 向父组件暴露子组件的引用。Marker 是标记点组件,接收传入的数组,向外暴露 onMarkerClick 事件。

{controls.includes('progress') ? (<div className="progress-wrap"><progress ref={progressEl} max="100" onClick={onProgressClick}>0% played</progress>{markers &&markers.map((marker, index) => {return (<Markerkey={index}marker={marker}duration={duration}onMarkerClick={onMarkerClick}/>);})}</div>
) : null}

Marker 组件本质上是有 i 标签组成的一个个小点,通过传入的 duration 和 time 计算出 position 的值。

if (duration) {const percent = time <= duration ? time / duration : 1;return `calc(${percent * 100}% - 2px)`;
}
<iid={id}className="react-video-marker"title={title}style={{background: color,left: getPosition()}}onClick={() => {onMarkerClick(marker);}}
/>

控制音量组件

{controls.includes('volume') ? (<div className="volume-wrap"><progressref={volumeEl}max="100"value={volume * 100}onClick={onVolumeClick}>{volume * 100}% volume</progress><buttonclassName={muted ? 'no-volume' : 'volume'}onClick={onMuteClick}>Volume</button></div>
) : null}

全屏组件

{controls.includes('full-screen') ? (<button className="full-screen" onClick={onFullScreenClick}>FullScreen</button>
) : null}

进度条事件如下:

  • play(点击播放)
  • pause(点击暂停)
  • handleProgressClick(进度条点击事件)
  • handleVolumeClick(改变音量事件)
  • handleMuteClick(点击静音事件)
  • handleFullScreenClick(点击全屏事件)
  • handleMarkerClick(标记点事件)

Controls 组件通过 isPlaying 来判断当前的播放状态。当触发 play 和 pause 事件时,应当改变 isPlaying 的值。

play 和 pause

// play
const play = () => {if (!videoRef.current) return;const playPromise = videoRef.current.play();playPromise &&playPromise.then(() => { }).catch(e => {console.log('Operation is too fast, audio play fails');});setIsPlaying(true);
};// pause
const pause = () => {if (!videoRef.current) return;videoRef.current.pause();setIsPlaying(false);
};

handleProgressClick

点击进度条,期望就是 video 的时间可以到达点击的位置。

  • e.clientX:鼠标相对于浏览器窗口可视区域的X,Y坐标,可视区域不包括工具栏和滚动条。
  • element.getBoundingClientRect():返回的结果是包含完整元素的最小矩形,并且拥有left, top, right, bottom, x, y, width, 和 height这几个以像素为单位的只读属性用于描述整个边框。除了widthheight 以外的属性是相对于视图窗口的左上角来计算的。

  • 获取当前页面滚动条横坐标的位置:document.body.scrollLeft

const handleProgressClick = (e: Event) => {const x =e['clientX'] -progressEl.current!.getBoundingClientRect().left +document.body.scrollLeft;const percentage =(x * progressEl.current!.max) / progressEl.current!.offsetWidth;videoRef.current!.currentTime =(percentage / 100) * videoRef.current!.duration;
}; (percentage / 100) * videoRef.current!.duration; };

handleVolumeClick

音量的改变与进度条同理。当改变的音量的时候,静音状态应该设置为 false。

const handleVolumeClick = e => {const y =volumeEl.current!.offsetWidth -(e.clientY -volumeEl.current!.getBoundingClientRect().top +document.body.scrollTop);const percentage =(y * volumeEl.current!.max) / volumeEl.current!.offsetWidth;videoRef.current!.muted = false;const volume = videoRef.current.volume;onVolumechange?.(volume);setVolume(percentage / 100);
};

handleMuteClick,设置 muted 的状态

const handleMuteClick = () => {if (muted) {videoRef.current!.muted = false;setVideoMuted(false);} else {videoRef.current!.muted = true;setVolume(0);setVideoMuted(true);}
};

handleFullScreenClick

通过 isFullScreen 来控制是否全屏。点击全屏的时候,调用当前元素上的 requestFullscreen 方法,退出全屏时,调用 document 的 exitFullscreen 方法。

const handleFullScreenClick = () => {const videoWrap = document.getElementsByClassName('react-video-wrap')[0];if (isFullScreen) {document.body.classList.remove('react-video-full-screen');if (document['exitFullscreen']) {document['exitFullscreen']();} else if (document['mozCancelFullScreen']) {document['mozCancelFullScreen']();} else if (document['webkitExitFullscreen']) {document['webkitExitFullscreen']();} else if (document['msExitFullscreen']) {document['msExitFullscreen']();}} else {document.body.classList.add('react-video-full-screen');if (videoWrap['requestFullscreen']) {videoWrap['requestFullscreen']();} else if (videoWrap['mozRequestFullScreen']) {videoWrap['mozRequestFullScreen']();} else if (videoWrap['webkitRequestFullscreen']) {videoWrap['webkitRequestFullscreen']();} else if (videoWrap['msRequestFullscreen']) {videoWrap['msRequestFullscreen']();}}setIsFullScreen(!isFullScreen);
};

handleMarkerClick

点击标记点,进度条到达标记点的时间

const handleMarkerClick = (marker: object) => {videoRef.current!.currentTime = marker['time'];onMarkerClick(marker);
};

最后

最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

基于 React video 视频打点相关推荐

  1. 基于 React hooks + Typescript + Cesium 实现视频融合功能

    文章目录 效果截图 功能介绍 实现思路 实现步骤 创建 Camera 作为观察点 创建 ShadowMap 创建视椎体 后期处理 PostProcessStage 着色器代码 使用 CesiumVid ...

  2. b站React禹哥版视频笔记-React应用(基于react脚手架)

    目录 前言 一.初始化react脚手架 1.react脚手架: 2.创建项目并启动 二.脚手架文件介绍 1.public 2.src 三.一个简单的Hello组件 四.样式的模块化 五.vscode中 ...

  3. 基于相位的视频运动检测(Phase-Based Video Motion Processing)

    基于相位的视频运动检测 基本原理 在基于相位的光流中,发现时空带通滤波后的视频的相位梯度可以进行运动估计.利用相位与运动之间的这种关联,可以通过直接增大相位来增强视频中的运动. 算法框图 算法步骤 1 ...

  4. 如何使用React创建视频和动画

    点击上方关注 TianTianUp,一起学习,天天进步 大家好,我是小弋. 分享的内容是: 如何使用 React Remotion 来创建视频的,如果你之前对视频很感兴趣的话,这篇文章可以参考. 正文 ...

  5. CVPR2021 | 基于transformer的视频实例分割网络VisTR

    原文:End-to-End Video Instance Segmentation with Transformers 翻译:夏初 摘要: 视频实例分割(VIS)是一项需要同时对视频中感兴趣的对象实例 ...

  6. 重磅开源!首个基于Transformer的视频理解网络来啦!

    部分转载自:机器之心  |  编辑:小舟.陈萍 Facebook AI 提出新型视频理解架构:完全基于Transformer,无需卷积,训练速度快.计算成本低.最近由Facebook提出的首个完全基于 ...

  7. OpenCV + python 实现人脸检测(基于照片和视频进行检测)

    OpenCV + python 实现人脸检测(基于照片和视频进行检测) Haar-like 通俗的来讲,就是作为人脸特征即可. Haar特征值反映了图像的灰度变化情况.例如:脸部的一些特征能由矩形特征 ...

  8. 3d max用不同目标做关键帧_基于光流的视频目标检测系列文章解读

    作者:平凡的外卖小哥 全文5747字,预计阅读时间15分钟 1 简介 目前针对于图片的目标检测的方法大致分为两类: faster R-CNN/R-FCN一类: 此类方法在进行bbox回归和分类之前,必 ...

  9. 轻量高效!清华智能计算实验室开源基于PyTorch的视频 (图片) 去模糊框架SimDeblur

    作者丨科技猛兽 编辑丨极市平台 清华大学自动化系智能计算实验室团队开源基于 PyTorch 的视频 (图片) 去模糊框架 SimDeblur. 基于 PyTorch 的视频 (图片) 去模糊框架 Si ...

最新文章

  1. Python3 websocket通信
  2. 体积小巧、功能强大的代理工具 -- 3proxy
  3. 福师计算机导论在线作业一,福师《计算机导论》在线作业一..doc
  4. script runat=server,%%区别
  5. Android Scroller完全解析,关于Scroller你所需知道的一切
  6. 基于堆栈的虚拟机实现
  7. OpenCV-Python 中文教程(搬运)目录
  8. linux的基础知识——多线程gdb调试
  9. 机器学习算法(8)——朴素贝叶斯、最小风险贝叶斯决策
  10. 单载波DSP模块介绍
  11. java面试题_2020年JAVA最新大厂面试题!
  12. spring学习笔记四(注入Bean属性)
  13. java模块化发布选型_Java模块化开发
  14. Docker环境搭建,K8s
  15. 外贸建站前必做的SEO优化?
  16. EXCEL 制作万年历
  17. Java equalsIgnoreCase() 方法
  18. 44特征02——相似对角化与方幂、代数重数与几何重数、可对角化的概念、相似对角化的条件、矩阵方幂的计算
  19. 图神经网络之Node2Vec详解
  20. 最新MySql安装教学,非常详细

热门文章

  1. 谈谈对测试驱动开发思想的体会
  2. ubuntu18安装office2010
  3. 计算机c盘变大,关于C盘可用空间忽大忽小的问题 变化很快 幅度很大~~~
  4. Python3,为了考研,我用了9.9行代码,搞出一个GUI翻译器,从此再也不用百度翻译了。
  5. localhost(操作系统有默认值与其对应,所以不必一定在hosts文件里明写)
  6. Java去除字符串中空格的方法详解
  7. ios项目传到github_使用GitHub Actions将iOS应用程序部署到TestFlight或App Store
  8. 边缘检测,Roberts 算子,Sobel算子,Canny 算子
  9. FPGA数字信号处理(十六)单级CIC滤波器Verilog设计
  10. 在Win7下通过SecureCRT 远程配置DynamipsGUI中的路由器--转载