跨浏览器音频基础

本文提供

  • 一个创建跨浏览器 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

注意:您可以在其他地方阅读更多关于缓冲、搜索和时间范围的信息。

另见