iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 11
1

Day11-課題內容

今天的課題中,我們將來了解 video 物件,並透過其相關方法,來客製化一個影片播放器。[1]
實作範例

Video物件

在HTML5推出之前,我們只能利用插件在網頁中嵌入影片,當HTML5推出之後, <video> 元素就變成在網頁中嵌入影片的標準方式。以下將先簡介幾個常用的方法以及屬性[2]

  1. HTML Audio/Video Methods:
  • play():開始播放。
  • pause():暫停播放。
  • load():重新讀取。
  1. HTML Audio/Video Properties:
  • paused:目前狀態為暫停True或是播放false
  • volume:設定音量或回傳目前的音量。
  • duration:回傳Audio/Video元素的整體長度(秒)。
  • currentTime:設定播放的時間點或回傳目前播放的時間點。

監聽事件及函式

知道 <video> 元素的相關方法與屬性之後,就可以開始設定各個元素的監聽事件與函式,為我們的播放器加上相關功能。
首先我們先將會用到的元素選取起來:

var selectVideo = document.querySelector('video');
var playButton = document.querySelector('.toggle');
var inputItem = document.querySelectorAll('input');
var skipButton = document.querySelectorAll('.player__button[data-skip]');
var progressBarOut = document.querySelector('.progress');
var progressBarIn = document.querySelector('.progress__filled');

1. 影片播放

當點擊影片或播放鈕時,我們想要使影片播放或暫停,並同時將播放鈕改為暫停或播放的圖示。
我們可以將指定元素加上監聽事件 click 來觸發函數,並透過<video> 元素的 play()pause() 方法來播放或暫停,且透過更改 .toggle 元素的 innerHTML 來更動播放鈕的圖示:

//選取影片及按鈕元素並加上監聽事件與執行函示
playButton.addEventListener('click', playToggle);
selectVideo.addEventListener('click', playToggle);

function playToggle() {
    //當影片狀態為暫停的時候
    if (selectVideo.paused) {
        //播放影片
        selectVideo.play();
        //將播放鈕圖示改為暫停鈕圖示                     
        playButton.innerHTML = '❚ ❚';
    //當影片是播放的時候                         
    } else {
        //暫停影片
        selectVideo.pause();
        //將暫停鈕圖示改為播放鈕圖示     
        playButton.innerHTML = '►';
    };
};

2. 調整音量及播放速度

下一步我們要將音量控制器與速度控制器與影片屬性連接。
一樣也是先將指定的元素加上監聽事件,並透過函式內容來修改影片的屬性,最後完成改變影片的音量或播放速度。
作者在 input 元素中有加入 name 屬性,且該屬性值分別就是影片屬性的名稱,因此我們可以利用 event.target.name 來取得元素中 name 屬性值,並將其指定成變數,讓函式可以自行判斷要更改的屬性,如此一來就可以利用同一個函式來更改音量或播放速度:

//利用forEach()方法將選到的每個input元素加上監聽事件
inputItem.forEach(function(item){
    item.addEventListener('input', changeCondition);
});

function changeCondition (event) {
    //取得觸發事件的input元素
    let changeInput = event.target;
    //取得input元素的name屬性
    let conditionName = changeInput.name;
    //取得input元素的值   
    let conditionValue = changeInput.value;
    //將影片屬性值改為input元素的值
    selectVideo[conditionName] = conditionValue;   
};

3. 快轉or倒帶

將快轉或倒帶的元素加上監聽事件後,在執行函數內透過取得指定元素的 data-attribute 的屬性值,再將目前的影片時間加上我們取得的值,再指派給 currentTime,我們就能在播放器上做出有快轉或是倒帶功能的按鈕:

function skipVideo(event) {
    let changeTime = parseInt(event.target.dataset.skip);
    selectVideo.currentTime += changeTime;
};

4. 影片進度條

最後我們要替播放器加上時間軸,並且可以透過點擊時間軸上的位置,將播放時間指定到我們選定的位置。
目前在我們的時間軸上,有作為背景的 .progress 元素,以及用來顯示目前進度的 .progress__filled 元素。

  1. 影片時間轉換成時間軸位置
    進度條的長度隨著每秒影片的播放而增加,因此我們要在這新增一個 HTML Audio/Video 的事件 timeupdatetimeupdate 事件每當指定元素的播放時間改變就會被觸發,在觸發的函式中結合影片的 duration 以及 currentTime 屬性,來換算出目前的播放時間比例,最後再把這個值指定為時間軸元素的寬度,就可以讓時間軸隨著影片播放時間增加跟著變長:
//將影片加上監聽事件以及觸發函示
selectVideo.addEventListener('timeupdate', progressing);

function progressing() {
    //取得影片時間總長度
    let videoDuration = selectVideo.duration;
    //取得影片目前時間長度
    let currentPosition = selectVideo.currentTime;
    //換算成比例
    currentProgress = currentPosition / videoDuration * 100;
    //將算出來的比例加到該元素的CSS屬性上
    progressBarIn.style.flexBasis = `${currentProgress}%`;
};
  1. 時間軸位置轉換成影片時間
    再來我們要將點擊的時間軸位置,換算成影片播放的時間。
    而我們進度條元素大小是隨著播放時間長短所決定,因此我們並無法點擊到影片尚未被播放的位置,所以我們需要將觸發事件的元素指定為背景的 .progress 元素。
    當我們在時間軸點下滑鼠拖曳時,作為指定的背景的 .progress 元素,被觸發監聽事件並執行函示,在觸發的函式中透過 event.offsetX 方法,取得目前滑鼠在該元素上的X軸座標,並且透過 offsetWidth 屬性,取得該元素的整體長度,最後再換將這兩個值換算出來的比例,換算成目前的播放時間指定給影片,如此一來我們就可以完成我們的時間軸功能:
//當滑鼠被按下時,執行addDragProgress函式
progressBarOut.addEventListener('mousedown', addDragProgress);
//當滑鼠被放開時,執行removeDragProgress函式
progressBarOut.addEventListener('mouseup', removeDragProgress);

function addDragProgress (e) {
    //當函示被執行時,新增監聽mousedown以及mousemove事件
    progressBarOut.addEventListener('mousedown', dragProgressBar);
    progressBarOut.addEventListener('mousemove', dragProgressBar);
};

function removeDragProgress () {
    //當函示被執行時,移除監聽mousemove事件
    progressBarOut.removeEventListener('mousemove', dragProgressBar);
};

function dragProgressBar (e) {
    //取得影片總長度
    let videoDuration = selectVideo.duration;
    //取得按下按鍵時的滑鼠在該元素的X軸座標
    let mouseX = e.offsetX;
    //取得時間軸背景元素總長度
    let progressBarWidth = progressBarOut.offsetWidth;
    //換算成長度比例
    let videoProgress = mouseX / progressBarWidth;
    //換算成影片時間
    let newPosition = videoDuration * videoProgress;
    //將計算出來的影片時間指定為目前播放時間
    selectVideo.currentTime = newPosition;
};

在後面的函式中我們並沒有將換算出來的長度比例指定給時間軸,這是因為透過前面繪製時間軸的監聽事件 timeupdate,在改變播放時間的那一瞬間,該函式就被處發並完成函式內容,將時間換算成時間軸長度。
另外可以在函式 addDragProgress() 中,我們又額外監聽了一次 mousedown 事件,這是因為如果只監聽 mousemove 事件的話,當我們在時間軸上按下滑鼠之後,需要再移動滑鼠才能觸發時間轉換的函式。但一般的播放器當我們點擊時間軸上的任意位置,會直接跳到對應的播放時間,因此在這邊多加上監聽 mousedown 事件,就可以做出點擊時間軸就馬上跳到對應的影片位置。
另外我們有將幾個點擊功能與鍵盤按鈕綁定,嘗試做出幾個 youtube 網站上的播放器功能,大家可以在實作連結中試看看,code內容如下:

window.addEventListener('keydown', keyboardFunction);

function keyboardFunction(e) {
    e.preventDefault();

    if (e.keyCode === 37) {
        let backButton = document.querySelector('.player__button[data-skip="-10"]');
        console.log(backButton);
        backButton.click();
    };
    if (e.keyCode === 39) {
        let nextBotton = document.querySelector('.player__button[data-skip="25"]');
        console.log(nextBotton);
        nextBotton.click();
        
    };
    if (e.keyCode === 38 && selectVideo.volume <= 1) {
        let volumeSlide = document.querySelector('[name = "volume"]');
        volumeSlide.stepUp();
        selectVideo.volume = volumeSlide.value;
        console.log(volumeSlide.value);
    };
    if (e.keyCode === 40 && selectVideo.volume > 0) {
        let volumeSlide = document.querySelector('[name = "volume"]');
        volumeSlide.stepDown();
        selectVideo.volume = volumeSlide.value;
        console.log(volumeSlide.value);
    };
    if (e.keyCode === 32) {
        playToggle();
    };
};

總結

在今天的播放器課題當中,我們學到以下的技能:

  1. HTML Audio/Video 方法
  2. HTML Audio/Video 屬性
  3. HTML Audio/Video 事件

透過事件的觸發,在執行函示中使用 video 物件的方法或是更改屬性,我們就可以創造出一個充滿各種神奇功能的播放器。以上是今天的方法與心得,感謝您的閱讀。

參考資料

  1. javascript30
  2. w3schools-HTML Audio/Video DOM Reference

上一篇
JS30-Day10-Hold Shift to Check Multiple Checkboxes
下一篇
JS30-Day12-Key Sequence Detection
系列文
新手也能懂的JS3030
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
阿姆
iT邦新手 5 級 ‧ 2018-08-17 21:17:30

你好,我最近剛好也在進行js30,常常翻閱你們的教學~
想請問第二點的

selectVideo[conditionName] = conditionValue;

這邊是基於甚麼條件或特性必須使用中括號呢??
(為什麼selectVideo.conditionName會出錯呢)

阿姆 iT邦新手 5 級 ‧ 2018-08-17 21:37:08 檢舉

我好像自己找到答案了
用 點號 . 存取時,點號右邊必須是一個可識別字(identifier),例如屬性名稱,不能是字串、變數、運算式...等。但用 中括號 [] 存取時,中括號裡面可以放字串、變數、運算式等。

應該是這樣沒錯吧...XD

王郁翔 iT邦新手 5 級 ‧ 2018-08-18 09:45:07 檢舉

想到竟然還有人在看我的文章大驚!
其實你已經答對了XD
物件在改變屬性的時候有兩種方法

  1. objectName.propertyName
  2. objectName["propertyName"]

所以今天當你是用一個變數來對物件進行操作時,使用第一種方法會因為找不到屬性而失敗,使用第二種方法才會成功。

舉上面的例子來說,當我們改變 volume 這個屬性的時候:

第一種方法會因在我的 selectVideo 裡面沒有 "conditionName" 這個屬性,所以反而變成在 selectVideo 增加一個 key 為 conditionName 的屬性。

第二種方法會先把 selectVideo[conditionName] 轉換成 selectVideo["volume"] 然後就可以成功取代成新值。
希望有幫助到你!

我要留言

立即登入留言