HTML - 如何高效使用 <audio> 标签

HTML - 如何高效使用 <audio> 标签

如果想要在页面中添加一段录音或音乐,有什么办法呢?本文将会介绍如何高效使用 <audio> 标签。

文章的英文版请戳我的 Medium . To read the article in English, please visit my Medium .


1. <audio> 标签的基本用法

1.1 <audio> 标签的属性

我们来一起看看 <audio> 标签的常用属性:

  1. src src 属性的值包含了嵌入音频的文件路径。
  2. controls : 如果 controls 属性被声明,浏览器将提供一个包含声音、播放进度、播放暂停的控制面板,让用户可以控制音频的播放。
  3. autoplay : 如果 autoplay 属性被声明,音频会尽快自动播放,不会等待整个音频文件下载完成。
  4. loop : 如果 loop 属性被声明,将循环播放音频。
  5. muted : 如果 muted 属性被声明,音频将被静音。
  6. preload preload 属性的值示意了浏览器使用何种加载方式以达到最好的用户体验。它的值可以是 none (音频不会被缓存)、 meta (获取例如音频长度的音频元数据)或 auto (整个音频都将被加载)。如果 autoplay 属性已经被声明时, preload 属性将被忽略。
  7. disableRemotePlayback : 如果 disableRemotePlayback 属性被声明,浏览器将禁用远程设备上进行进度控制的能力。
<!-- 一个自动播放、初始静音、循环播放、有控制面板的音频播放器 -->
<audio
 src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3"
 controls
 autoplay
 muted
  浏览器不支持音频播放。
</audio> 

如果想要看有关 <audio> 标签更多的示例代码,可以戳我的 CodePen

如果想要更完整地了解 <audio> 标签,可以看看 MDN - <audio>

1.2 如何预加载音频文件?

我们需要预加载音频文件吗?这完全取决于音频的作用。我们在 1.1 部分介绍了 preload 属性的 3 个值: none meta auto 。 我们一起来看看何时使用哪个值:

  1. none none 值表示了音频不会被预加载,这样会减少网络流量的使用。 一个典型的场景就是播客。当用户浏览一个列表的不同集数时,我们不需要准备所有的音频文件,只有当用户点击一个单集的“播放”按钮时,我们再去请求这个单集的音频数据。
Spotify 播客

2. meta meta 值表示了只获取音频的元数据。 一个典型的场景就是当你打开一个带有音频播放器的网页时,你可能不会马上开始播放音频,但我们需要相关的元数据来补充播放器的内容。例如,当你打开 Spotify 的主页时,页脚的播放器会展示你上一次播放的音频,但因为你也许会播放其它音频,所以这段音频不会直接被自动播放。

Spotify 主页

3. auto auto 值会在加载页面后加载整个音频文件。 一个典型的场景就是游戏。因为游戏过程中需要很多的背景音乐、声效等,所以在游戏加载时,我们也需要同时加载需要的音频文件。

为了解决一些移动设备 不支持 preload 属性, 我们可以使用 load() 方法来让 <audio> 元素加载音频。

<audio
 id="audio-player"
 src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3"
 preload="auto"
 controls
  浏览器不支持音频播放。
</audio>
window.addEventListener('load', () => {
  document.queryselector('#audio-player').load();
});

1.3 音频文件格式

HTML 支持 3 种音频文件格式: OGG、 MP3 和 WAV。

  1. OGG: OGG 文件没有被压缩,所以音质是 3 种文件格式中最好的,但同时文件大小也是最大的。
  2. MP3: MP3 文件被压缩过,所以文件大小会略小一些,同时音质也会低于 OGG 文件。另外, MP3 格式是最普遍使用的文件格式。
  3. WAV: WAV 文件也是被压缩过,文件大小是 3 种文件格式中最小的,但音质也是最差的。

如果想要查看浏览器对 3 种音频文件格式的兼容性,可以查看 Can I use audio format? 。如果我们想要解决兼容性的问题,我们可以把三种文件格式都包含在 <audio> 标签里,让浏览器选择最合适的文件格式来播放。

<audio controls>
  <!-- OGG 文件 -->
  <source src="music.ogg" type="audio/ogg" />
  <!-- MP3 文件 -->
  <source src="music.mp3" type="audio/mpeg" />
  <!-- WAV 文件 -->
  <source src="music.wav" type="audio/wav" />
</audio>

引用

HTML5 audio标签使用 preload属性 规定是否在页面加载后载入音频

HTML5 Audio的兼容性问题和优化

HTML Audio

Smallest audio file: MP3, Ogg, or Wav?


2. 操作 <audio> 元素

2.1 HTMLAudioElement 接口

HTMLAudioElement 接口提供了可以访问 <audio> 元素的属性,以及操作它们的方法。

最常用的 <audio> 元素属性有:

  1. src src 属性指定了元素中使用的音频资源的 URL。
  2. currentTime currentTime 属性以秒为单位返回当前音频元素的播放时间。
  3. duration duration 属性以秒为单位给出音频的长度。
  4. paused paused 属性指出音频是否正在暂停。
  5. playbackRate playbackRate 属性设置了音频文件播放时的速率。
  6. muted muted 属性指出音频是否被静音。
  7. volume volume 属性设置了音频播放时的音量。

最常用的操作 <audio> 元素的方法有:

  1. load() load() 方法让 <audio> 元素重新加载,让播放也回到起始点。
  2. ▶️ play() play() 方法尝试让音频开始播放。
  3. pause() pause() 方法暂停音频播放。

如果想要更完整地了解 HTMLAudioElement HTMLMediaElement 接口,可以看看 MDN - HTMLAudioElement MDN - HTMLMediaElement

2.2 <audio> 标签的事件

<audio> 标签支持很多的事件来让我们了解音频的加载以及播放进度。最常用的事件有:

  1. loadedmetadata : 元数据加载完成。
  2. canplay : 浏览器已经可以播放音频,但是预测加载的数据不足以在不暂停的情况下顺利将其播放到结束。
  3. canplaythrough : 浏览器预测已经可以在不暂停的前提下将音频播放到结束。
  4. stalled : 用户代理是图获取音频数据,但数据意外地没有进入。
  5. suspend : 音频加载挂起。
  6. play : 播放开始。
  7. pause : 播放暂停。
  8. waiting : 因为暂时性缺少数据,播放暂停。
  9. playing : 因缺少数据而暂停或延迟的状态结束,播放准备开始。
  10. seeking : 一次获取操作开始。
  11. seeked : 一次获取操作结束。
  12. ratechange : 播放速率变化。
  13. timeupdate HTMLAudioElement.currentTime 属性指定的时间更新。
  14. volumechange : 音量变化。
  15. ended : 播放到音频的结束为止,播放停止。
<audio
 id="audio-player"
 src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3"
 controls
  浏览器不支持音频播放。
</audio> 


const audio = document.querySelector('#audio-player');
// canplay
audio.addEventListener('canplay', () => {
  alert('You can play the audio now.');
// play
audio.addEventListener('play', () => {
  alert('The audio is playing now!');
// pause
audio.addEventListener('pause', () => {
  alert('The audio is paused to play.');
// seeked
audio.addEventListener('seeked', () => {
  alert(`You have changed the current playback time to ${Math.ceil(audio.currentTime)}s.`);
// timeupdate
audio.addEventListener('timeupdate', () => {
  if (Math.ceil(audio.currentTime) % 30 === 0) {
    alert(`The current playback time is ${Math.ceil(audio.currentTime / 60)}min(s).`);
// ended
audio.addEventListener('ended', () => {
  alert('Audio finished playing.');

如果想要看更多有关 <audio> 标签事件的示例代码,可以戳我的 CodePen

2.3 在实际情况操作 <audio> 元素

使用 <audio> 标签时会有很多的兼容性问题,我们一起来看看这些问题和它们的解决办法。我们也会看到一些操作 <audio> 元素来增加用户体验的例子 。

1. 如何查看音频播放结束?

想要查看 <audio> 元素是否播放到结尾了,监听 ended 事件不够准确,我们可以通过监听 timeupdate 事件来查看 currentTime 的值是否已经大等于 duration

<audio
 id="audio-player"
 src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3"
 controls
  浏览器不支持音频播放。
</audio>
const audio = document.querySelector('#audio-player');
audio.addEventListener('timeupdate', () => {
  if (audio.currentTime >= audio.duration) {
    alert('Audio finished playing');
});

2. 如何添加加载效果?

为了提高用户体验,我们可以在音频加载的过程中添加加载效果。我们可以通过监听 loadstart loadeddata 事件来让显示加载效果,然后当 canplay 事件被触发时移除加载效果。

<audio
 id="audio-player"
 src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3"
 controls
  浏览器不支持音频播放。
</audio> 


const audio = document.querySelector('#audio-player');
let isLoading = false;
audio.addEventListener('loadstart', () => {
  isLoading = true;
audio.addEventListener('loadeddata', () => {
  isLoading = true;
audio.addEventListener('canplay', () => {
  isLoading = false;

3. 如何自动播放音频?

为了解决一些移动设备 不支持 autoplay 属性,我们可以靠调用 play() 方法来播放音频。

<audio
 id="audio-player"
 src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3"
 controls
  浏览器不支持音频播放。
</audio> 


window.addEventListener('canplay', () => {
  document.queryselector('#audio-player').play();

4. 如何连续播放多个音频资源?

一种方法是在音频播放结束的时候修改 src 属性的值。

<audio
 id="audio-player"
 src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3"
 controls
  浏览器不支持音频播放。
</audio> 


const audio = document.querySelector('#audio-player');
let songNum = 1;
audio.addEventListener('timeupdate', () => {
  if (audio.currentTime >= audio.duration) {
    songNum = songNum >= 17 ? 1 : songNum + 1;
    audio.src = `https://www.soundhelix.com/examples/mp3/SoundHelix-Song-${songNum}.mp3`;

另一种方法是在一开始的时候通过多个 <audio> 标签预加载所有的音频资源,然后选择播放其中的一个。这会在一开始的时候消耗很多资源来加载多个音频文件,但会在切换音频文件的时候更快。

<audio
 id="audio-player1"
 src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3"
 controls
  浏览器不支持音频播放。
</audio>
<audio
 id="audio-player2"
 style="display: none;"
 src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3"
 controls
  浏览器不支持音频播放。
</audio>
<audio
 id="audio-player3"
 style="display: none;"
 src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-3.mp3"
 controls
  浏览器不支持音频播放。
</audio>


const audio1 = document.querySelector('#audio-player1');
const audio2 = document.querySelector('#audio-player2');
const audio3 = document.querySelector('#audio-player3');
audio1.addEventListener('timeupdate', () => {
  if (audio1.currentTime >= audio1.duration) {
    audio1.style.display = 'none';
    audio2.style.display = 'block';
    audio2.play();
audio2.addEventListener('timeupdate', () => {
  if (audio2.currentTime >= audio2.duration) {
    audio2.style.display = 'none';
    audio3.style.display = 'block';
    audio3.play();
audio3.addEventListener('timeupdate', () => {
  if (audio3.currentTime >= audio3.duration) {
    audio3.style.display = 'none';
    audio1.style.display = 'block';
    audio1.play();

引用

HTML5 Audio & Video - 兼容性总结(一)

HTML5 Audio的兼容性问题和优化

移动端浏览器对audio标签的限制总结


3. 改变音频播放器的样式

现在让我们来看看如何改变 <audio> 元素控制面板的样式。

一个有控制面板的音频元素

根据 MDN 的描述,

<audio> 元素没有自带的固有视觉样式,除非如果声明了 controls 属性,则会显示浏览器的默认控件。
默认控件的 display 的默认值为 inline 。将该值设为 block 通常会对定位和布局有好处,除非你想将控件放在文本块或类似元素中。
你可以使用作用于整个控件的属性来为其设置样式。例如可用 border border-radius padding , margin 等等。但你不能设置音频播放器中的单个组件(如改变按钮大小、改变图标或字体等)。控件在不同的浏览器中也有所不同。

我们将会介绍两种不同的方式来改变音频播放器的样式。

3.1 用 CSS 改变 <audio> 元素的样式

我们可以使用 <audio> 元素的伪元素来改变 <audio> 元素控制面板的样式。

⚠️ 注意: 只有以 WebKit 或 Blink 为内核的浏览器(例如 Safari 和 Chrome)才会支持 WebKit 伪元素。

  1. ::-webkit-media-controls-panel : 控制面板的容器。
  2. ::-webkit-media-controls-mute-button : 控制面板上的静音按钮。
  3. ::-webkit-media-controls-play-button : 控制面板上的播放按钮。
  4. ::-webkit-media-controls-current-time-display : 控制面板上展示当前播放时间的文字。
  5. ::-webkit-media-controls-time-remaining-display : 控制面板上展示总播放时长的文字。
  6. ::-webkit-media-controls-timeline : 控制面板上的播放时间线。
  7. ::-webkit-media-controls-volume-slider : 控制面板上控制音量的滚动条,在 Chrome 上滚动条会出现在悬浮在静音按钮上时。
<audio
 id="audio-player"
 src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3"
 controls
  浏览器不支持音频播放。
</audio> 


#audio-player::-webkit-media-controls-panel {
  background: lightblue;
#audio-player::-webkit-media-controls-current-time-display {
  color: red;
  font-weight: bold;
#audio-player::-webkit-media-controls-time-remaining-display {
  color: gray;
}

如果想要查看更多关于 <audio> 标签伪元素的示例代码,可以戳我的 CodePen

如果想要查看 <audio> 标签伪元素的完整列表,可以看看 mediaControl.css

3.2 使用 JavaScript 和 CSS 自定义一个音频播放器

我们可以创造一个没有 controls 属性的 <audio> 元素,然后创造一些元素例如按钮等来操作音频。通过点击这些元素,我们可以改变音频的相关属性或是调用相关方法来完成操作。我们也可以通过监听音频时间来改变样式。

<div id="audio-player">
  <!-- 音频资源 -->
  <audio id="audio" src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3" />
  <!-- 时间线 -->
  <div id="timeline">
    <div id="total-time">
      <div id="current-timeline" />
    </div>
    <div id="time-display">
      <span id="current-time" />
      <span id="duration" />
    </div>
  </div>
  <!-- 操作音频 -->
  <div id="operations">
    <button id="play-button">
      <img src="https://cdn-icons-png.flaticon.com/512/27/27223.png" />
    </button>
    <button id="pause-button">
      <img src="https://cdn-icons-png.flaticon.com/512/64/64485.png" />
    </button>
  </div>
</div>


#audio-player {
  width: 500px;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
/* 时间线 */
#timeline {
  width: 400px;
  margin: 20px;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
#total-time {
  width: 100%;
  height: 10px;
  border-radius: 20px;
  background: #063f60;
  margin: 10px;
  position: relative;
#current-timeline {
  height: 10px;
  border-radius: 20px 0 0 20px;
  background: #87ffc3;
  position: absolute;
  top: 0;
  left: 0;
#time-display {
  width: 100%;
  display: flex;
  justify-content: space-between;
  color: gray;
/* 操作音频 */
#operations {
  display: flex;
  justify-content: center;
button {
  background: transparent;
  border: 0;
  width: 40px;
  margin: 10px 20px;
button:hover {
  cursor: pointer;
button > img {
  width: 100%;
#pause-button {
  display: none;
} 


// 音频资源
const audio = document.querySelector('#audio');
// 操作
const playButton = document.querySelector('#play-button');
const pauseButton = document.querySelector('#pause-button');
audio.addEventListener('play', () => {
  playButton.style.display = 'none';
  pauseButton.style.display = 'block';
audio.addEventListener('pause', () => {
  playButton.style.display = 'block';
  pauseButton.style.display = 'none';
playButton.addEventListener('click', () => {
  audio.play();
pauseButton.addEventListener('click', () => {
  audio.pause();
// 时间线
const currentTimeText = document.querySelector('#current-time');
const currentTimeLine = document.querySelector('#current-timeline');
const durationElement = document.querySelector('#duration');
let currentTime = new Date(null);
let duration = new Date(null);
audio.addEventListener('loadedmetadata', () => {
  duration = new Date(null);
  duration.setSeconds(audio.duration);
  durationElement.innerHTML = `${duration.toISOString().substring(11, 19)}`;
  currentTime = new Date(null);
  currentTime.setSeconds(0);
  currentTimeText.innerHTML = `${currentTime.toISOString().substring(11, 19)}`;
audio.addEventListener('timeupdate', () => {