Day 11 - Custom HTML5 Video Player

前言

JS 30 是由加拿大的全端工程师 Wes Bos 免费提供的 JavaScript 简单应用课程,课程主打 No FrameworksNo CompilersNo LibrariesNo Boilerplate 在30天的30部教学影片里,建立30个JavaScript的有趣小东西。

另外,Wes Bos 也很无私地在 Github 上公开了所有 JS 30 课程的程序码,有兴趣的话可以去 fork 或下载。


本日目标

灵活运用 video 元素的相关属性、方法,实作出一个拥有快进快退播放速度倍率控制音量大小拖拉时间轴功能的简易影片播放器。


解析程序码

HTML 部分

.player 代表整个影片播放器,包含影片(.player__video)播放控制列(.player__controls)两部分。

播放控制列内部又可细分为四个部分:

1. 影片播放的时间轴 : .progress.progress__filled
2. 播放/暂停钮 : .toggle
3. 音量/播放速度倍率 : .player__slider
4. 快进/快退 : .player__button
<div class="player">
     <video class="player__video viewer" src="652333414.mp4"></video>

     <div class="player__controls">
       <div class="progress">
        <div class="progress__filled"></div>
       </div>
       <button class="player__button toggle" title="Toggle Play">►</button>
       <input type="range" name="volume" class="player__slider" min="0" max="1" step="0.05" value="1">
       <input type="range" name="playbackRate" class="player__slider" min="0.5" max="2" step="0.1" value="1">
       <button data-skip="-10" class="player__button">« 10s</button>
       <button data-skip="25" class="player__button">25s »</button>
     </div>
</div>

JS 部分

首先,取得所有要用到 HTML 标签并放到对应宣告的常数中。

/*get element we need*/
const player = document.querySelector('.player');
const video = player.querySelector('.viewer');

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

const toggle = player.querySelector('.toggle');
const skipButtons = player.querySelectorAll('[data-skip]');
const ranges = player.querySelectorAll('.player__slider');

下面我们将各个播放器的功能一个个拆出来做 :

1. 影片的播放/暂停

功能目的 : 我们希望在点击影片或播放/暂停钮时,播放或暂停影片。

video(影片)、toggle(播放/暂停钮)上都注册click 事件监听器,触发事件後用togglePlay()进行处理。

togglePlay()里,我们宣告一个常数method,经过条件判断,当video.paused回传 true 则 method = play;当video.paused回传 false 则method = pause

这边有一个特殊的写法video[method]();。举例来说,当method = play则实际效果相当於video.play();

function togglePlay(){
    const method = video.paused ? 'play' : 'pause';
    video[method]();
}

/*控制影片的播放*/
video.addEventListener('click',togglePlay);
toggle.addEventListener('click',togglePlay);
2. 更新播放/暂停图示

功能目的 : 我们希望在影片播放/暂停的状态下,同步更新图示。

video(影片)上注册play 事件pause 事件两个监听器并都以updateButton()进行事件处理。

updateButton()里,我们宣告一个常数icon指定当影片处於暂停状态则icon = '►',接着利用toggle.textContent = icon修改按钮的图示,当影片处於播放状态的处理也是用一样的方式。

function updateButton(){
    const icon = this.paused ? '►' : '❚ ❚';
    toggle.textContent = icon;
}

/*让播放键的图示改变*/
video.addEventListener('play',updateButton);
video.addEventListener('pause',updateButton);
3. 影片的快进/快退

功能目的 : 我们希望在点击快进/快退按钮时,同步调整影片的时间轴。

skipButtons(快进、快退按钮)里的所有button都注册click 事件并以skip()进行事件处理。

skip()里,我们将video.currentTime(影片现在播放的时间点)加上我们要快进或快退的秒数。

由於video.currentTime本身是 float 型别,因此需要将this.dataset.skip(快进/快退的秒数)用parseFloat()转换成float型别之後再进行运算。

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

/*调整影片的快进和倒退*/
skipButtons.forEach(button => button.addEventListener('click',skip));
4. 调整影片的播放速度(倍率)、音量大小

功能目的 : 我们希望在滑鼠在倍率或音量条上移动改变数值时,同步反映到video(影片)上。

在速度倍率和音量条上都注册change 事件mousemove 事件,分别在数值改变和滑鼠拖曳时触发事件,之後用handleRangeUpdate()进行事件处理。

handleRangeUpdate()里,我们使用和之前一样的特殊语法video[this.name] = this.value;video的属性值进行调整。举例来说,如果this.name = volumethis.value = 0,则video[this.name] = this.value;的效果和video.volume = 0;一样。

function handleRangeUpdate(){
    video[this.name] = this.value;
}

/*调整影片的播放速度、音量*/
ranges.forEach(range => range.addEventListener('change',handleRangeUpdate));
ranges.forEach(range => range.addEventListener('mousemove',handleRangeUpdate));
5. 更新时间轴

功能目的 : 我们希望在影片播放的过程中,不断地更新时间轴。

video(影片)上注册timeupdate 事件的监听器,当影片的播放时间(currentTime)有变动就触发事件,之後用handleProgress()进行事件处理。

handleProgress()里,我们宣告常数percent并放入video.currentTime(影片现在时间)除以video.duration(影片的总长度)再乘以100得到的比例值。

接着用progressBar.style.flexBasis = `${percent}%`;,用percent指定时间轴的长度占比。

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

/*持续更新时间轴*/
video.addEventListener('timeupdate',handleProgress);
6. 用拖拉的方式移动时间轴

功能目的 : 我们希望按住滑鼠拖或点击时间轴的同时,更新video(影片)现在播放的时间。

宣告mousedown作为 flag 判断现在是否有按住滑鼠。

我们在progress注册click 事件mousemove 事件mousedown 事件mouseup 事件监听器。

触发click 事件时,我们可以直接就以scrub(e)进行事件的处理。

但在触发mousemove 事件时,我们需要先判断是否有按住滑鼠,所以要借助mousedown 事件mouseup 事件的帮忙,在mousedown 事件触发地当下将 flag(mousedown) 设为 true,反之触发mouseup 事件则将 flag(mousedown) 设为 false。最後用mousedown && scrub(e)判断是否执行scrub(e),只有当flag(mousedown) 是 true 的时候,才接着执行scrub(e)完成事件处理。

scrub(e)里,我们宣告常数scrubTime放入将滑鼠在元素内部的X座标(e.offsetX)除以时间轴的长度(progress.offsetWidth)再乘以影片长度(video.duration)所得到要前往的时间点。最後将影片现在的时间(video.currentTime)指定为要前往的时间点(scrubTime)。

function scrub(e){
    const scrubTime = (e.offsetX / progress.offsetWidth) * video.duration;
    video.currentTime = scrubTime;
}

/*拖拉时间轴*/
let mousedown = false;
progress.addEventListener('click',scrub);
progress.addEventListener('mousemove',(e)=> mousedown && scrub(e));
progress.addEventListener('mousedown',() => mousedown = true);
progress.addEventListener('mouseup',() => mousedown = false);
补充说明:

HTMLVideoElement继承自HTMLMediaElement所以一些video元素的属性都可以到HTMLMediaElement查询。

使用HTMLElement.offsetWidth所取得的元素(element)宽度包括透过 CSS 设定的width、border、padding等等...。

补充资料:

HTMLMediaElement
HTMLElement.dataset
HTMLElement.offsetWidth
JS一秒区分clientX,offsetX,screenX,pageX之间关系

范例网页请按此


<<:  Laravel Queue Job:深入理解 timeout 的运作

>>:  Python list

Day-03 JavaScript资料型别(2)

资料型别:字串 JavaScript的字串(string)以单引号(‘ ’)或双引号(“ ”)包住,...

D27 / 怎麽测试? - Testing Compose

今天大概会聊到的范围 Testing Compose 的 Test 属於 UI Test ,在执行...

Day 22 ATT&CK for ICS - Discovery(2)

T0888 Remote System Information Discovery 攻击者透过後门收...

让你用 ZK快速开发的环境设定

本系列文章范例专案 本系列文章中所有的设定与范例程序码都可以在 Github 上的范例专案 zkqu...

SOURCEPATH 能帮助我们什麽呢?

在一般专案里,一定会有很多个 .java 跟 .class 档案,那我们要怎麽去管理这两种档案呢? ...