建立跨瀏覽器影片播放器

本文件介紹了一個簡單的 HTML 影片播放器,它使用 Media 和 Fullscreen API。除了全屏工作外,播放器還具有自定義控制元件,而不是僅使用瀏覽器預設控制元件。播放器控制元件本身不會進行任何超出基本要求的樣式化,以使它們正常工作;播放器的完整樣式將在以後的文章中處理。

工作示例

我們的示例影片播放器顯示了來自名為 Tears of Steel 的開源電影的片段,幷包含典型的影片控制元件。

a shot of a video player, with several control buttons such as play, pause and stop. The video is showing a group of men fighting a group of robots.

HTML 標記

首先,讓我們看一下構成播放器的 HTML。

影片

首先,定義了 <video> 元素,它包含在 <figure> 元素中,作為影片容器。對於熟悉 HTML 標記和 <video> 元素的人來說,這裡應該沒有什麼讓您感到驚訝的地方。

html
<figure id="videoContainer">
  <video id="video" controls preload="metadata" poster="img/poster.jpg">
    <source
      src="video/tears-of-steel-battle-clip-medium.mp4"
      type="video/mp4" />
    <source
      src="video/tears-of-steel-battle-clip-medium.webm"
      type="video/webm" />
    <source
      src="video/tears-of-steel-battle-clip-medium.ogg"
      type="video/ogg" />
    <!-- Offer download -->
    <a href="video/tears-of-steel-battle-clip-medium.mp4">Download MP4</a>
  </video>
  <figcaption>
    &copy; Blender Foundation |
    <a href="http://mango.blender.org">mango.blender.org</a>
  </figcaption>
</figure>

即使此播放器將定義自己的自定義控制元件集,controls 屬性仍然被新增到 <video> 元素中,並且播放器的預設控制元件集稍後將使用 JavaScript 關閉。這樣操作仍然允許 JavaScript 被停用(出於任何原因)的使用者仍然可以使用瀏覽器的原生控制元件。

為影片定義了一個海報影像,並且 preload 屬性設定為 metadata,這通知瀏覽器它最初應該只嘗試載入影片檔案中的元資料,而不是整個影片檔案。這將為播放器提供諸如影片持續時間和格式之類的資訊。

為播放器提供了三種不同的影片源:MP4、WebM 和 Ogg。使用這些不同的源格式提供了在所有支援 HTML 影片的瀏覽器中獲得最佳支援的機會。有關影片格式和瀏覽器相容性的更多資訊,請參見 支援的媒體格式

上面的程式碼將允許在大多數瀏覽器中播放影片,使用瀏覽器的預設控制元件集。下一步是定義一個自定義控制元件集,也在 HTML 中,它將用於控制影片。

控制元件集

大多數瀏覽器的預設影片控制元件具有以下功能

  • 播放/暫停
  • 靜音
  • 音量控制
  • 進度條
  • 快進
  • 全屏

自定義控制元件集還將支援此功能,並增加了停止按鈕。

再次,HTML 非常簡單,使用一個無序列表,其中設定了 list-style-type:none 來包含控制元件,每個控制元件都是一個列表項,其中設定了 float:left。對於進度條,可以使用 progress 元素。此列表插入在 <video> 元素之後,但在 <figure> 元素內部(這對全屏功能很重要,將在後面解釋)。

html
<ul id="video-controls" class="controls">
  <li><button id="playpause" type="button">Play/Pause</button></li>
  <li><button id="stop" type="button">Stop</button></li>
  <li class="progress">
    <progress id="progress" value="0" min="0"></progress>
  </li>
  <li><button id="mute" type="button">Mute/Unmute</button></li>
  <li><button id="volinc" type="button">Vol+</button></li>
  <li><button id="voldec" type="button">Vol-</button></li>
  <li><button id="fs" type="button">Fullscreen</button></li>
</ul>

每個按鈕都被賦予了一個 id,以便可以使用 JavaScript 方便地訪問它。

控制元件最初使用 CSS display:none 隱藏,並將使用 JavaScript 啟用。同樣,如果使用者停用了 JavaScript,自定義控制元件集將不會出現,並且他們可以無阻礙地使用瀏覽器的預設控制元件集。

當然,此自定義控制元件集目前沒有任何用處,並且什麼也不做:讓我們用 JavaScript 改進這種情況。

使用 Media API

HTML 帶有一個 JavaScript Media API,允許開發者訪問和控制 HTML 媒體。此 API 將用於使上面定義的自定義控制元件集真正起作用。此外,全屏按鈕將使用 Fullscreen API,這是另一個 W3C API,它控制 Web 瀏覽器使用計算機全屏顯示應用程式的能力。

設定

在處理各個按鈕之前,需要進行一些初始化呼叫。

首先,最好先檢查瀏覽器是否真正支援 <video> 元素,並且僅在支援的情況下設定自定義控制元件。這是透過檢查建立的 <video> 元素是否支援 canPlayType() 方法 來完成的,任何支援的 HTML <video> 元素都應該支援該方法。

js
const supportsVideo = !!document.createElement("video").canPlayType;
if (supportsVideo) {
  // set up custom controls
  // …
}

一旦確認瀏覽器確實支援 HTML 影片,就該設定自定義控制元件了。需要指向 HTML 元素的變數

js
const videoContainer = document.getElementById("videoContainer");
const video = document.getElementById("video");
const videoControls = document.getElementById("video-controls");

如前所述,現在需要停用瀏覽器的預設控制元件,並顯示自定義控制元件

js
// Hide the default controls
video.controls = false;

// Display the user defined video controls
videoControls.style.display = "block";

完成之後,現在需要指向每個按鈕的變數

js
const playpause = document.getElementById("playpause");
const stop = document.getElementById("stop");
const mute = document.getElementById("mute");
const volinc = document.getElementById("volinc");
const voldec = document.getElementById("voldec");
const progress = document.getElementById("progress");
const fullscreen = document.getElementById("fs");

使用這些控制代碼,現在可以將事件附加到每個自定義控制元件按鈕上,使它們具有互動性。大多數這些按鈕都需要新增一個簡單的 click 事件監聽器,以及在影片上呼叫的 Media API 定義方法和/或屬性。

播放/暫停

js
playpause.addEventListener("click", (e) => {
  if (video.paused || video.ended) {
    video.play();
  } else {
    video.pause();
  }
});

當在播放/暫停按鈕上檢測到 click 事件時,處理程式首先檢查影片當前是否暫停或已結束(透過 Media API 的 pausedended 屬性);如果是,則使用 play() 方法播放影片。否則,影片必須正在播放,因此使用 pause() 方法暫停它。

停止

js
stop.addEventListener("click", (e) => {
  video.pause();
  video.currentTime = 0;
  progress.value = 0;
});

Media API 沒有 stop 方法,因此要模擬此方法,影片將被暫停,並且它的 currentTime(即影片的當前播放位置)和 <progress> 元素的位置被設定為 0(有關 <progress> 元素的更多資訊,請參見後面)。

靜音

js
mute.addEventListener("click", (e) => {
  video.muted = !video.muted;
});

靜音按鈕是一個簡單的切換按鈕,它使用 Media API 的 muted 屬性來靜音影片:這是一個布林值,指示影片是否被靜音。為了讓它切換,我們將它設定為它自身的反值。

音量

js
volinc.addEventListener("click", (e) => {
  alterVolume("+");
});
voldec.addEventListener("click", (e) => {
  alterVolume("-");
});

定義了兩個音量控制按鈕,一個用於增加音量,另一個用於降低音量。建立了一個使用者定義的函式 alterVolume(direction) 來處理此問題

js
function alterVolume(dir) {
  const currentVolume = Math.floor(video.volume * 10) / 10;
  if (dir === "+" && currentVolume < 1) {
    video.volume += 0.1;
  } else if (dir === "-" && currentVolume > 0) {
    video.volume -= 0.1;
  }
}

此函式使用 Media API 的 volume 屬性,該屬性儲存影片的當前音量值。此屬性的有效值是 0 和 1 以及介於兩者之間的任何值。該函式檢查 dir 引數,該引數指示音量是增加 (+) 還是降低 (-),並相應地採取行動。該函式被定義為以 0.1 為步長增加或減少影片的 volume 屬性,確保它不會低於 0 或高於 1。

進度

在上面的 HTML 中定義 <progress> 元素時,只設置了兩個屬性,valuemin,兩者都被賦予了 0 的值。這些屬性的功能不言而喻,min 表示 progress 元素的最小允許值,而 value 表示它的當前值。它還需要設定一個最大值,以便它可以正確地顯示其範圍,這可以透過 max 屬性來完成,該屬性需要設定為影片的最大播放時間。這是從影片的 duration 屬性獲得的,該屬性也是 Media API 的一部分。

理想情況下,當 loadedmetadata 事件被觸發時,影片的 duration 屬性的正確值是可用的,該事件在影片的元資料被載入時發生

js
video.addEventListener("loadedmetadata", () => {
  progress.setAttribute("max", video.duration);
});

不幸的是,在一些移動瀏覽器中,當 loadedmetadata 被觸發時(如果它確實被觸發了)——video.duration 可能沒有正確的值,甚至可能根本沒有值。因此,需要做一些其他事情。更多內容請參見下面。

另一個事件 timeupdate 會在影片播放過程中定期觸發。此事件非常適合更新進度條的值,將其設定為影片的 currentTime 屬性的值,該屬性指示當前播放位置在影片中的位置。

js
video.addEventListener("timeupdate", () => {
  progress.value = video.currentTime;
});

隨著 timeupdate 事件的觸發,progress 元素的 value 屬性被設定為影片的 currentTime。此跨度具有一個實心的 CSS 背景色,這有助於它提供與 <progress> 元素相同的視覺反饋。

回到上面提到的 video.duration 問題,當 timeupdate 事件被觸發時,大多數移動瀏覽器中的影片 duration 屬性應該已經有了正確的值。可以利用這一點,在 progress 元素的 max 屬性當前未設定時對其進行設定。

js
video.addEventListener("timeupdate", () => {
  if (!progress.getAttribute("max"))
    progress.setAttribute("max", video.duration);
  progress.value = video.currentTime;
});

注意: 更多關於進度條和緩衝反饋的資訊和想法,請閱讀 媒體緩衝、跳轉和時間範圍

跳過

大多數瀏覽器預設影片控制集的另一個功能是,使用者可以點選影片的進度條,以“跳過”到影片中的不同位置。這也可以透過向 progress 元素新增一個簡單的 click 事件監聽器來實現。

js
progress.addEventListener("click", (e) => {
  const rect = progress.getBoundingClientRect();
  const pos = (e.pageX - rect.left) / progress.offsetWidth;
  video.currentTime = pos * video.duration;
});

這段程式碼使用點選位置來 (大致) 計算出使用者點選了 progress 元素中的哪個位置,並透過設定影片的 currentTime 屬性將其移動到該位置。

全屏

全屏 API 應該非常簡單易用:使用者點選一個按鈕,如果影片處於全屏模式:取消它,否則進入全屏模式。

如果未啟用全屏 API,則全屏按鈕會隱藏。

js
if (!document?.fullscreenEnabled) {
  fullscreen.style.display = "none";
}

全屏按鈕需要真正做點什麼。與其他按鈕類似,它也附帶了一個 click 事件處理程式,該處理程式呼叫使用者定義的函式 handleFullscreen

js
fullscreen.addEventListener("click", (e) => {
  handleFullscreen();
});

handleFullscreen 函式的定義如下:

js
function handleFullscreen() {
  if (document.fullscreenElement !== null) {
    // The document is in fullscreen mode
    document.exitFullscreen();
    setFullscreenData(false);
  } else {
    // The document is not in fullscreen mode
    videoContainer.requestFullscreen();
    setFullscreenData(true);
  }
}

如果瀏覽器當前處於全屏模式,則必須退出,反之亦然。有趣的是,document 必須用於退出/取消全屏模式,而任何 HTML 元素都可以請求全屏模式,這裡使用 videoContainer,因為它也包含自定義控制元件,這些控制元件也應該與影片一起出現在全屏模式下。

另一個使用者定義的函式 — setFullscreenData() — 也被呼叫,它在 videoContainer 上設定 data-fullscreen 屬性的值(這利用了 data-states)。

js
function setFullscreenData(state) {
  videoContainer.setAttribute("data-fullscreen", !!state);
}

這用於設定一些基本的 CSS,以改善自定義控制元件在全屏時的樣式(有關詳細資訊,請參閱示例程式碼)。當影片進入全屏模式時,它通常會顯示一條訊息,指示使用者可以按 Esc 鍵退出全屏模式,因此程式碼還需要監聽相關事件,以便呼叫 setFullscreenData() 函式來確保控制元件樣式正確。

js
document.addEventListener("fullscreenchange", (e) => {
  setFullscreenData(!!document.fullscreenElement);
});

另請參閱