跨瀏覽器音訊基礎

本文提供

  • 一個建立跨瀏覽器 HTML 音訊播放器的基本指南,其中解釋了所有相關的屬性、特性和事件
  • 一個使用 Media API 建立自定義控制元件的指南

基本音訊示例

以下程式碼是使用 HTML5 實現基本音訊的示例

html
<audio controls>
  <source src="audio-file.mp3" type="audio/mpeg" />
  <source src="audio-file.ogg" type="audio/ogg" />
  <!-- fallback for non-supporting browsers goes here -->
  <p>
    Your browser does not support HTML audio, but you can still
    <a href="audio-file.mp3">download the music</a>.
  </p>
</audio>

注意:您也可以使用 MP4 檔案代替 MP3。MP4 檔案通常包含 AAC 編碼的音訊。您可以使用 type="audio/mp4"。(目前,支援 mp3 的瀏覽器也支援 mp4 音訊)。

  • 這裡我們定義了一個帶有多個源的 <audio> 元素 — 我們這樣做是因為並非所有瀏覽器都支援相同的音訊格式。為了確保合理的覆蓋範圍,我們應該至少指定兩種不同的格式。能提供最大覆蓋範圍的兩種格式是 mp3 和 ogg vorbis。

  • 我們透過使用 <source> 元素來做到這一點,該元素帶有 srctype 屬性。

    • src 包含要載入的音訊檔案的路徑(相對或絕對)。
    • type 用於通知瀏覽器檔案型別。如果省略,大多數瀏覽器會嘗試從副檔名猜測。
  • 如果不支援 <audio> 元素,那麼 <audio><source> 將被忽略。但是,您在 <audio> 元素中定義的任何受支援的文字或元素都將被顯示或執行。因此,建立備用方案或告知不相容性的理想位置是在結束 </audio> 標籤之前。在這種情況下,我們提供了一個段落,其中包含一個直接下載音訊的連結。

  • 當我們需要瀏覽器為我們提供預設播放控制元件時,在 <audio> 元素上指定 controls 屬性。如果您不指定此屬性,則不會出現任何控制元件——您將不得不建立自己的控制元件並使用 Media API 程式設計其功能(見下文)。然而,這可能是一個好方法,因為預設控制元件在不同瀏覽器中的外觀不同。因此,建立自己的控制元件可以確保所有瀏覽器上的控制元件外觀一致。

HTML 音訊詳解

現在我們已經看了一個基本示例,接下來讓我們更詳細地探討 HTML 音訊的不同方面。

音訊 HTML 屬性

我們可以為音訊元素指定多個屬性,以進一步決定音訊的初始化方式。

autoplay

指定 autoplay 將導致音訊儘快播放,無需任何使用者互動——簡而言之,音訊將自動播放。

html
<audio autoplay>…</audio>

注意:此值在移動平臺上通常會被忽略,並且除非確實必要,否則不建議使用。自動播放音訊(和影片)通常非常煩人。此外,瀏覽器有政策會在許多情況下完全阻止自動播放。有關詳細資訊,請參閱媒體和 Web Audio API 的自動播放指南

loop

loop 屬性將確保當音訊剪輯播放到末尾時,音訊剪輯將迴圈回到開頭並重新開始播放。

html
<audio loop>…</audio>

muted

如果您希望音訊一開始就靜音(沒有音量),請新增 muted 屬性。

html
<audio muted>…</audio>

注意:此值在移動平臺上通常會被忽略。

preload

preload 屬性允許您指定瀏覽器預載入音訊的首選項,換句話說,當 <audio> 元素初始化時,以及在按下播放按鈕之前,它會下載檔案的哪個部分。

preload 可以採用 3 種不同的值

  1. none:在按下播放按鈕之前不下載任何內容。
  2. metadata:下載音訊元資料;這通常是最佳選項,因為它允許您訪問和顯示音訊長度等資訊,並允許瀏覽器確定它應該使用哪個音訊檔案。
  3. auto:儘快下載整個音訊檔案。這通常不是一個好的選項,除非您能保證您的使用者將擁有快速的網路連線。

注意:此值在移動平臺上通常會被忽略。

html
<audio preload="auto">…</audio>

controls

當我們需要瀏覽器為我們提供其預設播放控制元件時,我們指定 controls 屬性。

html
<audio controls>…</audio>

src

如上所述,您可以使用 <source> 元素來指定一個或多個源音訊檔案。或者,您可以直接在 <audio> 元素上包含 src 屬性來指定單個原始檔。

html
<audio src="audio-file.mp3">…</audio>

type

如上所述,為了確保瀏覽器知道正在指定的檔案型別,最佳實踐是與 src 屬性一起指定 type 屬性。type 屬性指定檔案的 MIME 型別或 Internet Media Type。

html
<audio src="audio-file.mp3" type="audio/mpeg">…</audio>

使用 JavaScript 操作音訊元素

除了能夠在 HTML 中指定各種屬性外,<audio> 元素還附帶了一些可以透過 JavaScript 操作的屬性和方法。

給定以下 HTML

html
<audio id="my-audio" src="audio-file.mp3">…</audio>

您可以這樣獲取 <audio> 元素

js
const audio = document.getElementById("my-audio");

或者,您可以建立一個新元素。這是一個建立 <audio> 元素,設定媒體播放,播放和暫停,然後從音訊的 5 秒處開始播放的示例

js
const audio = document.createElement("audio");

if (audio.canPlayType("audio/mpeg")) {
  audio.setAttribute("src", "audio-file.mp3");
}

if (audio.canPlayType("audio/ogg")) {
  audio.setAttribute("src", "audio-file.ogg");
}

alert("play");

audio.play();

alert("stop");

audio.pause();

alert("play from 5 seconds in");

audio.currentTime = 5;
audio.play();

讓我們更詳細地探討可用的屬性和方法。

play

play() 方法用於指示音訊播放。它不帶任何引數。

js
audio.play();

pause

pause() 方法用於指示音訊暫停。它不帶任何引數。

js
audio.pause();

注意:沒有 stop 方法——要實現停止功能,您必須暫停媒體,然後將 currentTime 屬性值設定為 0。

canPlayType

canPlayType() 方法詢問瀏覽器是否支援某種音訊檔案型別。它將要檢查的 mime 型別作為引數。

js
if (audio.canPlayType("audio/mpeg")) {
  // It's supported.
  // Do something here!
}

canPlayType() 返回以下三個值之一

  1. 大概
  2. 可能
  3. ""(一個空字串)

實際上,我們通常檢查結果是真還是假。非空字串為真。

注意:一個很早期的規範規定瀏覽器應該返回 no 而不是空字串,但幸運的是,使用實現此版本規範的舊版瀏覽器的人很少。

currentTime

currentTime 屬性獲取或設定音訊應播放的當前時間。這在許多方面都很有用,例如,由於 play() 不帶引數,如果我們不想從 0 開始播放,我們需要單獨設定播放點。

currentTime 的值是一個數字,表示以秒為單位的時間。

js
if (audio.currentTime > 5) {
  audio.currentTime = 3;
}

volume

volume 屬性允許我們將音訊音量設定為 0 到 1 之間的數字。

js
// set the volume at 50%
audio.volume = 0.5;

建立您自己的自定義音訊播放器

JavaScript 媒體 API 允許您建立自己的自定義播放器。讓我們看一個非常簡單的示例。我們可以結合 HTML 和 JavaScript 來建立一個帶有播放和暫停按鈕的播放器。首先,我們將在 HTML 中設定音訊,不帶 controls 屬性,因為我們正在建立自己的控制元件

html
<audio id="my-audio">
  <source src="audio-file.mp3" type="audio/mpeg" />
  <source src="audio-file.ogg" type="audio/ogg" />
  <!-- place fallback here as <audio> supporting browsers will ignore it -->
  <p>Download<a href="audio-file.mp3">audio-file.mp3</a></p>
</audio>

<!-- custom play and pause buttons -->
<button id="play">play</button>
<button id="pause">pause</button>

接下來,我們使用 JavaScript 為播放器新增一些功能

js
const audio = document.getElementById("my-audio");
const play = document.getElementById("play");
const pause = document.getElementById("pause");

// associate functions with the 'onclick' events
play.onclick = playAudio;
pause.onclick = pauseAudio;

function playAudio() {
  audio.play();
}

function pauseAudio() {
  audio.pause();
}

媒體載入事件

上面我們已經展示瞭如何建立一個音訊播放器,但是如果我們想顯示進度、緩衝,並且只在媒體準備好播放時啟用按鈕呢?幸運的是,我們有許多事件可以使用,讓我們的播放器準確地知道發生了什麼。

首先,讓我們按順序檢視媒體載入過程

loadstart

loadstart 事件告訴我們載入過程已開始,瀏覽器正在連線到媒體。

js
audio.addEventListener("loadstart", () => {
  // Grabbing the file
});

durationchange

如果您只想儘快知道媒體的持續時間是否已確定,那麼這就是適合您的事件。這可能很有用,因為持續時間的初始值為 NaN(非數字),您可能不想將其顯示給您的使用者。

js
audio.addEventListener("durationchange", () => {
  // You can display the duration now
});

loadedmetadata

元資料可以包含的不僅僅是持續時間——如果您想等到所有元資料下載完畢後再執行某些操作,您可以檢測 loadedmetadata 事件。

js
audio.addEventListener("loadedmetadata", () => {
  // You can display the duration now
});

loadeddata

當媒體的第一部分到達時,會觸發 loadeddata 事件。播放頭已就位但尚未完全準備好播放。

js
audio.addEventListener("loadeddata", () => {
  // You could display the playhead now
});

progress

progress 事件表示媒體的下載仍在進行中。此時顯示某種“載入器”是一個好習慣。

js
audio.addEventListener("progress", () => {
  // you could let the user know the media is downloading
});

canplay

canplay 是一個有用的事件,如果您想確定媒體是否已準備好播放,則應檢測此事件。例如,您可以在此事件發生之前停用自定義控制元件。

js
audio.addEventListener("canplay", () => {
  // Audio is ready to play
});

canplaythrough

canplaythrough 類似於 canplay,但它會告訴您媒體已準備好全程播放(也就是說,檔案已完全下載,或者預計它會及時下載,從而不會發生緩衝停止)。

js
audio.addEventListener("canplaythrough", () => {
  // Audio is ready to play all the way through
});

媒體載入事件順序

回顧一下,媒體載入事件的順序是

loadstart > durationchange > loadedmetadata > loadeddata > progress > canplay > canplaythrough

載入中斷事件

我們還有一些事件可用,當媒體載入過程出現某種中斷時,這些事件會觸發。

suspend

媒體資料不再被獲取,即使檔案尚未完全下載。

abort

媒體資料下載已中止,但並非由於錯誤。

error

下載媒體資料時遇到錯誤。

emptied

媒體緩衝區已清空,可能是由於錯誤或呼叫了 load() 方法來重新載入它。

stalled

媒體資料意外地不再可用。

媒體播放事件

我們還有另一組事件,這些事件對於響應媒體播放狀態很有用。

timeupdate

每次 currentTime 屬性更改時都會觸發 timeupdate 事件。實際上,這每 250 毫秒發生一次。此事件可用於觸發播放進度的顯示。

js
audio.addEventListener("timeupdate", () => {
  // Update something related to playback progress
});

playing

當播放因缺少媒體資料而暫停後準備開始時,會啟動 playing 事件。

waiting

當播放因缺少媒體資料而停止時,會觸發 waiting 事件,儘管預計一旦資料可用就會恢復播放。

play

play() 方法返回後,或者當 autoplay 屬性導致播放開始時,會啟動 play 事件。這是媒體狀態從暫停切換到播放的時候。

pause

pause() 方法返回後觸發 pause 事件。這是狀態從播放切換到暫停的時候。

ended

當媒體播放結束時,會啟動 ended 事件。

js
audio.addEventListener("ended", () => {
  // Do something once audio track has finished playing
});

volumechange

volumechange 事件表示音量已更改;這包括靜音。

帶反饋的音訊播放器

考慮這段 HTML 程式碼

html
<audio id="my-audio">
  <source
    src="http://jPlayer.org/audio/mp3/Miaow-07-Bubble.mp3"
    type="audio/mpeg" />
  <source
    src="http://jPlayer.org/audio/ogg/Miaow-07-Bubble.ogg"
    type="audio/ogg" />
  <!-- place fallback here as <audio> supporting browsers will ignore it -->
  <a href="audio-file.mp3">audio-file.mp3</a>
</audio>

<div id="controls">
  <span id="loading">loading</span>
  <button id="play">play</button>
  <button id="pause">pause</button>
</div>
<div id="progress">
  <div id="bar"></div>
</div>

樣式如下

css
#controls {
  width: 80px;
  float: left;
}

#progress {
  margin-left: 80px;
  border: 1px solid black;
}

#bar {
  height: 20px;
  background-color: green;
  width: 0;
}

#play,
#pause {
  display: none; /* hide until media is ready */
}

現在讓我們用 JavaScript 將它連線起來

js
const audio = document.getElementById("my-audio");
const play = document.getElementById("play");
const pause = document.getElementById("pause");
const loading = document.getElementById("loading");
const bar = document.getElementById("bar");

function displayControls() {
  loading.style.display = "none";
  play.style.display = "block";
}

// Check that the media is ready before displaying the controls
if (audio.paused) {
  displayControls();
} else {
  // not ready yet - wait for canplay event
  audio.addEventListener("canplay", () => {
    displayControls();
  });
}

play.addEventListener("click", () => {
  audio.play();
  play.style.display = "none";
  pause.style.display = "block";
});

pause.addEventListener("click", () => {
  audio.pause();
  pause.style.display = "none";
  play.style.display = "block";
});

// Display progress
audio.addEventListener("timeupdate", () => {
  // Sets the percentage
  bar.style.width = `${Math.floor(
    (audio.currentTime / audio.duration) * 100,
  )}%`;
});

您最終應該得到類似這樣的結果

A basic audio player with play/pause button and seek bar

使用搜索欄進行搜尋

這是一個很好的開始,但如果能夠使用進度條導航音訊,那就更好了。幸運的是,這實現起來並不太困難。

首先,我們對進度條的 CSS 進行快速更新,以便在懸停時顯示手形指標

css
#progress {
  margin-left: 80px;
  border: 1px solid black;
  cursor: pointer;
}

然後我們新增檢測點選並將“播放頭”移動到正確位置的程式碼

js
const progress = document.getElementById("progress");

progress.addEventListener("click", (e) => {
  // Calculate the normalized position clicked
  const clickPosition = (e.pageX - progress.offsetLeft) / progress.offsetWidth;
  const clickTime = clickPosition * audio.duration;

  // Move the playhead to the correct position
  audio.currentTime = clickTime;
});

緩衝

好的,我們快完成了,但還有另一條有用的資訊可以顯示:已緩衝或預先下載的音訊量。

我們還沒有看過幾個屬性,bufferedseekable

buffered

此屬性讓我們知道音訊的哪些部分已被緩衝(預先下載)。它返回一個名為 TimeRanges 的物件。

js
bufferedTimeRanges = audio.buffered;

seekable

seekable 屬性告知您是否可以直接跳轉到媒體的該部分而無需進一步緩衝。

js
seekableTimeRanges = audio.seekable;

緩衝事件

還有幾個與緩衝相關的事件

seeking

當媒體正在搜尋時,會觸發 seeking 事件。

seeked

seeking 屬性變為 false 時,會發生 seeked

注意:您可以在其他地方閱讀更多關於緩衝、搜尋和時間範圍的資訊。

另見