作品網址:


https://steven5j.github.io/11-Custom-Video-Player/

 

 


主題目標


  • 為video元素添加自定義樣式的播放控制面板
  • 可滑動調節音量、播放速度
  • 可通過按鈕快進、回退
  • 可點擊視頻畫面或按鈕播放或暫停視頻播放
  • 可點擊或拖動進度條選擇視頻播放進度
  • 將播放器的各項 DOM ,都建立監聽事件

 


需求思考分析


  1. 首先,我們已經有了HTML 文件,裡面包含各種播放器用到的元素,格式也已經在CSS 文件中幫我們設置好了
  2. 在JS 中選擇我們需要添加功能的HTML 元素,建立好變量
  3. 用JS 寫好播放器的功能
  4. 給第二步中獲取的元素加上事件監聽和回調,即可實現功能

 


處理步驟


步驟 1. 播放的呈現

  • 取得 HTML 上的各個元素
  • 綁定撥放功能
  • 增加變換播放按鈕圖案功能
  • 添加播放後,進度條的動態顯示

步驟 2. 快退/快進跳轉按鈕 和 音量與播放速度調整

  • 利用 html 上的 name 與值
  • 觸發狀態的綁定

步驟 3. 進度條的動態切換

  • 建立播放進度條跳轉至指定時間 scrub function
  • 簡單的拉動綁定事件 click 與 mousemove
  • 避免播放器的點擊功能互相影響,建立 mousedown 變數
  • 並在進度條上點擊時觸發

特別技術、函式


<HTML>

<video>: The Video Embed element

HTML &lt;video&gt;元素用於在HTML或者XHTML文檔中嵌入媒體播放器,用於支持文檔內的視頻播放。你也可以將 &lt;video&gt;  標籤用於音頻內容,但是&lt;audio&gt;元素可能在用戶體驗上更合適。

Media events

在處理用&lt;audio&gt;&lt;video&gt;標籤嵌入到HTML文檔中的媒體時,會觸發多種事件。本章列出這些事件,並給出一些使用方法。

事件名稱描述
abort 在播放被終止時觸發,例如, 當播放中的視頻重新開始播放時會觸發這個事件。
canplay在媒體數據已經有足夠的數據(至少播放數幀)可供播放時觸發。這個事件對應CAN_PLAY的readyState。
canplaythrough在媒體的readyState變為CAN_PLAY_THROUGH時觸發,表明媒體可以在保持當前的下載速度的情況下不被中斷地播放完畢。注意:手動設置currentTime會使得firefox觸發一次canplaythrough事件,其他瀏覽器或許不會如此。
durationchange元信息已載入或已改變,表明媒體的長度發生了改變。例如,在媒體已被加載足夠的長度從而得知總長度時會觸發這個事件。
emptied媒體被清空(初始化)時觸發。
ended播放結束時觸發。
error在發生錯誤時觸發。元素的error屬性會包含更多信息。參閱  HTMLMediaElement.error  獲得詳細信息。
loadeddata媒體的第一幀已經加載完畢。
loadedmetadata媒體的元數據已經加載完畢,現在所有的屬性包含了它們應有的有效信息。
loadstart在媒體開始加載時觸發。
mozaudioavailable當音頻數據緩存並交給音頻層處理時
pause播放暫停時觸發。
play在媒體回放被暫停後再次開始時觸發。即,在一次暫停事件後恢復媒體回放。
playing在媒體開始播放時觸發(不論是初次播放、在暫停後恢復、或是在結束後重新開始)。
progress告知媒體相關部分的下載進度時周期性地觸發。有關媒體當前已下載總計的信息可以在元素的buffered屬性中獲取到。
ratechange在回放速率變化時觸發。
seeked在跳躍操作完成時觸發。
seeking在跳躍操作開始時觸發。
stalled在嘗試獲取媒體數據,但數據不可用時觸發。
suspend在媒體資源加載終止時觸發,這可能是因為下載已完成或因為其他原因暫停。
timeupdate元素的currentTime屬性表示的時間已經改變。
volumechange在音頻音量改變時觸發(既可以是volume屬性改變,也可以是muted屬性改變).。
waiting在一個待執行的操作(如回放)因等待另一個操作(如跳躍或下載)被延遲時觸發。

使用下面的代碼,你可以很容易的觀察到這些事件:

var v = document.getElementsByTagName("video")[0];
v.addEventListener("seeked", function() { document.getElementsByTagName("video")[0].play(); }, true);
v.currentTime = 10.0;

<CSS>

::-webkit-slider-runnable-track 與 ::-moz-range-track

非標準的語法,依照 MDN 上的說明,可能會變換,主要是針對 中 type=”range” 的類型設定進度調軌跡的背景與邊框樣式

::-webkit-slider-thumb 與 ::-moz-range-thumb

這是type為range的input標籤內的一種偽類樣式,用於設置range的滑塊的具體樣式,該偽類只在內核為webkit/blink的瀏覽器中有效

該偽類需要配和::-webkit-slider-runnable-track使用,否則會沒有效果…….

這兩個的功能相同,簡單說就是移動時候的顯示樣式,主要是在不同瀏覽器的支援。

-moz-appearance (-webkit-appearance, appearance)

就是變更元素的外觀而已。

flex-basis

CSS屬性flex-basis指定了flex元素在主軸方向上的初始大小。如果不使用 box-sizing 改變盒模型的話,那麼這個屬性就決定了flex元素的內容盒(content-box)的尺寸。

<JavaScript>

HTMLMediaElement: timeupdate

當currentTime更新時會觸發timeupdate事件。這個事件的觸發頻率由系統決定,但是會保證每秒觸發4-66次。

MouseEvent.offsetX

MouseEvent接口的只讀屬性offsetX規定了事件對象與目標節點的內填充邊(padding edge)在X軸方向上的偏移量。

HTMLElement.offsetWidth

HTMLElement.offsetWidth是一個只讀屬性,返回一個元素的佈局寬度。一個典型的(譯者註:各瀏覽器的offsetWidth可能有所不同)offsetWidth是測量包含元素的邊框(border)、水平線上的內邊距(padding)、豎直方向滾動條(scrollbar)(如果存在的話)、以及CSS設置的寬度(width)的值。


完整程式碼


// 抓取全部的元素
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');

//讓影片撥放或暫停
console.log(video);
function togglePlay(){
   const method = video.paused? 'play':'pause';
    video[method]();
}

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

//加入圖示ICON點選邏輯事件變化
function updateButton(){
 const icon = this.paused ? '►' : '❚ ❚';
    console.log(icon);
    toggle.textContent= icon;
}
video.addEventListener ( 'play' , updateButton);
video.addEventListener ( 'pause' , updateButton);

//快退/快進

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

skipButtons.forEach(button => button.addEventListener('click',skip));


//音量和播放速度

console.log(ranges);
function handleRangeUpdate(){
    video[this.name] = this.value;
};
ranges.forEach(range => range.addEventListener('change', handleRangeUpdate));
ranges.forEach(range => range.addEventListener('mousemove', handleRangeUpdate));

//進度條操作--------------------------------------
console.log(progressBar)
//自動進度條依照撥放時間圖示變化
function handleProgress(){
    const percent = (video.currentTime/video.duration)*100;
    progressBar.style.flexBasis = `${percent}%`;
}
video.addEventListener('timeupdate',handleProgress);
//根據點擊位置設置播放時間
function scrub(e){
    const scrubTime = (e.offsetX/progress.offsetWidth) * video.duration;
    video.currentTime = scrubTime;
}
progress.addEventListener('click',scrub);

//鼠標在progress上移動時更新進度
let mousedown =  false ;
//利用邏輯和操作符&&的短路特性來實現“只有當mousedown為true,或可類型轉換為true時才執行scrub(e)”的判斷操作
progress.addEventListener('mousemove',(e) => mousedown && scrub(e));
// progress . addEventListener ( ' mousemove ' , ( e ) => {

//     //若處於拖拽狀態則執行更新
//     if (mousedown) {
//          scrub (e);
//     }
// });
progress.addEventListener('mousedown',() => mousedown = true);
progress.addEventListener('mouseup',() => mousedown = false);

 

參考資料:


JS30紀錄 10-Hold Shift and Check Checkboxes: https://shunnien.github.io/2017/12/27/Javascript30days-10/

Js 30 day 中文指南:https://github.com/soyaine/JavaScript30

發佈留言