iT邦幫忙

2021 iThome 鐵人賽

DAY 11
0
自我挑戰組

JS30 學習日記系列 第 11

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之間關係

範例網頁請按此


上一篇
Day 10 - Hold Shift to Check Multiple Checkboxes
下一篇
Day 12 - Key Sequence Detection (KONAMI CODE)
系列文
JS30 學習日記30

尚未有邦友留言

立即登入留言