影片和音訊 API
HTML 提供了用於在文件中嵌入富媒體的元素—— <video> 和 <audio> ——它們又帶有自己的 API,用於控制播放、定位等。本文將向你展示如何執行常見任務,例如建立自定義播放控制元件。
| 預備知識 | 熟悉 HTML、CSS 和 JavaScript,尤其是 JavaScript 物件基礎知識以及 DOM 指令碼和網路請求等核心 API 知識。 |
|---|---|
| 目標 |
|
HTML 影片和音訊
<video> 和 <audio> 元素允許我們將影片和音訊嵌入到網頁中。正如我們在 HTML 影片和音訊中所示,典型的實現如下
<video controls>
<source src="rabbit320.mp4" type="video/mp4" />
<source src="rabbit320.webm" type="video/webm" />
<p>
Your browser doesn't support HTML video. Here is a
<a href="rabbit320.mp4">link to the video</a> instead.
</p>
</video>
這會在瀏覽器中建立一個影片播放器,如下所示
你可以在上面連結的文章中檢視所有 HTML 功能的作用;對於我們在此處的目的,最有趣的屬性是 controls,它啟用了預設的播放控制元件集。如果你不指定此屬性,將不會獲得播放控制元件
這對於影片播放來說並不是立即可用的,但它確實有優點。原生瀏覽器控制元件的一個大問題是它們在每個瀏覽器中都不同——這對於跨瀏覽器支援來說不是很好!另一個大問題是大多數瀏覽器中的原生控制元件對鍵盤的可訪問性不是很好。
你可以透過隱藏原生控制元件(透過刪除 controls 屬性)並使用 HTML、CSS 和 JavaScript 程式設計自己的控制元件來解決這兩個問題。在下一節中,我們將介紹可用於執行此操作的基本工具。
HTMLMediaElement API
作為 HTML 規範的一部分,HTMLMediaElement API 提供了允許你以程式設計方式控制影片和音訊播放器的功能——例如 HTMLMediaElement.play()、HTMLMediaElement.pause() 等。此介面適用於 <audio> 和 <video> 元素,因為你想要實現的功能幾乎相同。讓我們透過一個示例,逐步新增功能。
我們完成的示例看起來(和功能)如下
入門
要開始此示例,請按照以下步驟操作
-
在你的硬碟上建立一個名為
custom-video-player的新目錄。 -
在其中建立一個名為
index.html的新檔案,並填充以下內容html<!doctype html> <html lang="en-gb"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Video player example</title> <link rel="stylesheet" type="text/css" href="style.css" /> </head> <body> <div class="player"> <video controls> <source src="/shared-assets/videos/sintel-short.mp4" type="video/mp4" /> <source src="/shared-assets/videos/sintel-short.webm" type="video/webm" /> </video> <div class="controls"> <button class="play" data-icon="P" aria-label="play pause toggle"></button> <button class="stop" data-icon="S" aria-label="stop"></button> <div class="timer"> <div></div> <span aria-label="timer">00:00</span> </div> <button class="rwd" data-icon="B" aria-label="rewind"></button> <button class="fwd" data-icon="F" aria-label="fast forward"></button> </div> </div> <p> Sintel © copyright Blender Foundation | <a href="https://studio.blender.org/films/sintel/" >studio.blender.org/films/sintel/</a >. </p> <script src="custom-player.js"></script> </body> </html> -
在其中建立另一個名為
style.css的新檔案,並填充以下內容css@font-face { font-family: "HeydingsControlsRegular"; src: url("https://mdn.github.io/learning-area/javascript/apis/video-audio/finished/fonts/heydings_controls-webfont.eot"); src: url("https://mdn.github.io/learning-area/javascript/apis/video-audio/finished/fonts/heydings_controls-webfont.eot?#iefix") format("embedded-opentype"), url("https://mdn.github.io/learning-area/javascript/apis/video-audio/finished/fonts/heydings_controls-webfont.woff") format("woff"), url("https://mdn.github.io/learning-area/javascript/apis/video-audio/finished/fonts/heydings_controls-webfont.ttf") format("truetype"); font-weight: normal; font-style: normal; } video { border: 1px solid black; } p { position: absolute; top: 310px; } .player { position: absolute; } .controls { visibility: hidden; opacity: 0.5; width: 400px; border-radius: 10px; position: absolute; bottom: 20px; left: 50%; margin-left: -200px; background-color: black; box-shadow: 3px 3px 5px black; transition: 1s all; display: flex; } .player:hover .controls, .player:focus-within .controls { opacity: 1; } button, .controls { background: linear-gradient(to bottom, #222222, #666666); } button::before { font-family: "HeydingsControlsRegular"; font-size: 20px; position: relative; content: attr(data-icon); color: #aaaaaa; text-shadow: 1px 1px 0px black; } .play::before { font-size: 22px; } button, .timer { height: 38px; line-height: 19px; box-shadow: inset 0 -5px 25px #0000004d; border-right: 1px solid #333333; } button { position: relative; border: 0; flex: 1; outline: none; } .play { border-radius: 10px 0 0 10px; } .fwd { border-radius: 0 10px 10px 0; } .timer { line-height: 38px; font-size: 10px; font-family: monospace; text-shadow: 1px 1px 0px black; color: white; flex: 5; position: relative; } .timer div { position: absolute; background-color: rgb(255 255 255 / 20%); left: 0; top: 0; width: 0; height: 38px; z-index: 2; } .timer span { position: absolute; z-index: 3; left: 19px; } button:hover, button:focus { box-shadow: inset 1px 1px 2px black; } button:active { box-shadow: inset 3px 3px 2px black; } .active::before { color: red; } -
在目錄中建立另一個名為
custom-player.js的新檔案。暫時留空。
此時,如果載入 HTML,你將看到一個完全正常的 HTML 影片播放器,並渲染了原生控制元件。
探索 HTML
開啟 HTML 索引檔案。你會看到許多功能;HTML 主要由影片播放器及其控制元件組成
- 整個播放器都包裹在一個
<div>元素中,因此如果需要,可以將其作為一個單元進行樣式設定。 <video>元素包含兩個<source>元素,以便根據檢視網站的瀏覽器載入不同的格式。- 控制元件 HTML 可能是最有趣的
- 我們有四個
<button>——播放/暫停、停止、快退和快進。 - 每個
<button>都有一個class名稱、一個用於定義每個按鈕應顯示哪個圖示的data-icon屬性(我們將在下面一節中展示其工作原理),以及一個aria-label屬性,用於提供每個按鈕的易於理解的描述,因為我們沒有在標籤內提供人類可讀的標籤。當螢幕閱讀器使用者聚焦於包含它們的元素時,aria-label屬性的內容會被螢幕閱讀器讀出。 - 還有一個計時器
<div>,它將在影片播放時報告已用時間。為了好玩,我們提供了兩種報告機制——一個包含以分鐘和秒為單位的已用時間的<span>,以及一個額外的<div>,我們將用它建立一個水平指示條,隨著時間的推移而變長。
- 我們有四個
探索 CSS
現在開啟 CSS 檔案並檢視內部。示例的 CSS 並不太複雜,但我們在此處重點介紹最有趣的部分。首先,請注意 .controls 樣式
.controls {
visibility: hidden;
opacity: 0.5;
width: 400px;
border-radius: 10px;
position: absolute;
bottom: 20px;
left: 50%;
margin-left: -200px;
background-color: black;
box-shadow: 3px 3px 5px black;
transition: 1s all;
display: flex;
}
.player:hover .controls,
.player:focus-within .controls {
opacity: 1;
}
- 我們首先將自定義控制元件的
visibility設定為hidden。稍後在我們的 JavaScript 中,我們將把控制元件設定為visible,並從<video>元素中刪除controls屬性。這樣,如果 JavaScript 因某種原因未能載入,使用者仍然可以使用帶有原生控制元件的影片。 - 我們預設給控制元件
opacity設定為0.5,這樣在觀看影片時它們就不會那麼分散注意力。只有當你懸停/聚焦在播放器上時,控制元件才會以完全不透明度顯示。 - 我們使用 Flexbox (
display: flex) 在控制欄內佈局按鈕,使事情變得更容易。
接下來,讓我們看看我們的按鈕圖示
@font-face {
font-family: "HeydingsControlsRegular";
src: url("https://mdn.github.io/learning-area/javascript/apis/video-audio/finished/fonts/heydings_controls-webfont.eot");
src:
url("https://mdn.github.io/learning-area/javascript/apis/video-audio/finished/fonts/heydings_controls-webfont.eot?#iefix")
format("embedded-opentype"),
url("https://mdn.github.io/learning-area/javascript/apis/video-audio/finished/fonts/heydings_controls-webfont.woff")
format("woff"),
url("https://mdn.github.io/learning-area/javascript/apis/video-audio/finished/fonts/heydings_controls-webfont.ttf")
format("truetype");
font-weight: normal;
font-style: normal;
}
button::before {
font-family: "HeydingsControlsRegular";
font-size: 20px;
position: relative;
content: attr(data-icon);
color: #aaaaaa;
text-shadow: 1px 1px 0px black;
}
首先,在 CSS 的頂部,我們使用 @font-face 塊匯入自定義網頁字型。這是一種圖示字型——字母表中的所有字元都等同於你可能希望在應用程式中使用的常見圖示。
接下來,我們使用生成的內容在每個按鈕上顯示一個圖示
- 我們使用
::before選擇器在每個<button>元素之前顯示內容。 - 我們使用
content屬性將每種情況下要顯示的內容設定為等於data-icon屬性的內容。在我們的播放按鈕的情況下,data-icon包含一個大寫字母 "P"。 - 我們使用
font-family將自定義網頁字型應用於我們的按鈕。在這種字型中,“P”實際上是一個“播放”圖示,因此播放按鈕上顯示了一個“播放”圖示。
圖示字型有許多優點——減少 HTTP 請求,因為你不需要將這些圖示作為影像檔案下載,出色的可伸縮性,以及你可以使用文字屬性(如 color 和 text-shadow)來設定它們的樣式。
最後但並非最不重要的一點,讓我們看看計時器的 CSS
.timer {
line-height: 38px;
font-size: 10px;
font-family: monospace;
text-shadow: 1px 1px 0px black;
color: white;
flex: 5;
position: relative;
}
.timer div {
position: absolute;
background-color: rgb(255 255 255 / 20%);
left: 0;
top: 0;
width: 0;
height: 38px;
z-index: 2;
}
.timer span {
position: absolute;
z-index: 3;
left: 19px;
}
- 我們將外部
.timer元素設定為flex: 5,因此它佔據了控制欄的大部分寬度。我們還將其設定為position: relative,以便我們可以根據其邊界而不是<body>元素的邊界方便地定位其中的元素。 - 內部的
<div>絕對定位,直接位於外部<div>的頂部。它的初始寬度也設定為 0,因此你根本看不到它。當影片播放時,其寬度將透過 JavaScript 隨著影片的流逝而增加。 <span>也絕對定位,位於計時器欄的左側附近。- 我們還為內部
<div>和<span>提供了適當的z-index,以便計時器顯示在頂部,內部<div>顯示在下面。這樣,我們確保可以看到所有資訊——一個框不會遮擋另一個框。
實現 JavaScript
我們已經有了一個相當完整的 HTML 和 CSS 介面;現在我們只需要連線所有按鈕以使控制元件工作。
-
在
custom-player.js檔案的頂部,插入以下程式碼jsconst media = document.querySelector("video"); const controls = document.querySelector(".controls"); const play = document.querySelector(".play"); const stop = document.querySelector(".stop"); const rwd = document.querySelector(".rwd"); const fwd = document.querySelector(".fwd"); const timerWrapper = document.querySelector(".timer"); const timer = document.querySelector(".timer span"); const timerBar = document.querySelector(".timer div");在這裡,我們正在建立常量來儲存對我們要操作的所有物件的引用。我們有三個組
<video>元素和控制欄。- 播放/暫停、停止、快退和快進按鈕。
- 外部計時器包裝器
<div>,數字計時器讀數<span>,以及隨著時間流逝而變寬的內部<div>。
-
接下來,將以下內容插入到程式碼的底部
jsmedia.removeAttribute("controls"); controls.style.visibility = "visible";這兩行程式碼將影片的預設瀏覽器控制元件移除,並使自定義控制元件可見。
播放和暫停影片
讓我們實現可能最重要的控制元件——播放/暫停按鈕。
-
首先,將以下內容新增到程式碼底部,以便在單擊播放按鈕時呼叫
playPauseMedia()函式jsplay.addEventListener("click", playPauseMedia); -
現在定義
playPauseMedia()——再次在程式碼底部新增以下內容jsfunction playPauseMedia() { if (media.paused) { play.setAttribute("data-icon", "u"); media.play(); } else { play.setAttribute("data-icon", "P"); media.pause(); } }在這裡我們使用
if語句檢查影片是否已暫停。HTMLMediaElement.paused屬性在媒體暫停時返回 true,這表示影片未播放的任何時間,包括首次載入後持續時間設定為 0 的情況。如果它已暫停,我們將播放按鈕上的data-icon屬性值設定為 "u",這是一個“已暫停”圖示,並呼叫HTMLMediaElement.play()方法播放媒體。第二次點選時,按鈕將再次切換回來——將再次顯示“播放”圖示,並且影片將透過
HTMLMediaElement.pause()暫停。
停止影片
-
接下來,讓我們新增功能來處理停止影片。在之前新增的行下方新增以下
addEventListener()行jsstop.addEventListener("click", stopMedia); media.addEventListener("ended", stopMedia);click事件是顯而易見的——我們希望在點選停止按鈕時透過執行stopMedia()函式來停止影片。但是,當影片播放結束時,我們也希望停止影片——這由ended事件觸發表示,因此我們也設定了一個監聽器,在事件觸發時執行該函式。 -
接下來,讓我們定義
stopMedia()——在playPauseMedia()下面新增以下函式jsfunction stopMedia() { media.pause(); media.currentTime = 0; play.setAttribute("data-icon", "P"); }HTMLMediaElement API上沒有
stop()方法——等效的方法是pause()影片,並將其currentTime屬性設定為 0。將currentTime設定為某個值(以秒為單位)會立即將媒體跳轉到該位置。之後要做的就是將顯示的圖示設定為“播放”圖示。無論在按下停止按鈕時影片是暫停還是播放,你都希望它之後能夠播放。
快退和快進
有多種方法可以實現快退和快進功能;這裡我們向你展示一種相對複雜的方法,它在以意外順序按下不同按鈕時不會中斷。
-
首先,在前面新增的行下方新增以下兩個
addEventListener()行jsrwd.addEventListener("click", mediaBackward); fwd.addEventListener("click", mediaForward); -
現在轉到事件處理程式函式——在你的前一個函式下面新增以下程式碼來定義
mediaBackward()和mediaForward()jslet intervalFwd; let intervalRwd; function mediaBackward() { clearInterval(intervalFwd); fwd.classList.remove("active"); if (rwd.classList.contains("active")) { rwd.classList.remove("active"); clearInterval(intervalRwd); media.play(); } else { rwd.classList.add("active"); media.pause(); intervalRwd = setInterval(windBackward, 200); } } function mediaForward() { clearInterval(intervalRwd); rwd.classList.remove("active"); if (fwd.classList.contains("active")) { fwd.classList.remove("active"); clearInterval(intervalFwd); media.play(); } else { fwd.classList.add("active"); media.pause(); intervalFwd = setInterval(windForward, 200); } }你會注意到,我們首先初始化了兩個變數——
intervalFwd和intervalRwd——你稍後會發現它們的作用。讓我們逐步講解
mediaBackward()(mediaForward()的功能完全相同,但方向相反)- 我們清除了快進功能上設定的所有類和間隔——我們這樣做是因為如果我們在按下
fwd按鈕後按下rwd按鈕,我們希望取消任何快進功能並將其替換為快退功能。如果同時嘗試執行這兩者,播放器將崩潰。 - 我們使用
if語句檢查rwd按鈕上是否已設定active類,這表明它已被按下。classList是一個相當方便的屬性,存在於每個元素上——它包含元素上設定的所有類的列表,以及用於新增/刪除類等的方法。我們使用classList.contains()方法檢查列表是否包含active類。這返回布林值true/false結果。 - 如果
rwd按鈕上已設定active,我們使用classList.remove()將其刪除,清除首次按下按鈕時設定的間隔(有關更多解釋,請參閱下文),並使用HTMLMediaElement.play()取消快退並開始正常播放影片。 - 如果尚未設定,我們使用
classList.add()將active類新增到rwd按鈕,使用HTMLMediaElement.pause()暫停影片,然後將intervalRwd變數設定為等於setInterval()呼叫。當呼叫時,setInterval()會建立一個活動的間隔,這意味著它每 x 毫秒執行一次作為第一個引數給定的函式,其中 x 是第二個引數的值。因此,我們這裡每 200 毫秒執行一次windBackward()函式——我們將使用此函式持續倒回影片。要停止setInterval()執行,你必須呼叫clearInterval(),並給出要清除的間隔的識別名稱,在這種情況下是變數名intervalRwd(請參閱函式中前面部分的clearInterval()呼叫)。
- 我們清除了快進功能上設定的所有類和間隔——我們這樣做是因為如果我們在按下
-
最後,我們需要定義在
setInterval()呼叫中呼叫的windBackward()和windForward()函式。在你的兩個前一個函式下面新增以下內容jsfunction windBackward() { if (media.currentTime <= 3) { rwd.classList.remove("active"); clearInterval(intervalRwd); stopMedia(); } else { media.currentTime -= 3; } } function windForward() { if (media.currentTime >= media.duration - 3) { fwd.classList.remove("active"); clearInterval(intervalFwd); stopMedia(); } else { media.currentTime += 3; } }同樣,我們將只執行其中第一個函式,因為它們的工作方式幾乎相同,但彼此相反。在
windBackward()中,我們執行以下操作——請記住,當間隔處於活動狀態時,此函式每 200 毫秒執行一次。- 我們首先使用一個
if語句,檢查當前時間是否小於 3 秒,即,如果再倒退三秒鐘會使其回退到影片的開頭。這會導致奇怪的行為,因此如果出現這種情況,我們透過呼叫stopMedia()停止影片播放,從快退按鈕中刪除active類,並清除intervalRwd間隔以停止快退功能。如果我們不執行最後一步,影片將永遠倒帶。 - 如果當前時間不在影片開始的 3 秒內,我們透過執行
media.currentTime -= 3從當前時間中減去 3 秒。因此,實際上,我們每 200 毫秒將影片倒退 3 秒。
- 我們首先使用一個
更新已用時間
我們媒體播放器要實現的最後一個功能是已用時間顯示。為此,我們將在 <video> 元素上每次觸發 timeupdate 事件時執行一個函式來更新時間顯示。此事件觸發的頻率取決於你的瀏覽器、CPU 效能等(請參閱此 Stack Overflow 帖子)。
-
在其他行下方新增以下
addEventListener()行jsmedia.addEventListener("timeupdate", setTime); -
現在定義
setTime()函式。將以下內容新增到檔案的底部jsfunction setTime() { const minutes = Math.floor(media.currentTime / 60); const seconds = Math.floor(media.currentTime - minutes * 60); const minuteValue = minutes.toString().padStart(2, "0"); const secondValue = seconds.toString().padStart(2, "0"); const mediaTime = `${minuteValue}:${secondValue}`; timer.textContent = mediaTime; const barLength = timerWrapper.clientWidth * (media.currentTime / media.duration); timerBar.style.width = `${barLength}px`; }
這是一個相當長的函式,所以讓我們一步一步地進行
- 首先,我們計算
HTMLMediaElement.currentTime值中的分鐘和秒數。 - 然後我們初始化另外兩個變數——
minuteValue和secondValue。我們使用padStart()使每個值長度為 2 個字元,即使數字值只有一個數字。 - 要顯示的實際時間值設定為
minuteValue加上一個冒號字元再加上secondValue。 - 計時器的
Node.textContent值被設定為時間值,因此它會在 UI 中顯示。 - 內層
<div>的長度應透過以下方式計算:首先計算外層<div>的寬度(任何元素的clientWidth屬性將包含其長度),然後將其乘以HTMLMediaElement.currentTime除以媒體的總HTMLMediaElement.duration。 - 我們將內部
<div>的寬度設定為等於計算出的條形長度,加上“px”,這樣它將被設定為該畫素數。
修復播放和暫停
還有一個問題需要解決。如果播放/暫停或停止按鈕在快退或快進功能處於活動狀態時被按下,它們就無法工作。我們如何修復它,使它們取消 rwd/fwd 按鈕功能並像你期望的那樣播放/停止影片?這很容易修復。
-
首先,在
stopMedia()函式中新增以下幾行——任何位置都可以jsrwd.classList.remove("active"); fwd.classList.remove("active"); clearInterval(intervalRwd); clearInterval(intervalFwd); -
現在,在
playPauseMedia()函式的最開始(就在if語句開始之前)再次新增相同的行。 -
此時,你可以從
windBackward()和windForward()函式中刪除等效的行,因為該功能已在stopMedia()函式中實現。
注意:你還可以透過建立一個單獨的函式來執行這些行,然後在需要的地方呼叫它,而不是在程式碼中多次重複這些行,從而進一步提高程式碼的效率。但我們將把這個留給你。
總結
我認為我們在這篇文章中已經教你足夠了。HTMLMediaElement API 提供了豐富的功能來建立簡單的影片和音訊播放器,但這僅僅是冰山一角。請參閱下面的“另請參閱”部分,獲取指向更復雜和有趣功能的連結。
以下是一些你可以增強我們已構建的現有示例的方法建議
-
如果影片時長達到一小時或更長(嗯,它不會顯示小時,只會顯示分鐘和秒),當前時間顯示就會中斷。你能想出如何修改示例以使其顯示小時嗎?
-
因為
<audio>元素也具有相同的HTMLMediaElement功能,所以你也可以輕鬆地讓這個播放器適用於<audio>元素。嘗試這樣做。 -
你能想出一種方法,將計時器內部的
<div>元素變成一個真正的進度條/捲軸嗎——也就是說,當你點選條上的某個位置時,它會跳到影片播放中的那個相對位置?作為提示,你可以透過getBoundingClientRect()方法獲取元素的左/右和上/下側的 X 和 Y 值,你還可以透過在Document物件上呼叫的點選事件的事件物件獲取滑鼠點選的座標。例如jsdocument.onclick = function (e) { console.log(e.x, e.y); };
另見
HTMLMediaElement- HTML 影片和音訊——
<video>和<audio>HTML 的簡單指南。 - 音訊和影片傳輸——瀏覽器內部媒體傳輸的詳細指南,包含許多提示、技巧和指向更高階教程的連結。
- 音訊和影片操作——使用 Canvas API、Web Audio API 等操作音訊和影片的詳細指南。
<video>和<audio>參考頁面。- Web 媒體型別和格式指南