「JS30紀錄&心得」11 - Custom Video Player

主題

介紹如何使用HTML5的video tag來完成各種播放器功能,
播放/暫停、快進/快退、音量控制、速率控制等…

[DEMO][GitHub][JavaScript30全列表]

步驟

Step1. 基本設定

作者已經有將基礎的css及html tag設定好,僅需針對各項目的功能開始進行js撰寫即可,
但這邊我有將背景色調調整,並把對應icon改用font-awesome來顯示(原本是文字符號)。

由於寫到最後已經很多寫法跟原作者的方法不太一樣,所以接著各功能會再稍微備註為何這麼寫。

Step2. 播放/暫停按鈕

為了在整個播放器範圍及點擊播放按鈕時能播放/暫停,
先針對這兩個元素做addEventListener
並在togglePlay()中使影片產生對應動作&更換圖示,
比較特別的是使用了video[method]的寫法,來直接操作video的屬性,
直接用影片是否已暫停paused來做判斷。

1
2
3
4
5
6
7
8
9
function togglePlay() {
const method = video.paused ? 'play' : 'pause';
const icon = video.paused ? `<i class="icon-play"></i>` : `<i class="icon-pause"></i>`;
toggle.innerHTML = icon;
video[method]();
}

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

原本是將圖標更換&影片動作分開寫,我改成寫一起。

Step3. 音量/速率操作

在HTML中已經定義好對應的input-range標籤,
在這裡只需要做監聽並取屬性值來操作就好了!

1
2
<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">Ï

在javascript的部分,ranges是透過querySelectorAll來取得的,
所以可以用forEach來把所有range加上addEventListener
也因為range是拖曳條,除了click外,也必須要監聽mousemove
而name的命名volumeplaybackRate也就是video本身的屬性,直接使用。

1
2
3
4
5
6
7
function handleRangeUpadte() {
video[this.name] = this.value;
}
ranges.forEach(range => {
range.addEventListener('change', handleRangeUpadte);
range.addEventListener('mousemove', handleRangeUpadte);
})

Step4. 快進/快退操作

一樣也在HTML中的input定義好對應的秒數了,只須取出使用。

1
2
<button data-skip="-10" class="player__button skip_left"><i class="icon-backward"></i></button>
<button data-skip="25" class="player__button skip_right"><i class="icon-forward"></i></button>

在javascript的部分,原本的寫法只有點擊後觸發,
因為我有加上鍵盤觸發的動作,所以將skip加上了一個參數direction來判斷,
skipButtons也跟Step3的ranges一樣用forEach來加上監聽效果,
快進快退的作法是取出input中設定的data-skip後透過currentTime來調整影片時間。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function skip(direction) {
let skipTime = 0;
if (direction === 'left') {
skipTime = document.querySelector('.skip_left').dataset.skip;
} else if (direction === 'right') {
skipTime = document.querySelector('.skip_right').dataset.skip;
} else {
skipTime = this.dataset.skip;
}
video.currentTime += parseFloat(skipTime);
}
skipButtons.forEach(button => {
button.addEventListener('click', skip);
})

Step5. 進度條顯示

使用video的currenTimeduration計算出進度%數,
再透過CSS改變進度條的色塊%數,值得一提的是作者有說到兩個監聽參數:
timeupdateprogress都可以做為影片時間變動時的觸發條件,
我稍微小測試後發現,使用progress會在載入時就將進度顯示在正確位置,
timeupdate必須在啟動播放後才會去抓到正確的位置,
可以將CSS中的flexBasis預設設為50%來觀察這兩者的差別。

1
2
3
4
5
function handleProgress() {
const precent = (video.currentTime / video.duration) * 100;
progressBar.style.flexBasis = `${precent}%`;
}
video.addEventListener('progress', handleProgress);

Step6 進度條操作

在影片的進度條上,做點擊切換段落,或著是按著滑動片段,
分解動作會有:點擊按住並移動這兩種觸發條件,
為了要讓function能同時判斷兩種狀態,必須要將其中一個條件設flag,
這裡就將mousedown做了一個flag來操作狀態,
並利用e.offsetX的位置及progress.offsetWidth寬度與影片總長來操作當前秒數。

監聽的部分,因為原本重複寫了四次addEventListener
所以我把會使用到的event寫在一個陣列裡面,使用forEach去執行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let mousedown = false;
function scrunb(e) {
const mouseType = e.type;
if (mouseType === 'mousedown') { mousedown = true; }
if (mouseType === 'mouseup') { mousedown = false; }
if (mouseType === 'click' || mouseType === 'mousemove' && mousedown) {
const scrunbTime = (e.offsetX / progress.offsetWidth) * video.duration;
video.currentTime = scrunbTime;
}
}
const progressEvents = ['click', 'mousemove', 'mousedown', 'mouseup'];
progressEvents.forEach(progressEvent => {
progress.addEventListener(progressEvent, scrunb);
})

Step7. 全螢幕

作者最後有提到的小功能,他說留給我們自己去研究。
首先在HTML中加上對應的功能按鈕與圖標(就是這個放大圖標讓我去用font-aswsome的XD)

1
<button class="player__button fullScreen" title="Full Screen"><i class="icon-fullscreen"></i></button>

然後再javascript中加入這段,多個判斷是為了不同的瀏覽器而寫,
值得一提的是,不用對取消全螢幕特別做處理,預設就會有esc關閉及對應的關閉icon了,
但如果有特別需求可以使用exitFullscreen()來關閉。

1
2
3
4
5
6
7
8
9
10
11
12
function fullScreen() {
if (video.requestFullscreen) {
video.requestFullscreen();
} else if (video.msRequestFullscreen) {
video.msRequestFullscreen();
} else if (video.mozRequestFullScreen) {
video.mozRequestFullScreen();
} else if (video.webkitRequestFullscreen) {
video.webkitRequestFullscreen();
}
}
fullScreenBtn.addEventListener('click', fullScreen);

Step8. 鍵盤動作偵測

最後加上的功能是鍵盤對應操作,
加入了空白對應播放/暫停、鍵盤左右鍵對應快進/快退,
空白鍵加上了preventDefalut是防止預設空白會跳到最底下的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function eventKeydown(e) {
switch (e.keyCode) {
//空白鍵
case 32:
e.preventDefault()
togglePlay();
break;
//方向鍵左
case 37:
skip('left');
break;
//方向鍵右
case 39:
skip('right')
break;
}
}
document.addEventListener('keydown', eventKeydown);

HTML5語法&備註

Video & Media Element

這次的主軸是HTML的video標籤,所以滿多操作都是直接操作video的屬性,
例如偵測暫停的paused或是當前播放時間currentTime
但其實這些屬性並非video獨有的,而是HTML Media Element,好比說audio也會有。

參閱:
MDN-Video
MDN-HTMLMediaElement

JavaScript語法&備註

屬性使用

在Step2中有使用video[method]()的方法來操作屬性,
其實相關的操作方法就等同於video.play(),但不能寫成video.method()
因為這樣就變成呼叫video底下的function method了,
所以使用中括號[]包起來的會自動變成字串,可以避免掉這樣的問題。

HTMLElement.dataset

在Step4中使用到的,使用dataset可以取得htmlTag中的data-*屬性!

參閱:MDN-

Event.preventDefault()

在Step8中使用到的,這個方法是將取消事件(如果事件可取消),
這次使用的場景是於網頁瀏覽器中按下空白,預設會將網頁捲到底部,
但我希望只要啟動我的播放/暫停功能就好不要捲動,就可以使用。

參閱:MDN-Event.preventDefault()

探索

本次的探索滿多,都已經寫在step中了,
修改了播放器樣式及部分色調調整,對重複的監聽與變數整理,
加入了全螢幕效果以及鍵盤操作功能。

其他

終於踏出JavaScript30 2/3的第一步,
最近好多事,一直沒能好好靜下心來進修,
除了JS30,之前預購的一些CSS課程也要陸續上線了,
稍微覺得時間有點不夠用外,也開始容易分心了XD,
畢竟CSS等設計相關的課程看起來比較華麗啊哈哈哈。

加油持續把這系列練習完!!