跨瀏覽器音訊基礎

本文件提供

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

基本音訊示例

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

html
<audio controls>
  <source src="audiofile.mp3" type="audio/mpeg" />
  <source src="audiofile.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="audiofile.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 屬性。如果您未指定此屬性,則不會顯示任何控制元件——您將不得不使用媒體 API 建立自己的控制元件並對其功能進行程式設計(見下文)。但是,這可能是一種好方法,因為預設控制元件在各種瀏覽器中看起來不同。因此,建立自己的控制元件可以確保控制元件在所有瀏覽器中都具有一致的外觀。

HTML 音訊詳解

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

音訊 HTML 屬性

我們可以使用音訊標籤指定許多屬性,以進一步確定音訊的初始化方式。

autoplay

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

html
<audio autoplay></audio>

**注意:**此值在移動平臺上經常被忽略,除非確實需要,否則不建議使用它。自動播放音訊(和影片)通常非常煩人。此外,瀏覽器具有策略,在許多情況下會完全阻止自動播放。有關詳細資訊,請參見 媒體和 Web 音訊 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="audiofile.mp3"></audio>

type

如上所述,為了確保瀏覽器知道正在指定的檔案型別,最好在 src 屬性旁邊指定 type 屬性。type 屬性指定檔案的 MIME 型別或 Internet 媒體型別。

html
<audio src="audiofile.mp3" type="audio/mpeg"></audio>

使用 JavaScript 操作音訊元素

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

假設以下 HTML

html
<audio id="my-audio" src="audiofile.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", "audiofile.mp3");
}

if (audio.canPlayType("audio/ogg")) {
  audio.setAttribute("src", "audiofile.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();

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

canPlayType

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

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

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

  1. probably
  2. maybe
  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="audiofile.mp3" type="audio/mpeg" />
  <source src="audiofile.ogg" type="audio/ogg" />
  <!-- place fallback here as <audio> supporting browsers will ignore it -->
  <p>Download<a href="audiofile.mp3">audiofile.mp3</a></p>
</audio>

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

接下來,我們使用 JavaScript 將一些功能附加到播放器

js
window.onload = () => {
  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

canplaythroughcanplay 類似,但它讓您知道媒體已準備好播放完整內容(即檔案已完全下載,或估計它將在緩衝停止發生之前完成下載)。

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

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

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

playing

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

waiting

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

play

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

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="audiofile.mp3">audiofile.mp3</a>
</audio>

<div id="controls">
  <span id="loading">loading</span>
  <button id="play" style="display:none">play</button>
  <button id="pause" style="display:none">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;
}

現在讓我們使用 JavaScript 連線它

js
window.onload = () => {
  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

seekedseeking 屬性更改為 false 時發生。

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

另請參見